本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/daily_checkin/说明.md。
每日签到(daily_checkin)插件说明
版本:1.3.0 | 作者:FlintHub | 状态:✅ 启用(
plugin.jsonactivated: true)功能:用户每日签到获取积分,支持连续签到奖励(连签天数加成);后台可配置开关与奖励参数
一、功能简介
- 用户每日可签到一次,签到获得基础积分(
checkin_points_base,默认 2) - 连续签到奖励:连续签到天数加成(
checkin_points_bonus,默认 1/天) - 数据表
UNIQUE(user_id, checkin_date)天然去重,一天只能签一次 - 签到总开关(
checkin_enabled,默认开):关闭后右栏不出面板、签到按钮置灰、直 POST 被拒 - 两个前台入口(2026-10-03 起:已按要求移除「顶部导航」入口):
- 右栏「本周签到」面板(
right_sidebar_after钩子,用户栏下方、与用户栏同级) - 前台导航「应用」下拉(
nav_plugin_links)
- 一个后台入口:
/admin/daily-checkin设置页(开关 + 奖励参数 + 只读统计) - 签到页
/checkin:积分概览 + 奖励规则 + 本月日历(可翻月)
二、目录结构
plugins/daily_checkin/
├── plugin.json # 插件清单(version 1.3.0 / author FlintHub / admin_url / permissions)
├── Plugin.php # 生命周期(activate 建表 + 默认设置)+ 设置读写(getSettings/saveSettings)+ class_alias
├── CheckinController.php # 前台签到控制器(index / buildAreaData / doCheckin)
├── AdminController.php # ★ 后台控制器(index / stats / settingsSave),继承 Admin\BaseController
├── hook/
│ ├── init_after.php # 懒建表 + checkin_date 字段类型迁移
│ ├── route_register.php # 前台路由注册(/checkin)
│ ├── admin_route_register.php # ★ 后台路由注册(/daily-checkin、/daily-checkin/settings/save)
│ ├── right_sidebar_after.php # ★ 右栏「本周签到」面板(自取数据,渲染 _sidebar.php)
│ ├── nav_plugin_links.php # 前台「应用」下拉入口
│ └── nav_user_menu_items.php # 用户下拉菜单入口(当前为空实现,作者禁用)
├── assets/
│ ├── style.css # 前台样式(dc-modern-* 签到页 / dc-week-* 右栏面板)
│ └── admin.css # ★ 后台设置页样式(dc-admin-*)
├── lang/
│ ├── zh.php / en.php / zh_tw.php # 三语字典(各 42 键,键集与键序一致)
└── views/
├── index.php # 签到页面外壳
├── _area.php # 签到页主体(积分概览 + 奖励规则 + 本月日历)
├── _sidebar.php # ★ 右栏「本周签到」面板(钩子 + htmx 回包共用)
└── admin.php # ★ 后台设置页三、数据表
| 表 | 用途 |
|---|---|
daily_checkin | 签到记录(user_id / checkin_date / points / consecutive_days / created_at,UNIQUE(user_id, checkin_date)) |
- 插件独立库:
plugins/daily_checkin/data/daily_checkin.sqlite(Plugin::db()→\app\Helpers\Plugin::db('daily_checkin')) activate()建表 + 写入默认奖励配置(settings 表,见下)uninstall()删除表- 卸载后
users.points字段保留(不删除,防误删其他积分逻辑)
设置项(存核心 settings 表,group = daily_checkin)
| 键 | 默认 | 说明 |
|---|---|---|
checkin_enabled | 1 | 是否开放签到(1 开 / 0 关) |
checkin_points_base | 2 | 每日签到基础积分 |
checkin_points_bonus | 1 | 连续签到每天的额外加成 |
⚠️ 写库必须走
Settings::update($key,$value,'daily_checkin'),不要用Model\Setting::setValue()。
Settings::get()会优先命中protected/settings_cache.php缓存文件,而Settings::update()内部才会
buildCache()重建缓存 → 只写库不清缓存 = 「后台显示保存成功、前台读到的仍是旧值」,零日志。
四、Hooks
| Hook | 用途 |
|---|---|
init_after | 懒建表 + 迁移 |
route_register | 前台路由(/checkin) |
admin_route_register | 后台路由(/daily-checkin、/daily-checkin/settings/save) |
right_sidebar_after | 右栏「本周签到」面板(用户栏下方、与用户栏同级) |
nav_plugin_links | 「应用」下拉签到入口 |
nav_user_menu_items | 用户菜单入口(当前为空实现,作者禁用) |
⚠️
nav_main_links(顶部导航入口)已于 2026-10-03 按要求移除:钩子注册从
plugin.json的hooks中删除,hook/nav_main_links.php文件同步删除。签到入口保留在右栏面板与「应用」下拉两处(PC 左栏「应用」面板 + 顶栏「应用」下拉共用同一钩子)。
⚠️ 若要恢复顶部导航入口:重新加回
plugin.json的nav_main_links键 + 重建该钩子文件即可。改
plugin.json的hooks后必须刷新plugins/plugins_cache.json(删缓存后触发一次前台请求即可重建)。⚠️ 验证时注意:无 Cookie 的纯游客 GET 会命中
pages/页面缓存直接退出、完全跳过init.php→ 插件缓存永不重建。要触发重建必须带 Cookie。
⚠️ 已移除
controller_view_before:它原先把checkin_today/checkin_today_points/
checkin_consecutive/checkin_points注入视图,而唯一的消费方是核心右栏里那段硬编码的签到代码(
right_sidebar.php的include('plugins/daily_checkin/_sidebar')+#checkinHint)。2026-09-27 核心完成解耦、那段代码被删除后,该注入再无消费方,故连同钩子一并移除。
现在面板由
right_sidebar_after自己查库(每次渲染 1 次查询;右栏被隐藏时零查询)。
五、后台设置页(/admin/daily-checkin)
- 控制器
AdminController继承app\Controllers\Admin\BaseController—— 其构造函数已做
Auth::isAdmin() 门禁(AND 口径:role === 'admin' 且 group_id === 4)与管理员二次验证。
⚠️ 不要改成继承 app\Core\Controller —— 会绕过全部后台门禁(越权高危,本仓已复发 2 次)。
- 页面内容:
- 只读统计三格:今日签到人数 / 本周参与人数 / 累计签到人次(
stats()三条 COUNT,异常吞掉返回 0,不阻断设置页) - 设置表单:签到开关(checkbox)+ 基础积分 + 连签加成(number,
0~1000防手滑),提交后 302 回本页
- 表单两个隐性契约:
- 哨兵字段
settings_submitted:checkbox 未勾选时浏览器根本不提交该字段,靠哨兵区分
「提交过但没勾选」与「压根没提交表单」两种情况。
- 逐字段
array_key_exists判定,⚠️ 禁止写(int)($_POST[$k] ?? 0):字段缺失会被静默当成
「设为 0」写库(症状:302 显示保存成功,但 DB 与缓存都不变、零日志)。
- 视图红线:全部 markup 必须留在
<main class="admin-content">内 —— 核心后台表单/按钮样式
全是 .admin-content 作用域(admin.css:237-282),放到 </main> 之后 = 「弹出页没有 UI」,零日志。
- CSS 版本号:
views/admin.php的section('css')里用__DIR__ . '/../assets/admin.css'+
substr(md5_file($css),0,8) 自动破缓存。⚠️ 视图目录是 views/,样式在同级 ../assets/;
写成 ../../assets/ 恒不命中 → 版本号永远退化成 v=1,改了样式不破缓存。
- 写操作审计:保存成功记
AuditLog::log('daily_checkin_settings_save', 'dc_settings', 0, 'keys=...')。 - 参数白名单:
Plugin::saveSettings()只接受SETTING_DEFAULTS里有的键,白名单外的键一律忽略
(防构造 POST 覆写任意核心 settings 键)。
六、右栏「本周签到」面板(right_sidebar_after)
- 位置:
app/Views/_components/right_sidebar.php:210—— 用户面板之后、站点统计之前 - 形态:独立
.mn-panel,与排行榜 / 最新会员 / 站点统计一致 - 内容:标题(带跳转
/checkin的右箭头)+ 本周 7 格(周一 → 周日)+ 状态区 + 底部提示 - 已签到的日子:主色实心圆 + 勾
- 今日未签到:
立即签到按钮(htmxhx-post="/checkin",hx-target="#checkinWeek"原地替换) - 今日已签到:绿胶囊「今日已签到」
- 底部灰字:未签到显示副标题;已签到显示「明天见」
- 只对登录用户显示;游客直接
return,不输出任何内容 - 签到开关关闭时不出面板:钩子里读
\app\Helpers\Settings::get('checkin_enabled','1')判断,
非 '1' 即 return。⚠️ 不要在钩子里调 Plugin::isEnabled() —— 渲染钩子在静态闭包里执行,
加载顺序不保证插件主类已就位;直接读 Settings 更稳。
- 数据:
SELECT checkin_date, points, consecutive_days ... WHERE checkin_date BETWEEN 本周一 AND 本周日
(1 次查询同时得出 7 格状态 + 今日积分/连续天数)
- 图标:一律
<i class="fa mn-fs-12">,不用$view->icon()(后者带内联style,违反插件零内联) - 渲染钩子红线:闭包作用域内
$this不可用 → 钩子里用htmlspecialchars()/Csrf::token()
⚠️ 已知差异:两处日历的「周起始日」不同
| 位置 | 起始日 | 表头顺序 |
|---|---|---|
右栏「本周签到」面板(_sidebar.php) | 周一 | 一 二 三 四 五 六 日 |
签到页「本月日历」(_area.php) | 周日 | 日 一 二 三 四 五 六 |
右栏面板对齐参考设计(M T W T F S S);_area.php 的月份日历未改动。
两处刻意保留各自口径,不是 bug,改动前请先确认。
⚠️ 模板取数红线
_sidebar.php 有两个入口,取数方式必须统一为普通变量($week_days / $checkin_today):
| 入口 | 路径 | `$this->getData()` | 普通变量 |
|---|---|---|---|
| 右栏钩子 | $GLOBALS['__view']->include($tpl, $extra) | ❌ 取不到 | ✅ |
| htmx 回包 | viewRaw($tpl, $data) → display() 会 extract | ✅ | ✅ |
原因:TemplateCompiler::include($tpl, $extra) 是 array_merge($this->data, $extra) 后 extract,
不会把 $extra 写回 $this->data → 钩子路径下 getData() 拿到的是默认值。
所以模板里禁止 $this->getData()。($this->e() / $this->url() / $this->t() 两条路径都可用。)
七、路由
前台
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /checkin | 签到页面(积分概览 + 奖励规则 + 本月日历,支持 ?month=YYYY-MM 翻月) |
| POST | /checkin | 执行签到。htmx 请求(HX-Request)返回局部 HTML;普通表单提交 302 回 /checkin |
后台(admin_route_register)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /admin/daily-checkin | 后台设置页(统计 + 表单) |
| POST | /admin/daily-checkin/settings/save | 保存设置(CSRF + 白名单 + 302 PRG) |
⚠️
hook/admin_route_register.php里注册的路径不写/admin前缀(核心已在 admin 分组内注册,再写会 404 —— 规范 §11.22)。
plugin.json的admin_url=/admin/daily-checkin(含前缀),两边拼起来才是最终 URL。
八、核心逻辑(doCheckin)
- 签到开关检查:
Plugin::isEnabled()为假 → 写入$_SESSION['checkin_msg']=msg_disabled,
htmx 请求返回局部 _area 片段(exit),普通表单 302 回 /checkin
- 校验登录 + CSRF
- 查今日是否已签(
UNIQUE(user_id, checkin_date)+ 前置查询)→ 已签返回提示 - 计算连续天数:昨日有签 → 连续 +1;否则重置为 1
- 写入签到记录 →
Points::award发放积分(基础 + 连签加成) - htmx:
viewRaw()重渲染(widget=sidebar→_sidebar.php,否则_area.php);普通表单:302 回/checkin
buildAreaData()里$data['checkin_enabled'] = Plugin::isEnabled();必须在try之外赋值(异常分支也要带上该字段,否则
_area.php取到默认值true,关站时按钮仍可点)。
九、注意事项
- 签到记录按
checkin_date TEXT(Y-m-d)去重,跨时区以服务器日期为准 - 连续天数判断依赖昨日记录,跨天未签则断签重置
- 入口
nav_user_menu_items文件当前为空实现(作者禁用),实际入口走顶部导航 + 「应用」下拉 + 右栏面板 - ⚠️ 目录名含下划线(
daily_checkin),核心用目录名硬拼类名\Plugin\{目录名}\Plugin
(app/Helpers/Plugin.php:699/750/846),而本插件实际命名空间是 Plugin\DailyCheckin
→ class_exists() 恒为 false,activate() / deactivate() / uninstall() 会被静默跳过。
✅ 已修(v1.1.0):Plugin.php 末尾加了
\class_alias(Plugin::class, 'Plugin\\daily_checkin\\Plugin', false);
→ class_exists('\Plugin\daily_checkin\Plugin') 实测为 true,卸载可正常删表。
该行不可删除:删掉后生命周期再次失效,且只在卸载时才暴露,极难排查。
- ⚠️ 改插件静态资源(css/js)后必须 bump 版本号 —— 后台
admin.css走md5_file自动破缓存;
前台 style.css 若为硬编码 ?v=,需手工同步。