本文目录导读:

我来详细讲解ThinkPHP项目中大文本的存储与检索方案。
存储方案选择
数据库存储方案
MySQL TEXT类型选择
// 根据数据大小选择合适类型 - TINYTEXT: 255字节 - TEXT: 64KB - MEDIUMTEXT: 16MB - LONGTEXT: 4GB
建表示例
CREATE TABLE `article` ( `id` int(11) NOT NULL AUTO_INCREMENT, varchar(255) NOT NULL, `content` longtext, `content_hash` char(32) DEFAULT NULL, `word_count` int(11) DEFAULT '0', `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_content_hash` (`content_hash`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
文件系统存储
<?php
namespace app\common\storage;
use think\facade\Filesystem;
class TextFileStorage
{
/**
* 保存大文本到文件
*/
public static function save($content, $path = 'text')
{
$filename = md5(uniqid() . time()) . '.txt';
$filePath = $path . '/' . date('Y/m/d') . '/' . $filename;
// 使用ThinkPHP文件系统
Filesystem::put($filePath, $content);
return [
'path' => $filePath,
'url' => Filesystem::url($filePath)
];
}
/**
* 读取文件内容
*/
public static function read($filePath)
{
return Filesystem::get($filePath);
}
/**
* 删除文件
*/
public static function delete($filePath)
{
return Filesystem::delete($filePath);
}
}
模型层设计
模型类实现
<?php
namespace app\common\model;
use think\Model;
use think\facade\Cache;
class Article extends Model
{
protected $name = 'article';
protected $pk = 'id';
// 自动时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'created_at';
protected $updateTime = 'updated_at';
// 字段类型转换
protected $type = [
'id' => 'integer',
'word_count' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
// JSON字段
protected $json = ['extra_data'];
/**
* 保存大文本内容
*/
public function saveContent($title, $content, $options = [])
{
$this->title = $title;
$this->content = $content;
$this->content_hash = md5($content);
$this->word_count = mb_strlen(strip_tags($content), 'utf-8');
if (isset($options['extra_data'])) {
$this->extra_data = $options['extra_data'];
}
return $this->save();
}
/**
* 获取文章内容(带缓存)
*/
public function getContentWithCache($id)
{
$cacheKey = 'article_content_' . $id;
return Cache::remember($cacheKey, function() use ($id) {
$article = self::find($id);
return $article ? $article->content : null;
}, 3600);
}
/**
* 清空文章缓存
*/
public function clearCache($id)
{
Cache::delete('article_content_' . $id);
Cache::delete('article_detail_' . $id);
}
}
检索方案实现
MySQL全文索引
<?php
namespace app\common\search;
use think\facade\Db;
class MysqlSearch
{
/**
* 创建全文索引
*/
public function createFulltextIndex()
{
$sql = "ALTER TABLE article ADD FULLTEXT INDEX ft_content (title, content) WITH PARSER ngram";
Db::execute($sql);
}
/**
* 全文搜索
*/
public function search($keyword, $page = 1, $limit = 10)
{
$where = "MATCH(title, content) AGAINST (:keyword IN NATURAL LANGUAGE MODE)";
$total = Db::name('article')
->whereRaw($where, ['keyword' => $keyword])
->count();
$list = Db::name('article')
->whereRaw($where, ['keyword' => $keyword])
->field('id, title,
LEFT(content, 200) as content_preview,
MATCH(title, content) AGAINST (:keyword2) as relevance')
->bind(['keyword2' => $keyword])
->order('relevance DESC')
->page($page, $limit)
->select();
return [
'total' => $total,
'list' => $list
];
}
}
使用Elasticsearch(推荐)
<?php
namespace app\common\search;
use Elasticsearch\ClientBuilder;
class ElasticsearchService
{
private $client;
public function __construct()
{
$config = config('elasticsearch');
$this->client = ClientBuilder::create()
->setHosts($config['hosts'])
->build();
}
/**
* 创建索引
*/
public function createIndex($indexName = 'articles')
{
$params = [
'index' => $indexName,
'body' => [
'settings' => [
'number_of_shards' => 3,
'number_of_replicas' => 1
],
'mappings' => [
'properties' => [
'id' => ['type' => 'integer'],
'title' => [
'type' => 'text',
'analyzer' => 'ik_max_word',
'search_analyzer' => 'ik_smart'
],
'content' => [
'type' => 'text',
'analyzer' => 'ik_max_word',
'search_analyzer' => 'ik_smart',
'index_options' => 'offsets'
],
'created_at' => ['type' => 'date']
]
]
]
];
return $this->client->indices()->create($params);
}
/**
* 索引文档
*/
public function indexDocument($article)
{
$params = [
'index' => 'articles',
'id' => $article['id'],
'body' => [
'id' => $article['id'],
'title' => $article['title'],
'content' => $article['content'],
'created_at' => $article['created_at']
]
];
return $this->client->index($params);
}
/**
* 搜索
*/
public function search($keyword, $page = 1, $size = 10)
{
$params = [
'index' => 'articles',
'body' => [
'from' => ($page - 1) * $size,
'size' => $size,
'query' => [
'multi_match' => [
'query' => $keyword,
'fields' => ['title^3', 'content'],
'operator' => 'and'
]
],
'highlight' => [
'pre_tags' => ['<em class="highlight">'],
'post_tags' => ['</em>'],
'fields' => [
'title' => ['fragment_size' => 100],
'content' => ['fragment_size' => 200, 'number_of_fragments' => 2]
]
]
]
];
$response = $this->client->search($params);
return $this->formatSearchResult($response, $page, $size);
}
/**
* 格式化搜索结果
*/
private function formatSearchResult($response, $page, $size)
{
$hits = $response['hits'];
$list = [];
foreach ($hits['hits'] as $hit) {
$item = $hit['_source'];
$item['highlight'] = $hit['highlight'] ?? [];
$list[] = $item;
}
return [
'total' => $hits['total']['value'],
'page' => $page,
'size' => $size,
'list' => $list
];
}
}
简易搜索封装
<?php
namespace app\common\search;
use think\facade\Db;
use app\common\search\ElasticsearchService;
class TextSearch
{
private $searchEngine;
public function __construct($engine = null)
{
$this->searchEngine = $engine ?: new ElasticsearchService();
}
/**
* 综合搜索
*/
public function search($keyword, $options = [])
{
// 关键词预处理
$keyword = trim($keyword);
if (mb_strlen($keyword) < 2) {
return ['total' => 0, 'list' => []];
}
// 根据关键词长度选择搜索方式
if (mb_strlen($keyword) <= 10) {
// 短关键词使用ES
return $this->searchEngine->search($keyword,
$options['page'] ?? 1,
$options['size'] ?? 10);
} else {
// 长文本使用MySQL LIKE查询
return $this->mysqlSearch($keyword, $options);
}
}
/**
* MySQL搜索
*/
private function mysqlSearch($keyword, $options)
{
$page = $options['page'] ?? 1;
$limit = $options['size'] ?? 10;
$like = '%' . $keyword . '%';
$query = Db::name('article')
->where('title', 'like', $like)
->whereOr('content', 'like', $like);
$total = $query->count();
$list = $query
->field('id, title, LEFT(content, 200) as content_preview')
->page($page, $limit)
->select();
return [
'total' => $total,
'page' => $page,
'size' => $limit,
'list' => $list
];
}
}
大文本优化策略
分块存储
<?php
namespace app\common\storage;
class TextChunk
{
private $chunkSize = 60000; // 每块大小
/**
* 分块保存
*/
public function saveChunked($content, $articleId)
{
// 计算分块数量
$chunks = mb_str_split($content, $this->chunkSize);
// 保存到数据库
$data = [];
foreach ($chunks as $index => $chunk) {
$data[] = [
'article_id' => $articleId,
'chunk_index' => $index,
'content' => $chunk
];
}
Db::name('article_chunks')->insertAll($data);
}
/**
* 读取完整内容
*/
public function getFullContent($articleId)
{
$chunks = Db::name('article_chunks')
->where('article_id', $articleId)
->order('chunk_index')
->column('content');
return implode('', $chunks);
}
}
内容压缩存储
<?php
namespace app\common\storage;
class CompressedStorage
{
/**
* 压缩后保存
*/
public function saveCompressed($content, $articleId)
{
// GZIP压缩
$compressed = gzcompress($content, 9);
// Base64编码存储
$encoded = base64_encode($compressed);
return Db::name('article_compressed')->insert([
'article_id' => $articleId,
'content' => $encoded,
'original_size' => strlen($content),
'compressed_size' => strlen($encoded),
'created_at' => date('Y-m-d H:i:s')
]);
}
/**
* 读取解压
*/
public function getContent($articleId)
{
$record = Db::name('article_compressed')
->where('article_id', $articleId)
->find();
if (!$record) {
return null;
}
// 解码并解压
$compressed = base64_decode($record['content']);
return gzuncompress($compressed);
}
}
完整使用示例
<?php
namespace app\index\controller;
use think\Controller;
use app\common\model\Article;
use app\common\search\TextSearch;
use app\common\storage\TextFileStorage;
class ArticleController extends Controller
{
/**
* 保存文章
*/
public function save()
{
$data = $this->request->post();
// 验证数据
validate(['title' => 'require|max:255', 'content' => 'require'])
->check($data);
$article = new Article();
// 保存文章
$article->saveContent($data['title'], $data['content'], [
'extra_data' => [
'source' => $data['source'] ?? '',
'author' => $data['author'] ?? ''
]
]);
// 索引到ES
try {
$es = new ElasticsearchService();
$es->indexDocument($article->toArray());
} catch (\Exception $e) {
// 记录日志,不影响主流程
trace('ES索引失败: ' . $e->getMessage(), 'error');
}
return json(['status' => 1, 'message' => '保存成功']);
}
/**
* 搜索文章
*/
public function search()
{
$keyword = $this->request->get('keyword', '');
$page = $this->request->get('page', 1);
if (empty($keyword)) {
return json(['status' => 0, 'message' => '关键词不能为空']);
}
$searchService = new TextSearch();
$result = $searchService->search($keyword, ['page' => $page]);
return json([
'status' => 1,
'data' => $result
]);
}
/**
* 获取完整文章
*/
public function detail($id)
{
$article = Article::find($id);
if (!$article) {
return json(['status' => 0, 'message' => '文章不存在']);
}
// 浏览量增加
$article->setInc('views');
// 获取内容(带缓存)
$content = $article->getContentWithCache($id);
return json([
'status' => 1,
'data' => [
'id' => $article->id,
'title' => $article->title,
'content' => $content,
'created_at' => $article->created_at,
'views' => $article->views
]
]);
}
}
性能优化建议
缓存策略
- 使用Redis缓存热门文章内容
- 缓存搜索结果
- 合理的缓存过期时间
数据库优化
- 合理使用索引
- 分表存储大数据量文本
- 使用读写分离
检索优化
- 结合ES进行全文检索
- 使用IK分词器提高中文搜索准确性
- 优化查询语句
异步处理
- 使用消息队列异步索引
- 异步生成摘要
这个方案提供了完整的大文本存储与检索解决方案,可以根据实际需求选择合适的方式组合使用。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播