🔍 搜索系统概览

CMS的搜索系统是一个功能强大的智能搜索解决方案,支持多模型搜索、敏感词过滤、搜索历史记录、实时统计和个性化展示。搜索历史可独立成文,具有完整的内容管理功能。

✨ 核心功能特性

1. 智能搜索引擎

  • 多模型搜索:支持多个内容模型同时搜索
  • 智能分词:支持搜索引擎式拆词(空格、引号)
  • 字段配置:可配置每个模型的搜索字段
  • 结果优化:智能排序和分页

2. 搜索历史管理

  • 自动记录:自动记录用户搜索词
  • 独立成文:每个搜索词生成独立页面
  • 点击统计:记录搜索页面访问量
  • 状态管理:支持完整的发布工作流

3. 敏感词过滤

  • 实时检测:搜索时自动检测敏感词
  • 智能屏蔽:发现敏感词时返回特定结果
  • 可配置开关:支持开启/关闭过滤功能
  • 灵活管理:可自定义敏感词库

4. 权限与安全

  • API限流:搜索接口支持限流保护
  • 并发控制:原子操作更新点击量
  • 输入验证:严格的参数验证
  • 防攻击:防止SQL注入和暴力搜索

5. 模板系统

  • 主题继承:支持多主题模板
  • 结果模板:专门的搜索结果页面
  • 历史模板:搜索历史详情页面
  • 可扩展:支持自定义模板

6. 统计与分析

  • 搜索热度:记录每个搜索词的点击量
  • 使用频率:统计搜索历史使用次数
  • 时间分析:分析搜索时间分布
  • 结果分析:统计搜索结果数量

7. API接口

  • 搜索接口:统一的搜索入口
  • 统计接口:点击量统计接口
  • 管理接口:搜索历史管理接口
  • RESTful:符合RESTful设计

8. 用户体验

  • 实时搜索:快速返回搜索结果
  • 分页支持:支持查看更多结果
  • 多维度搜索:可按模型路径搜索
  • 智能提示:可扩展搜索建议

🎯 后台使用指南

1. 管理搜索历史

在Admin中的功能
列表页显示
 
ID | 标题(搜索词) | 状态标签 | 创建时间 | 更新时间 | 扩展功能
 
 
状态颜色标签
 
colors = {
    0: '#666',   # 已入库 - 深灰
    1: '#999',   # 草稿 - 灰色
    2: '#ffc107', # 已提交 - 黄色
    3: '#17a2b8', # 审核中 - 青色
    4: '#28a745', # 已通过 - 绿色
    5: '#dc3545', # 已驳回 - 红色
    6: '#6c757d', # 已归档 - 灰暗
    99: '#007bff'  # 已发布 - 蓝色
}
 
 
扩展功能
 
1. 浏览:查看搜索历史页面
2. 移除静态:清除该页面的静态缓存
 
 
编辑页面字段
 
基础信息:
- 标题:搜索词
- 标题拼音:自动生成
- 状态:选择发布状态

内容管理: - 内容:富文本编辑器,可编写详细内容

发布设置: - 允许评论:控制是否允许评论 - 点击量:自动统计,只读 - 个性模板:选择自定义模板 - 更新时间/创建时间:自动记录,只读

 
 

2. 搜索配置管理

搜索配置模型(SearchConfig)字段:
 
# 基本配置
updateswitch: 是否记录搜索历史
searchengine: 是否启用搜索引擎模式

内容模型搜索

contentmodels: 启用搜索的内容模型列表 contentfields: 内容模型的搜索字段

用户空间搜索

userspacesearch: 用户自定义模型搜索配置

 
 
配置示例
 
# 配置内容模型搜索
contentmodels = ['articles.article', 'downloads.download']
contentfields = ['title', 'content', 'meta_description']

配置用户空间搜索

userspacesearch = { 'articles.article': { 'searchfields': ['title', 'content', 'author'], 'displayfields': ['title', 'author', 'createtime'], 'limit': 10 } }

 
 

3. 敏感词配置

敏感词配置模型(SensitiveConfig):
 
filterswitch: 是否开启敏感词过滤

其他敏感词配置项

 
 
过滤流程
 
1. 用户发起搜索请求

  1. 系统检查敏感词过滤开关
  2. 如果开启,检测搜索词是否包含敏感词
  3. 包含敏感词:返回特定结果,不执行搜索
  4. 不包含敏感词:正常执行搜索
 
 

🔧 搜索功能详解

1. 搜索流程

前端搜索请求
 
GET /search/?key=搜索词&model_path=articles.article
 
 
后端处理流程
 
1. 获取搜索词和模型路径参数
  • 敏感词检测(如果开启)
  • 记录搜索历史(如果开启)
  • 构建动态查询条件
  • 执行多模型搜索
  • 返回格式化结果
  • 更新搜索历史点击量

  •  
     

    2. 智能分词算法

    搜索引擎模式
     
    def split_query(self, query):
    """搜索引擎式拆词逻辑"""

    1. 按空格拆分基础词

    base_words = re.split(r'\s+', query.strip())

    2. 提取带引号的短语

    phrases = re.findall(r'\"(.+?)\"', query)

    3. 合并结果并去重

    terms = list(set(base_words + phrases))

    4. 过滤空词

    return [term for term in terms if term]

     
     
    查询构建
     
    def builddynamicquery(self, fields, query, searchengine=False):
    """构建动态查询条件"""
    qobjects = Q()
    if searchengine:
        # 搜索引擎模式:使用AND逻辑连接各分词
        terms = self.splitquery(query)
        for term in terms:
            termq = Q()
            for field in fields:
                termq |= (Q({f"{field}_icontains": term})
                           & Q({f"{field}isnull": False}))
            qobjects &= termq
    else:
        # 普通模式:使用OR逻辑连接各字段
        for field in fields:
            qobjects |= (Q(*{f"{field}icontains": query})
                          & Q(*{f"{field}_isnull": False}))
    return qobjects
     
     

    3. 多模型搜索实现

    指定模型搜索
     
    # 搜索指定模型
    /search/?key=关键词&model_path=articles.article

    只搜索文章模型

    在指定模型的配置字段中搜索

     
     
    全站搜索
     
    # 搜索所有配置的模型
    
    

    /search/?key=关键词

    在所有配置的内容模型中搜索

    支持分模型显示结果

     
     
    用户空间搜索
     
    # 搜索用户自定义的模型
    
    

    需要预先在SearchConfig中配置

    userspacesearch = { 'app.model': { 'searchfields': [...], # 搜索字段 'displayfields': [...], # 显示字段 'limit': 10 # 结果限制 } }

     
     

    4. 搜索历史管理

    自动记录策略
     
    def createuniquesearchhistory(self, title):
        """创建唯一的搜索历史记录"""
        try:
            # 使用getorcreate确保唯一性
            obj, created = SearchHistory.objects.getorcreate(
                title=title,
                defaults={
                    'pytitle': '',  # 可自动生成拼音
                    'content': '',   # 默认空内容
                    'status': SearchHistory.StatusChoices.REVIEW
                })
            return (created, obj)
        except IntegrityError:
            # 处理并发冲突
            existing = SearchHistory.objects.filter(title=title).first()
            return (False, existing) if existing else (False, None)
     
     
    点击量统计
     
    @requireGET
    def incrementsearchhits(request):
        """原子操作更新点击量"""
        try:
            objid = request.GET.get('id')
            record = SearchHistory.objects.get(pk=objid)
            # 原子操作,避免并发问题
            record.hits = models.F('hits') + 1
            record.save(updatefields=['hits'])
            record.refreshfromdb()  # 重新加载最新值
            return JsonResponse({'id': record.id, 'hits': record.hits})
        except SearchHistory.DoesNotExist:
            return JsonResponse({'error': 'Record not found'}, status=404)
     
     

    📊 字段说明速查

    SearchHistory模型字段(继承CreateCommon)

    字段分组
    字段名
    说明
    必填
    默认值
    基础信息
    title
    搜索词/标题
    -
     
    pytitle
    标题拼音
    自动
     
    status
    审核状态
    99(已发布)
    内容管理
    content
    详细内容
    发布设置
    allowcomment
    允许评论
    True
     
    hits
    点击量
    自动
    0
     
    template
    个性模板
    导航关联
    previousobj
    上一篇
    null
     
    nextobj
    下一篇
    null
    权限控制
    staffonly
    仅员工可见
    False
     
    allowedgroups
    允许用户组
    时间信息
    createtime
    创建时间
    自动
    当前时间
     
    updatetime
    更新时间
    自动
    当前时间
    其他字段
    thumbimage
    缩略图
     
    istop
    是否推荐
    False

    SearchConfig配置字段

    字段名
    说明
    类型
    默认值
    updateswitch
    是否记录搜索历史
    Boolean
    False
    searchengine
    是否启用搜索引擎模式
    Boolean
    False
    contentmodels
    内容模型列表
    JSON
    []
    contentfields
    内容模型搜索字段
    JSON
    ['title']
    userspacesearch
    用户空间搜索配置
    JSON
    {}

    🎨 前端使用指南

    1. 搜索表单示例

    基础搜索表单
     
    <form action="/search/" method="get" class="search-form">
        <input type="text" name="key" placeholder="输入搜索关键词..." 
               value="{{ query|default:'' }}" class="search-input">
        <button type="submit" class="search-button">搜索</button>
    </form>
     
     
    高级搜索表单
     
    <form action="/search/" method="get" class="advanced-search">
        <input type="text" name="key" placeholder="搜索关键词..." 
               value="{{ query|default:'' }}">

    <!-- 模型选择 --> <select name="model_path"> <option value="">全站搜索</option> <option value="articles.article">文章</option> <option value="downloads.download">下载</option> <option value="wikis.wiki">百科</option> </select>

    <!-- 搜索模式 --> <label> <input type="checkbox" name="exact" value="1"> 精确匹配 </label>

    <button type="submit">搜索</button> </form>

     
     

    2. 搜索结果展示

    模板结构
     
    {# 搜索结果页面 #}
    {% extends "base.html" %}

    {% block content %} <div class="search-results"> <h1>搜索"{{ query }}"的结果</h1>

    {% if sensitive %} <div class="alert alert-warning"> 搜索词包含敏感内容,已屏蔽搜索结果。 </div> {% else %} {% if not has_results %} <div class="no-results"> 没有找到相关结果。 </div> {% endif %}

    {# 按模型分组显示结果 #} {% for modelpath, data in contentresults.items %} <div class="model-group"> <h2>{{ data.verbose_name }} ({{ data.results|length }})</h2>

    {% for result in data.results %} <div class="result-item"> <h3> <a href="{{ result.getabsoluteurl }}"> {{ result.title }} </a> </h3> {% if result.metadescription %} <p>{{ result.metadescription|truncatechars:200 }}</p> {% endif %} <div class="result-meta"> <span class="update-time"> {{ result.update_time|date:"Y-m-d" }} </span> </div> </div> {% endfor %}

    {% if data.more %} <div class="more-results"> <a href="/search/?key={{ query|urlencode }}&modelpath={{ modelpath }}"> 查看更多{{ data.verbose_name }}结果 </a> </div> {% endif %} </div> {% endfor %}

    {# 用户空间搜索结果 #} {% for modelpath, data in userresults.items %} <div class="user-space-results"> <h2>{{ data.verbose_name }}</h2> <table> <thead> <tr> {% for field in data.fields %} <th>{{ field|title }}</th> {% endfor %} </tr> </thead> <tbody> {% for result in data.results %} <tr> {% for field in data.fields %} <td>{{ result|getattr:field }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> </div> {% endfor %} {% endif %} </div> {% endblock %}

     
     

    3. 搜索历史页面

    搜索历史详情页
     
    {# 搜索历史独立页面 #}
    {% extends "base.html" %}

    {% block title %}{{ search_obj.title }} - 搜索历史{% endblock %}

    {% block content %} <article class="search-history-detail"> <header> <h1>{{ search_obj.title }}</h1>

    {% if searchobj.subtitle %} <h2 class="subtitle">{{ searchobj.subtitle }}</h2> {% endif %}

    <div class="meta"> <span>搜索次数: {{ searchobj.hits }}</span> <span>更新时间: {{ searchobj.update_time|date:"Y-m-d H:i" }}</span> </div> </header>

    {% if searchobj.content %} <div class="content"> {{ searchobj.content|safe }} </div> {% endif %}

    {# 相关搜索结果 #} <div class="related-results"> <h3>相关搜索结果</h3> {% include "search/resultspartial.html" %} </div>

    {% if searchobj.allowcomment %} <div class="comments"> {% include "comments/comments.html" %} </div> {% endif %} </article> {% endblock %}

     
     

    4. 实时点击量更新

    AJAX更新点击量
     
    // 在搜索历史详情页加载时更新点击量
    document.addEventListener('DOMContentLoaded', function() {
        const searchId = '{{ searchobj.id }}';

    // 发送点击量更新请求 fetch(/api/search/increment-hits/?id=${searchId}, { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest', } }) .then(response => response.json()) .then(data => { if (data.hits) { // 可更新页面上的点击量显示 const hitsElement = document.querySelector('.hits-count'); if (hitsElement) { hitsElement.textContent = data.hits; } } }) .catch(error => console.error('Error updating hits:', error)); });

     
     

    💡 最佳实践建议

    1. 搜索性能优化

    索引优化
     
    # 为搜索字段添加索引
    class Article(models.Model):
        title = models.CharField(maxlength=200, dbindex=True)
        content = models.TextField()

    class Meta:
        indexes = [
            models.Index(fields=['title'], name='idx_article_title'),
            # 可考虑全文索引
        ]</code></pre></div><div class="hyc-code-scrollbar__track" style="bottom:4px;height:7px;left:4px;position:absolute;right:4px;"><div class="hyc-code-scrollbar__thumb" style="display:block;height:100%;position:relative;width:0px;">&nbsp;</div></div><div style="border-radius:3px;bottom:2px;position:absolute;right:2px;top:2px;width:6px;"><div style="background-color:rgba(0, 0, 0, 0.2);border-radius:inherit;cursor:pointer;display:block;height:0px;position:relative;width:100%;">&nbsp;</div></div></div></div><div class="ybc-p"><strong>查询优化</strong>:</div><div class="hyc-common-markdown__code"><div class="hyc-common-markdown__code__hd">&nbsp;</div><div class="hyc-code-scrollbar" style="height:100%;overflow:hidden;position:relative;width:100%;"><div class="hyc-code-scrollbar__view" style="inset:0px;margin-bottom:-13px;margin-right:-13px;overflow:scroll;position:relative;"><pre><code class="language-python"># 只查询需要的字段
    

    results = model.objects.filter(qobjects).only( 'title', 'metadescription', 'update_time', 'pk' )

    限制结果数量

    results = results[:11] # 10+1用于判断是否有更多

     
     
    缓存策略
     
    # 缓存热门搜索结果
    from django.core.cache import cache

    def getsearchresults(query, modelpath=None): cachekey = f'search:{query}:{modelpath}' results = cache.get(cachekey)

    if not results: # 执行搜索 results = performsearch(query, modelpath) # 缓存5分钟 cache.set(cache_key, results, 300)

    return results

     
     

    2. 搜索体验优化

    搜索建议
     
    // 实时搜索建议
    const searchInput = document.querySelector('.search-input');
    const suggestions = document.querySelector('.search-suggestions');

    searchInput.addEventListener('input', function() { const query = this.value.trim();

    if (query.length >= 2) { fetch(/api/search/suggestions/?q=${encodeURIComponent(query)}) .then(response => response.json()) .then(data => { suggestions.innerHTML = ''; data.suggestions.forEach(suggestion => { const li = document.createElement('li'); li.textContent = suggestion; li.addEventListener('click', () => { searchInput.value = suggestion; suggestions.innerHTML = ''; }); suggestions.appendChild(li); }); }); } else { suggestions.innerHTML = ''; } });

     
     
    分页优化
     
    # 搜索结果分页
    from django.core.paginator import Paginator

    def search_view(request): query = request.GET.get('key', '') page = request.GET.get('page', 1)

    results = perform_search(query) paginator = Paginator(results, 10) # 每页10条

    try: pageobj = paginator.page(page) except PageNotAnInteger: pageobj = paginator.page(1) except EmptyPage: pageobj = paginator.page(paginator.numpages)

    return render(request, 'search/results.html', { 'query': query, 'pageobj': pageobj, 'results': pageobj.objectlist, })

     
     

    3. 安全与防护

    输入验证
     
    # 验证搜索词长度
    def cleansearchquery(query):
        query = query.strip()

    # 长度限制 if len(query) > 100: raise ValidationError("搜索词过长")

    # 字符限制 if not re.match(r'^[\w\s"\'-]+$', query): raise ValidationError("包含非法字符")

    return query

     
     
    频率限制
     
    # 使用Django Ratelimit
    from django_ratelimit.decorators import ratelimit

    @ratelimit(key='ip', rate='10/m', method='GET') def searchview(request): # 搜索逻辑 pass

     
     
    防恶意搜索
     
    # 检测恶意搜索模式
    def ismalicious_search(query):
        # 检测重复字符
        if re.match(r'^(.)\1+$', query):
            return True

    # 检测过长无意义词 if len(query) > 50 and not re.search(r'[\u4e00-\u9fa5]', query): return True

    # 检测SQL注入特征 sqlkeywords = ['SELECT', 'INSERT', 'DELETE', 'UPDATE', 'DROP', 'UNION'] for keyword in sqlkeywords: if keyword in query.upper(): return True

    return False

     
     

    4. 数据分析与监控

    搜索统计
     
    # 记录搜索统计
    class SearchStats(models.Model):
        query = models.CharField(maxlength=200)
        count = models.IntegerField(default=1)
        lastsearched = models.DateTimeField(autonow=True)
        hasresults = models.BooleanField(default=True)

    @classmethod def recordsearch(cls, query, hasresults=True): stats, created = cls.objects.getorcreate( query=query, defaults={'hasresults': hasresults} ) if not created: stats.count += 1 stats.hasresults = hasresults stats.save(updatefields=['count', 'hasresults', 'lastsearched'])

     
     
    热门搜索
     
    # 获取热门搜索词
    def gethot_searches(limit=10, days=7):
        from django.utils import timezone
        from django.db.models import Count

    cutoff_date = timezone.now() - timezone.timedelta(days=days)

    return SearchStats.objects.filter( lastsearchedgte=cutoffdate ).orderby('-count')[:limit]

     
     
    无结果搜索
     
    # 分析无结果的搜索词
    def getnoresultsearches(limit=20):
        return SearchStats.objects.filter(
            hasresults=False
        ).orderby('-count', '-last_searched')[:limit]
     
     
    这个搜索系统提供了完整的搜索功能、搜索历史管理和分析工具,可以显著提升网站的用户体验和内容发现能力。