[开发]问答社区(qa)插件说明

👑Lv.11 元老 🌞 骨干会员
2026-10-25 01:18:14

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/qa/说明.md。

问答社区(qa)插件说明

版本:1.0.0 | 作者:FlintHub | 状态:⛔ 未启用

功能:完整问答闭环——提问 / 回答 / 投票 / 采纳最佳答案 / 标签管理,含前台与后台两套页面。


一、功能简介

  • 提问:标题 + 富文本正文(编辑器 base64 原文,显示时统一解码 + 净化)+ 标签多选
  • 回答:对问题进行回答,支持 htmx 局部提交
  • 投票:对问题与回答均可投「赞成 / 反对」,重复点击取消,不能给自己投票;

新增赞成票给内容作者 +1 积分(与核心帖子获赞口径一致)

  • 采纳最佳答案:仅问题作者可采纳;采纳后给回答者发放积分(后台可配,默认 5),自问自答不奖励
  • 标签体系:标签增删改 + 每标签问题计数自动刷新(改标签时新旧标签计数一并刷新,无残留)
  • 列表四视图:最新(newest)/ 最高票(votes)/ 待回答(unanswered)/ 热门(hot,按 view_count + answer_count*3 加权)
  • 后台管理:问题列表与编辑、删除、标签管理
  • 前端交互:htmx 局部刷新(投票列整列交换,高亮由服务端渲染),零内联 JS

二、目录结构

plugins/qa/
├── plugin.json                      # 插件清单(8 个钩子 + admin_url + permissions: route:admin, system:settings)
├── Plugin.php                       # 主类:建表、提问/回答/投票/采纳/标签的业务逻辑(控制器与 CLI 共用)
├── QaController.php                 # 前台控制器(列表/提问/详情/回答/投票/采纳/标签)
├── AdminController.php              # 后台控制器(问题管理 / 标签管理)
├── hook/
│   ├── init_after.php               # 幂等建表兜底(缓存标记 protected/.qa_table_cache)
│   ├── route_register.php           # 前台路由注册
│   ├── admin_route_register.php     # 后台路由注册(需 route:admin)
│   ├── nav_main_links.php           # 顶部主导航入口
│   ├── nav_plugin_links.php         # 左栏「应用」/ 顶部「应用」下拉入口
│   ├── nav_user_menu_items.php      # 用户下拉菜单入口(仅登录用户)
│   ├── layout_head_end.php          # 【空实现,直接 return】样式由各视图 section('css') 自带
│   └── right_sidebar_after.php      # 右栏「最新问题 / 热门问题 / 热门标签」(仅 /qa 前缀页面输出)
├── views/
│   ├── question/list.php            # 问题列表(四 Tab + 分页)
│   ├── question/ask.php             # 提问 / 编辑(共用,question 为 null 时是新增)
│   ├── question/show.php            # 问题详情(问题卡 + 回答列表 + 回答表单)
│   ├── tag/list.php                 # 标签列表
│   └── admin/{index,edit,tags}.php  # 后台问题列表 / 编辑 / 标签管理
├── assets/                          # style.css / script.js(零内联)
├── data/                            # qa.sqlite(独立库)+ .htaccess / web.config
└── lang/                            # zh.php / en.php / zh_tw.php(各 83 键,三语零差异)

三、数据表

插件独立库:plugins/qa/data/qa.sqlite

表用途
qa_questions问题(user_id / title / content / view_count / vote_count / answer_count / accepted_answer_id / status)
qa_answers回答(question_id / user_id / content / vote_count / is_accepted)
qa_votes投票(user_id / target_type(q/a)/ target_id / vote;UNIQUE(user_id, target_type, target_id))
qa_tags标签(name UNIQUE / description / question_count)
qa_question_tags问题 ↔ 标签关联(PRIMARY KEY(question_id, tag_id))

  • 索引 8 个:idx_qa_q_user / idx_qa_q_status / idx_qa_q_created / idx_qa_a_question / idx_qa_a_user / idx_qa_a_accepted / idx_qa_v_target / idx_qa_qt_tag
  • status:1 正常(前台仅展示 status = 1 的问题;后台可查看全部)
  • 级联删除:删问题会一并清理标签关联 / 该问题的投票 / 其下回答的投票 / 回答 / 问题本身;删标签会清理标签关联
  • 建表:activate() + init_after 双保险;卸载:uninstall() 按依赖顺序删五张表

四、路由

前台

方法路径说明
GET/qa问题列表(`?tab=newest\
GET/qa/ask提问页(需登录)
POST/qa/ask提交问题(需登录 + CSRF + 限流)
GET/qa/question/{id}问题详情(游客可看)
GET / POST/qa/edit/{id}编辑问题(需登录,仅作者本人)
POST/qa/question/{id}/answer提交回答(需登录 + CSRF + 限流)
POST/qa/vote投票(需登录 + CSRF + 限流)
POST/qa/accept/{id}采纳回答(需登录 + CSRF,仅问题作者)
GET/qa/tags标签列表
GET/qa/tag/{name}某标签下的问题列表

后台

方法路径说明
GET/admin/qa问题管理列表
GET/admin/qa/edit/{id}问题编辑页
POST/admin/qa/edit/{id}保存修改(CSRF)
POST/admin/qa/delete/{id}删除问题(CSRF,级联)
GET/admin/qa/tags标签管理页
POST/admin/qa/tags/add /edit/{id} /delete/{id}标签增改删(CSRF)

⚠️ 后台路由需声明 route:admin;写全局设置需 system:settings。二者均已声明。

五、核心规则

限流(RateLimiter::hit)

动作阈值
提问 qa_save每 小时 最多 5 次
回答 qa_answer每 小时 最多 20 次
投票 qa_vote每 分钟 最多 30 次(带 IP 维度)

投票口径(Plugin::toggleVote())

  • 入参 vote ∈ {1, -1, 0};0 = 取消
  • 重复点击同方向 = 取消(等价 vote = 0)
  • 不能给自己投票(item.user_id === userId → 拒绝)
  • vote_count 由 SUM(qa_votes.vote) 重算后回写,不做增量累加(避免漂移)
  • 仅当「最终 vote = 1 且原状态 ≠ 1」时,给内容作者 Points::award(…, 1, …, 'qa_q'|'qa_a')

采纳口径(Plugin::acceptAnswer())

  • 仅问题作者可采纳(否则 err_permission)
  • 采纳前先把该问题下所有回答的 is_accepted 清零,再置新的为 1,同步 qa_questions.accepted_answer_id
  • 发放积分:qa_accept_points(默认 5);自问自答不发放
  • 回答列表排序:is_accepted DESC, vote_count DESC, created_at ASC(采纳置顶)

配置项(核心 settings 表)

键默认说明
qa_accept_points5采纳回答给回答者的积分
qa_per_page15前台每页问题数

两键在 activate() 时写入默认值(已存在则不覆盖)。

计数联动

  • 提问 → Settings::runtimeIncr('_qa_questions')
  • 回答 → Settings::runtimeIncr('_qa_answers'),并重算 qa_questions.answer_count
  • 标签计数 → 每次改标签后,对「旧标签 ∪ 新标签」逐个重算 question_count

六、注意事项

  • ⚠️ 正文是编辑器 base64 原文,落库前不转义;显示时统一 Content::decode() 解码 + 净化。

改动任何输出路径时务必确认净化层未被绕过

  • ⚠️ 游客可浏览问题列表与详情,但提问 / 回答 / 投票 / 采纳均需登录
  • ⚠️ layout_head_end.php 是空实现(直接 return):本插件所有页面都在各自视图里

通过 section('css') 加载样式,不需要全局注入,不要误删这个文件(plugin.json 里仍注册着)

  • ⚠️ **nav_main_links.php / nav_plugin_links.php / right_sidebar_after.php 用

$_SERVER['REQUEST_URI'] 判前缀做高亮**,而核心只有 left_sidebar.php:100 调用

nav_plugin_links 时注入 __nav_uri,main.php:160/218 调用 nav_main_links /

nav_plugin_links 时不传任何参数。因此本插件采用 REQUEST_URI 是当前唯一可行写法,

在站点根目录部署下行为正确;若日后改为二级目录部署(BASE_PATH 非空),

前缀判断会因路径带前缀而失效——届时需核心侧统一为这几个钩子注入 __nav_uri,而非只改插件

  • 当前状态:qa_questions 1 行 / qa_answers 2 行 / qa_votes 2 行 / qa_tags 2 行 /

qa_question_tags 2 行;插件未启用(activated: false)

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 3 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录