Files
subtrack/README.md
T
chun_qiu cde1c84c9a feat(v1.11.5-L): lite 精简版——移除大屏/客户自查/多用户/小键盘,套餐仅保留按日
需求与确认(用户逐项确认):
- 老数据「按次 / 通行证 / 深渊」统一迁移成按日,代码里彻底删除三种模式
- 打手归属一并移除(只剩一个人,颜色与指派没有区分意义)
- 盲水印做成完全不可见:只存在于 DOM / 源码,肉眼与截图都看不到
- 拼写采用更正后的 Powered by Baka春秋
- 版本号与品牌全站统一带 -L
- screen_public / portal_public 保留解析但不起作用,保证旧 ini 能直接启动
- README 同步更新

品牌与版本(全站统一):
- const version = "1.11.5-L":<title>、启动横幅、日志前缀 [代打台-L]、
  设置页版本号、页脚版本号全部带 -L
- 左上角导航品牌 代打台 → 代打台-L(登录页、初始化页同步)
- 页脚新增可见署名 Powered by Baka春秋

盲水印:
- _layout.html 在 <body> 起始处注入 .wm-blind 容器(含注释与 data-wm 双标记),
  覆盖包括登录/初始化在内的所有页面
- app.css 用零尺寸 + 透明 + opacity:0 + clip-path:inset(50%) 四重隐藏,
  同时留同名注释;不占位、不上色、不进无障碍树、不影响打印

功能移除:
- 大屏:删除 handlers_screen.go、pages/screen.html、_screen_layout.html、
  static/screen.css、static/screen.js、store.Stats/ScreenStats;
  导航「大屏 ↗」、页脚「大屏」、设置页「大屏刷新/大屏」行、启动横幅大屏行一并删除
- 客户自查:删除 handlers_portal.go、pages/portal.html 与 .portal-*/.pcal-* 样式,
  设置页「客户自助」行删除
- 多管理员:删除 handleUserCreate/Toggle/Color/UserEditPage/UserEditSave、
  pages/user_form.html、store 侧 ActiveUsers/SetUserColor/SetUserActive/
  UpdateUserByAdmin/NormalizeColor/AssigneePalette;设置页「管理员」整块移除,
  只保留首次启动创建的那一个账户与「修改我的密码」
- 打手归属:删除 customers.assignee_id 相关读写与 UI(客户详情指派下拉、
  客户表单「归属打手」、日历格左侧打手色竖条、任务卡打手标签、套餐详情客户列)
- 登录页与初始化页的说明文案同步改为单账户口径(「只有一个管理员账户」
  「创建后不能再添加其他管理员」),字数与 main 对齐,卡片不换行、高度不变
- 触摸小键盘:删除 3×4 键盘、⌨/触控开关、localStorage 记忆、.keypad* 样式
- 套餐计费方式:只保留按日。删除按次/通行证/深渊的 Package 与 Subscription 字段、
  domain 里的 UnitCount/StateUsedUp/CycleIndex、package_form 计费方式单选与
  通行证/深渊/有效期字段、日历整段色块(has-block/cell-block/season-band)、
  追加次数分支、SeasonComplete/AddCount、CountActiveSubs 的次数条件

数据库迁移(幂等,Migrate 内自动执行):
- normalizeLegacyData:套餐一律 unit='day' 并清 season/abyss/cycle_days/
  default_validity_days/season_*;订阅清 season/abyss/cycle_days/season_done
- 通行证、深渊当年就是 unit='day' 且起止日完整,仅清标记,服务区间原样保留
- 按次按「1 次 = 1 天」折算:结束日 = 开始日 + 总次数 − 1;仍缺结束日退回开始日
- 只动数据,不删表不删列:老库的 season/abyss/cycle_days/total_count/used_count
  保留但不再使用,升级可逆、不会因为 DDL 失败卡住
- 新库 schema 干净:不再创建上述列,users.color / customers.assignee_id 也不创建
- 老库 ensureColumn 保留 calendar_separate / tag / archived 三项(真增量列)

兼容:
- config 仍解析 screen_public / portal_public(结构体字段注明已废弃),
  默认 ini 模板里改为「已废弃,写了不报错、不生效」

测试:
- go vet ./... 通过;go test ./... 全部通过
  (新增 TestNormalizeLegacyTypeData 验证三模式折算成按日;
   TestSingleUserAccount / TestSessionLifecycle 覆盖单用户与会话;
   删除按次、深渊、打手颜色、多用户等已不存在的能力的用例)
- 新增 scripts/smoke-lite.ps1:起隔离实例,19 个页面级请求全部 200,
  已移除的 /screen、/customer、/api/screen/day 均 404,
  水印/品牌/v1.11.5-L/无残留词 12 项断言通过,
  真实数据下日历出现待打任务、「剩 30 天」、今天到期徽标 4 项断言通过,
  本次 0 失败

README:改为 lite 版说明,删除大屏/客户自查/通行证/深渊/按次/小键盘/多用户章节,
新增「盲水印」与「与 main 分支的差异」两节(含老数据折算规则与列保留说明)。
2026-10-21 03:33:27 +08:00

19 KiB
Raw Blame History

代打台-L · 代打管理平台(lite 版)

一个给代打工作室用的单文件管理程序:套餐 → 订阅 → 追加时长 → 日历打卡。 整个程序就是一个 daili.exe + 一个 daili.ini,数据存在同目录的 daili.db 里,局域网内手机、电视都能打开。界面支持亮色 / 暗色切换,默认跟随系统。

这是 lite 分支:只保留「按日(包时)」一种套餐,去掉了大屏、客户自助查询、多管理员、触摸小键盘。 版本号统一带 -L 后缀(当前 1.11.5-L),界面品牌为「代打台-L」,页脚署名 Powered by Baka春秋。 与 main 分支的差异见文末「10. 与 main 分支的差异」。


1. 快速开始

  1. 把 daili.exe 放进任意文件夹(建议路径不含空格,例如 D:\代打台\);
  2. 双击 daili.exe:
    • 首次运行会自动生成配置文件 daili.ini;
    • 弹出一个黑色窗口,打印访问地址(默认 17777 端口);
    • 默认会自动打开浏览器;
  3. 浏览器打开 http://127.0.0.1:17777 → 第一次会让你创建管理员账号(这是唯一的账号,之后不能再添加);
  4. 之后按「套餐 → 客户 → 订阅」的顺序录入即可。
  ┌───────────────────────────────────────────────┐
  │        代 打 台 - L · 代打管理平台            │
  └───────────────────────────────────────────────┘
   版本 1.11.5-L   配置文件 daili.ini
   数据库 D:\代打台\daili.db
   访问地址:
     本机   http://127.0.0.1:17777
     局域网 http://192.168.1.23:17777
   按 Ctrl+C 停止服务

关掉黑窗口 = 停止服务。数据已经落盘,下次打开继续用。


2. 配置文件 daili.ini

配置文件固定放在 exe 同级目录,首次运行自动生成(带中文注释),改完重启生效。 写法:key = value,# 或 ; 开头的整行是注释,不支持分节;相对路径相对于 exe 所在目录。

配置项 默认值 说明
listen_ip 0.0.0.0 监听 IP。0.0.0.0 = 所有网卡(局域网可访问);127.0.0.1 = 只允许本机
listen_port 17777 监听端口(1–65535)
database_path daili.db SQLite 数据库文件(单文件,含全部数据)
backup_enabled true 是否每日自动备份
backup_dir backup 备份目录(留空则用 exe 同级 backup/)
backup_keep 14 备份保留份数(1–365),超出自动清理
timezone 空 留空跟随系统;可固定 Asia/Shanghai。影响所有“自然日”计算
open_browser true 启动后自动打开浏览器
log_enabled true 运行日志写文件(控制台始终输出)
log_dir logs 日志目录,按天切分 daili-YYYY-MM-DD.log

screen_public(大屏)与 portal_public(客户自助)在 lite 版已废弃:仍然接受解析,但不起任何作用。 保留它们只是为了让你从 main 升级时,旧 daili.ini 能直接启动,不会被「未知配置项」拦住。

行为约定

  • 文件不存在 → 自动生成默认配置,照常启动;
  • 文件写错 → 报错退出并指出行号,原文件不会被修改。例如:
[代打台-L] 配置文件有问题,已停止启动:

第 3 行: listen_port 必须在 1-65535 之间,当前是 99999
修好后重新启动即可(原文件未被修改)
  • 未知配置项、重复配置项、布尔值写成 maybe、时区写错,都会带行号报错;
  • 端口被占用不会自动换端口,而是明确报错并提示改 listen_port。

命令行参数(临时覆盖配置,优先级更高)

参数 说明
-config <路径> 指定别的配置文件(默认 exe 同级 daili.ini)
-addr <ip:端口> 临时覆盖监听地址,如 -addr 127.0.0.1:19000
-db <路径> 临时覆盖数据库路径
-backup-keep <n> 临时覆盖备份保留份数
-no-backup 本次运行关闭自动备份
-no-open 本次运行不自动打开浏览器

示例:daili.exe -addr 127.0.0.1:19000 -no-open

业务参数(到期预警天数)不在配置文件里,仍在网页「设置」中修改。


3. 第一次使用(5 分钟走完)

  1. 建套餐:套餐 → 新增套餐
    • 「包月日常」默认 30 天,色签绿;
    • 只有「按日」一种计费方式:有开始日与结束日,每天一条待打任务。

    以后要加套餐 D/E/F,直接再加一行就行,不会影响任何已有客户数据。

  2. 建客户:客户 → 新建客户,填手机尾号 + 客户名(这两项组合唯一)。
  3. 开通订阅:进客户详情 → 选套餐、开始日、天数 → 开通订阅。
  4. 日常使用:打开「日历」,今天那格里就是这个客户今天要打的单,点「完成」打卡。
  5. 续费:客户转账后,点订阅上的「追加时长」→ 在日历上点新的终止日期 → 确认。 面板会实时显示「计算起点 / 原结束日 / 新结束日 / 本次追加几天 / 追加后剩余几天」,并在客户页留下追加记录。

想先看效果再录自己的数据?程序启动后运行 powershell -ExecutionPolicy Bypass -File scripts\seed.ps1,会写入演示客户与套餐(默认账号 admin / daili123)。


4. 业务规则(和 PRD 一致)

剩余时长

  • 剩余天数 = 结束日 − 今天 + 1,结束日当天仍算服务日;
  • 没有次数、没有周期:一个订阅就是从开始日到结束日的一段连续服务期。

追加时长(核心)

  • 打开追加面板,在月历上点选新的终止日期,系统自动反算;
  • 未到期的订阅:从原结束日的次日起算,追加天数 = 新结束日 − 原结束日;
  • 已到期的订阅:从今天起算(不会把中间空档的天数白送给客户);
  • 追加记录保留:计算起点、旧结束日、新结束日、天数、备注、操作人、时间。

暂停 / 恢复

  • 暂停期间冻结剩余时长,日历上不出现待打任务;
  • 恢复时结束日自动顺延暂停的自然日天数,时间轴上是灰色斜纹段。

打卡

  • 每天一条「完成 / 跳过」,只做记录不扣天数(天数按自然日流逝);
  • 撤销打卡会删掉这条记录,不影响结束日。

预警

  • 剩余 ≤3 天橙色、今天到期红色、已过期灰色;顶栏常驻「今日到期 / 3 天内 / 7 天内」三个数字。

查询

  • 顶栏搜索框输入手机尾号即可定位客户(也支持姓名、游戏名、联系方式),按 / 快速聚焦。

4.1 工作台速用

  • 按尾号快速完成:日历右侧「当日待完成」顶部输入手机尾号 → 回车。
    • 只匹配到 1 项:直接完成并提示;
    • 同一尾号有多个客户,或一个客户今天有多个服务:弹出勾选面板,默认全选,可多选后一次完成。
  • 加急:每条任务右侧点「加急」,会加红色 加急 标签并排到最前;日历格子出现红点。再点一次取消。
  • 按套餐分日历:套餐编辑页勾选「在日历中独立显示」后,工作台顶部会出现套餐标签(全部 / 每日任务 / …),可以只看某类委托的排单。
  • 日历格子:只保留大字「已完成 / 总数」(如 1/2 = 2 单里做完 1 单)。
  • 按钮收纳:任务行只露出「完成」,跳过 / 加急 / 追加收进「⋯」菜单,点空白处自动收起。
  • 停用客户:客户详情页可「停用」(默认从列表与日历隐藏,订阅保留)或「彻底删除」(连订阅/打卡/追加记录一起删,需二次确认);列表右上角可切换到「显示已停用」再恢复;
  • 7 天内到期:顶栏徽标,固定统计未来 7 天内到期的订阅;
  • 抽屉时钟:右侧当日面板顶部有时钟,24 小时制精确到秒,下面一行是当天日期;
  • 已完成折叠:当日清单里已完成/已跳过的单子自动折叠成一行(尾号 + 姓名 + 套餐 + 状态),点这一行展开看剩余,以及撤销/加急/追加;未完成的行不受影响;
  • 主题:顶栏右上角 ☾ / ☀ 切换亮 / 暗,偏好记在浏览器里;没选过时由页面在首屏按系统偏好自动决定。整套界面只认 <html data-bs-theme> 一个开关,不会出现「半亮半暗」。
  • 升级不缓存旧界面:CSS/JS 链接带内容指纹(app.css?v=<12 位哈希>)。资源可以长缓存(一年、immutable),但样式或脚本一改、重新编译,URL 就会变,浏览器自动取新文件,不用手动清缓存;HTML 仍然 no-store。

4.2 套餐标签与批量开通

  • 套餐标签:套餐编辑页可以填一个标签(如「包月」)。日历顶部页签会按标签合并:同标签的套餐合成一个页签;勾了「在日历中独立显示」的套餐额外再开一个自己的页签。
  • 批量开通:套餐 → 详情/批量,勾选客户(表头有全选,已订阅该套餐的客户置灰),填开始日期与天数后一次开通;重复的会自动跳过并提示。
  • 客户月历:客户详情页「当月排单」,显示该客户当月每天几单、完成几单,可翻月,点某天直接跳到日历那天。

5. 数据、备份与导出

文件 说明
daili.db 全部数据都在这一个文件里(SQLite,单文件回滚日志模式,运行中也不会产生 -wal/-shm)
backup/daili-YYYYMMDD-HHMMSS.db 每天首次启动时自动备份一份,保留份数见 backup_keep
logs/daili-YYYY-MM-DD.log 运行日志,按天切分

迁移与重置(只说一次,很重要)

  • 迁移:关闭程序 → 把 daili.db 复制到新电脑的新目录 → 放入 daili.exe 双击即可,账号、套餐、客户、订阅、追加记录、打卡、日志全部原样保留。

  • 重置:关闭程序 → 删除 daili.db → 重新打开,会回到「创建管理员」的全新状态。

  • 套餐标签是 packages.tag;客户停用是 customers.archived(0/1);加急标记存在 task_flags 表;套餐的「独立日历」是 packages.calendar_separate 列,老版本数据库打开时会自动加列,不用手工迁移。

  • 从 main 升级:lite 版启动时会自动把老库里的「按次 / 通行证 / 深渊」统一折算成按日(详见文末差异说明),老库直接打开即可,无需手工改数据。

  • daili.ini(监听/备份/日志配置)与 backup/、logs/ 都是独立于数据的,迁移时可以不拷,程序会按默认值重新生成。

  • 手动备份:设置 → 立即备份(同样遵守保留份数),备份文件本身也是可直接打开的单文件库;

  • 导出:设置 → 导出客户 / 订阅 / 日志 CSV,UTF-8 BOM,Excel 直接打开不乱码。


6. 从源码构建

环境:Windows + Go 1.25(本项目在 go1.25.6 上验证)。

.\build.ps1

build.ps1 会设置 GOPROXY=off、GOCACHE=<项目>\.gocache,用本地模块缓存离线构建,产物为 daili.exe(约 13.5 MB,内含 SQLite 引擎、时区库、模板与静态资源,不依赖任何 DLL)。

为什么有 third_party/fileutil:SQLite 驱动 modernc.org/sqlite 的测试代码引用了 modernc.org/fileutil,但编译产物并不依赖它。本机无法访问任何 Go 模块源,因此用一个空模块占位,仅用于满足模块图解析。

为什么内嵌时区库:Windows 没有 /usr/share/zoneinfo,为了 timezone = Asia/Shanghai 在任何机器上都可用,引入了 time/tzdata(约 0.5 MB)。

改了依赖之后,请在 Windows 上额外跑一次 GOOS=linux go build ./... 再提交 go.sum: 有些依赖(例如 modernc.org/libc 的 Linux 专用文件)只在 Linux 构建路径下被编译, 只在本机 Windows 构建会漏掉它们的 go.sum 条目,导致 CI 报 missing go.sum entry。

测试:

$env:GOFLAGS='-mod=mod'; $env:GOPROXY='off'; $env:GOCACHE="$PWD\.gocache"
go test ./...

页面级冒烟(起一个隔离实例,把所有页面渲染一遍,并校验水印/品牌/已移除入口):

powershell -ExecutionPolicy Bypass -File scripts\smoke-lite.ps1

目录结构:

main.go                     程序入口(读配置、日志、时区、监听、备份)
internal/config             daili.ini 的生成/解析/校验(含单测)
internal/domain             纯业务计算(日期、剩余天数、状态、追加换算)+ 单元测试
internal/store              SQLite 连接、迁移、全部读写 + 单元测试
internal/logx               控制台 + 按天切分的运行日志
internal/pw                 口令散列(PBKDF2-SHA256,标准库实现)
internal/web                路由、会话、模板渲染、处理器
internal/web/templates      页面与布局(embed 进二进制)
internal/web/static         app.css / app.js
scripts/smoke-lite.ps1      lite 版页面级冒烟(渲染 + 水印 + 已移除入口 404)
scripts/acceptance.ps1      冒烟清单执行器(main 分支遗留,部分条目已不适用)
scripts/config-test.ps1     配置文件专项测试
scripts/seed.ps1            写入演示数据
scripts/dbtool.py           测试预处理(暂停回溯、批量造数、完整性检查)
.gitea/workflows/build.yml   Gitea Actions:vet → test → 交叉编译 → 上传产物
.gitattributes               统一换行(.go/.yml 用 LF,.ps1 用 CRLF)
PRD.md                      产品需求文档(本地文档,不入库)
TEST-CHECKLIST.md           冒烟测试清单 + 执行结果 + 缺陷记录(本地,不入库)
test-evidence/              测试证据(本地,不入库)
daili.ini                   首次运行自动生成

持续集成(Gitea Actions)

仓库:https://git.code.cq.cn/chun_qiu/subtrack.git,CI 配置在 .gitea/workflows/build.yml。

  • 触发:推送到 main / master、打 v* 标签、PR、手动执行;
  • 运行环境:runs-on: ubuntu-latest;Go 版本由 actions/setup-go@v5 读取 go.mod(1.25);
  • 步骤:go vet ./... → go test ./... → 交叉编译 Windows / Linux 两个二进制 → sha256sum → 用 actions/upload-artifact@v4 上传;
  • 上传步骤带 env: ACTIONS_RESULTS_URL: https://git.code.cq.cn/;
  • 产物:subtrack-<run_number>,内含 daili-windows-amd64.exe、daili-linux-amd64、SHA256SUMS.txt,保留 14 天;
  • 模块代理默认 https://goproxy.cn,direct,如在海外或内网构建,改 workflow 顶部的 GOPROXY 即可。

仓库只入库源码、构建脚本、CI 配置和本 README;PRD、测试清单、截图与测试证据都在 .gitignore 里排除。


7. 常见问题

Windows 提示「已保护你的电脑」 点「更多信息 → 仍要运行」。这是 Windows 对未签名 exe 的默认拦截。

端口被占用 / 想换端口 改 daili.ini 里的 listen_port 后重启;临时换端口可用 daili.exe -addr :19000。

只想自己用,不让局域网访问 把 listen_ip 改成 127.0.0.1,启动横幅会提示「仅本机可访问」。

手机打不开局域网地址

  1. 确认 listen_ip 是 0.0.0.0;
  2. 确认手机和电脑在同一个 WiFi;
  3. Windows 防火墙首次会弹窗,选「允许访问」(专用网络);
  4. 用启动时打印的那个 192.168.x.x 地址,不要用 127.0.0.1。

忘记密码 lite 版只有一个管理员,没有「新增管理员」可以绕过,只能二选一:删掉 daili.db 从零开始,或用任意 SQLite 工具把 users 表的 password_hash 清空后重新初始化。请务必记牢密码。

数据会不会因为程序崩溃丢? 单文件 SQLite + 每次操作即时落盘,正常使用不会丢;每天的自动备份是双保险。仍然建议定期把 backup/ 拷到别的盘。

能放到公网吗? 不建议。程序按「局域网内可信环境」设计:会话 Cookie 有 HttpOnly / SameSite 保护,但没有 HTTPS、没有登录失败锁定。要放公网请套一层反向代理 + HTTPS + 访问控制。


8. 安全说明

  • lite 版只有一个管理员,就是首次启动时创建的那个账户(users.id = 1),不提供新增、停用或编辑他人账户的入口;
  • 改自己的密码用 设置 → 修改我的密码;
  • 口令使用 PBKDF2-HMAC-SHA256(12 万次迭代 + 随机盐)存储,不可逆;
  • 所有写操作记录操作人、时间、IP 与摘要,可在「操作日志」里查询;
  • 服务端模板全部走 html/template 自动转义;
  • 所有 POST 校验 Origin,会话 Cookie 使用 SameSite=Lax + HttpOnly;
  • 配置文件里的数据库/备份/日志路径请自行确保目录权限,程序不会覆盖已有文件(备份重名会跳过)。

9. 盲水印

界面里带一处不可见的版权水印 Powered by Baka春秋:写在 _layout.html 里由 .wm-blind 样式隐藏(零尺寸、透明、clip-path 裁掉、不进入无障碍树),同时在 app.css 里留了同名注释。它不占位、不上色、不影响布局与打印,只用于源码/DOM 层面溯源,请勿删除。

页脚另有一行可见署名 Powered by Baka春秋。


10. 与 main 分支的差异

项目 main lite(本分支)
版本号 1.11.5 1.11.5-L(<title>、启动横幅、日志前缀、页脚全站统一带 -L)
品牌 代打台 代打台-L
大屏 /screen 有,screen_public 控制免登录 已移除,路由返回 404
客户自助 /customer 有,portal_public 控制免登录 已移除,路由返回 404
套餐计费方式 按日 / 按次 / 通行证 / 深渊 只有按日
管理员 可新增多个,为主管可编辑他人 只有首次启动创建的那一个
打手归属 客户可指派打手,彩色竖条标识 已移除(只剩一个人,颜色无意义)
触摸小键盘 3×4 小键盘 + 触控/PC 切换 已移除
署名 无 页脚 + 盲水印 Powered by Baka春秋

老数据怎么折算成按日(启动时自动完成,可重复执行):

  • 通行证:当年就是 unit='day' 且起止日期完整,清掉 season 标记即可,服务区间原样保留;
  • 深渊:同样已是 unit='day',结束日 = 开始日 + 周期天数 × 周期数 − 1,清掉 abyss/cycle_days 标记,区间原样保留;
  • 按次:按「1 次 = 1 天」折算。有总次数但没结束日的,结束日 = 开始日 + 总次数 − 1 天;仍缺结束日的退回开始日;
  • 折算只动数据,不删表、不删列:老库里的 season / abyss / cycle_days / total_count / used_count 等列会保留但不再使用,避免升级过程不可逆。

screen_public 与 portal_public 两个配置项在 lite 版仍然被解析(不报错),但不再有任何作用。