本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/suiyu/说明.md。
碎语时光(suiyu)· 插件说明
短文字 + 图片动态流。全部自建,零插件依赖(不读不写任何其他插件的数据表,也不调用其他插件的类方法)。
一、它能干什么
| 功能 | 说明 |
|---|---|
| 发布 | 纯文字 / 文字 + 图片(最多 9 张,可在后台调);表情 shortcode( 等)自动还原 |
| 流 | 三个 tab:最新 / 热门(按点赞数,不加权)/ 关注 |
| 我的 | /suiyu/mine |
| 个人页 | /suiyu/u/{id}(含关注按钮、关注数/粉丝数) |
| 互动 | 点赞(可取消)、评论(含楼中楼)、转发(可附一句,repost_of 指向最初原创,禁止套娃) |
| 关注 | 自建 sy_follows,不碰 user_profile 的 user_follows |
| 搜索 | 自建 LIKE 搜索(%/_ 已转义 + ESCAPE '\'),支持「只搜我的」;不接入核心全站搜索 |
| 审核 | 敏感词库(拦截 / 替换 两种动作) |
| 举报 | 同一人同一帖只报一次(UNIQUE(post_id,user_id) + INSERT OR IGNORE);后台可「删碎语」或「忽略」 |
| 通知 | 点赞 / 评论 / 关注 → 走核心 Notification(type = suiyu_like / suiyu_comment / suiyu_follow) |
| 后台 | 碎语列表 / 回收站 / 敏感词库 / 举报处理 / 设置 |
v1.0.0 明确不做:@提及(@用户名)、碎语进全站搜索、热门加权排序、核心个人主页上的关注按钮。
二、入口
| 端 | 位置 | 实现 |
|---|---|---|
| PC | 左侧栏「应用」面板 | hook/nav_plugin_links.php |
| 移动端 | 顶栏「应用」下拉 | 同一个钩子、同一份输出(双触发) |
| 全站右栏 | 「碎语广场」面板(最近 5 条) | hook/right_sidebar_after.php |
移动端不是第三个触发点:
.mn-mobile-icon-apps与.mn-apps-dropdown是同一份 DOM,靠
@media (min-width:769px){ .mn-nav-right .mn-mobile-icon-apps{display:none} }切换显隐。⚠️ 右栏在
@media (max-width:1024px)起整块display:none→ 面板只是辅助入口,真正的导航入口是「应用」。
三、目录结构
plugins/suiyu/
├── plugin.json # name=suiyu · admin_url=/admin/suiyu
│ # permissions=["route:admin","system:settings"] ← ★ 缺一不可
├── Plugin.php # 建表 + 全部业务静态方法(前台/后台/探针共用)
├── FrontController.php # 前台 13 条路由
├── AdminController.php # 后台 13 条路由(extends app\Controllers\Admin\BaseController)
├── hook/
│ ├── init_after.php # 惰性建表兜底(门控 = md5(ddl()) **内容哈希**,不是「文件是否存在」)
│ ├── route_register.php # 前台路由(**写全路径**)
│ ├── admin_route_register.php # 后台路由(**不写 /admin**)
│ ├── layout_head_end.php # 前台注入 style.css(全站)+ script.js(仅本插件页)
│ ├── nav_plugin_links.php # ★ 唯一导航入口(双触发 · 禁顶层符号)
│ └── right_sidebar_after.php # 右栏「碎语广场」面板
├── views/
│ ├── stream.php # 流页(全站 / 我的 / 个人页 共用)
│ ├── _list.php # htmx 翻页片段(⚠️ 不带 /四、数据表(6 张,全部在 plugins/suiyu/data/suiyu.sqlite)
| 表 | 用途 | 关键约束 |
|---|---|---|
sy_posts | 碎语 | status:0 正常 / 1 回收站;repost_of 指向最初原创 |
sy_comments | 评论 | reply_to 跨帖会被归零(防挂载到别的帖子下) |
sy_likes | 点赞明细 | UNIQUE(post_id, user_id) |
sy_follows | 关注 | UNIQUE(follower_id, followee_id) |
sy_blocked_words | 敏感词 | word UNIQUE;action:block / replace |
sy_reports | 举报 | UNIQUE(post_id, user_id) → 同一人同一帖只能报一次 |
计数一致性(红线)
like_count / comment_count / repost_count 与明细行永远在同一个事务里改;
取消点赞时先看 rowCount() > 0 才 -1,并统一用 MAX(x-1, 0) 兜底防负。
图片删除策略
| 操作 | 图片文件 |
|---|---|
| 软删(前台删除 / 后台「删除」) | 保留(可恢复) |
| 彻底删除(后台「彻底删除」) | 一并删除(Upload::delete()) |
五、路由
前台(route_register,写全路径)
| 方法 | 路径 | 响应 |
|---|---|---|
| GET | /suiyu | 页面(`?tab=new\ |
| GET | /suiyu/mine | 页面 |
| GET | /suiyu/u/{id} | 页面 |
| GET | /suiyu/post/{id} | 页面 |
| GET | /suiyu/search | 页面(?q=&mine=1) |
| GET | /suiyu/list | htmx 片段(viewRaw) |
| POST | /suiyu/post | PRG |
| POST | /suiyu/post/{id}/delete | PRG |
| POST | /suiyu/repost | PRG |
| POST | /suiyu/like | XHR → JSON |
| POST | /suiyu/comment | XHR → JSON(回包含渲染好的评论 HTML) |
| POST | /suiyu/follow | XHR → JSON |
| POST | /suiyu/report | XHR → JSON |
后台(admin_route_register,不写 /admin)
/admin/suiyu · /admin/suiyu/trash · /admin/suiyu/words · /admin/suiyu/reports · /admin/suiyu/settings
(另有 delete/{id}、restore/{id}、purge/{id}、words/{add,edit,delete}、reports/{id}/handle)
六、后台设置项
| 设置键 | 默认 | 范围 | 说明 |
|---|---|---|---|
suiyu_per_page | 15 | 1–50 | 前台列表每页条数 |
suiyu_max_len | 300 | 10–2000 | 单条碎语 / 评论最大字数 |
suiyu_max_img | 9 | 0–9 | 单条最多图片数(0 = 关闭图片) |
suiyu_like_points | 0 | 0–100 | 每被点赞一次作者获得的积分(0 = 不奖励) |
suiyu_report_enabled | 1 | 0/1 | 是否显示举报入口 |
⚠️ 保存走
Settings::update(),需要plugin.json声明system:settings权限。缺声明时表现为「提示保存成功、回读 DB 值没变」(只写
error.log后静默 return)。⚠️ 首次启用后请到后台「维护 → 清理缓存」清一次设置缓存 ——
settings_cache.php不会自动补新键,旧站点读不到这 5 个新键(后台不显示、
config.php读不到)。
七、几个刻意的设计取舍
nav_plugin_links用字面量图标<i class="fa mn-fs-16"></i>,不用$view->icon()
—— 核心 TemplateCompiler::icon() 的返回值恒带 style="font-size:Npx" 内联样式,
违反插件零内联红线(plugin_hygiene.py 的口径是「任意 style 属性都算」)。
- 右栏面板「无数据不渲染」:一条碎语都没有时直接
return,不输出空占位卡。
想改成「空态也显示引导」,把 hook/right_sidebar_after.php 里的
if (empty($syItems)) { return; } 去掉即可(一行)。
- 后台表格列序刻意避开核心的按列序硬编码
(assets/css/admin.css:180 给 td:nth-child(4) 限宽省略号、:182-189 给 1/3/5/6/7 列加 nowrap)。
本插件列表统一用 ID | 内容 | 作者 | 互动 | 状态 | 时间 | 操作 —— 长文本落在第 2 列(不受限),
短文本落在受限列 → 零特异性战争、零 !important。
- 缩略图两档:
thumb_(400×300,列表九宫格)+md_(800×600,详情页)。
⚠️ 一律走 Upload::thumbUrl($rel, $prefix),禁止手拼 'thumb_' . $rel
—— makeThumbnail() 把前缀加在 basename 上,而 makeRelPath() 返回 ns/YYYY-MM/<hex>.ext
(namespace 是前导目录),手拼必 404。
- 图片灯箱靠
data-fullurl:assets/js/lightbox.js:45只收集[data-fullurl], .post-text-content img。
碎语图不在 .post-text-content 里 → 不写 data-fullurl 就点图毫无反应(完全静默)。
- 评论走 XHR 而非整页刷新:
/suiyu/comment返回 JSON,其中html字段是服务端渲染好的评论片段
(复用 views/_comment.php)→ 前端就地插入,楼层/回复关系与刷新后一致。
- 加载更多用 htmx(
hx-get="/suiyu/list"+hx-swap="outerHTML"),
因此 views/_list.php 绝不能出现 <link> / <script>(否则每点一次 DOM 就多一个节点,视觉无感但会累积)。
八、卸载
后台「插件」页点卸载 → 调 Plugin::uninstall(),DROP 掉本插件独立库里的 6 张表,
不触碰核心库、不删除其他插件数据。上传的图片文件不会被自动删除(如需清理请手动处理 assets/uploads/)。
