* LICENSE —— MIT
* THIRD-PARTY-NOTICES —— 依赖清单、再分发合规说明(含随仓库分发的
Apache ECharts 5.6.0 / Apache-2.0)与自查清单
* CONTRIBUTING.md —— 开发环境、五层验证、必须遵守的不变量、提交规范
* SECURITY.md —— 漏洞私有报告渠道、已有措施、已知非目标
* CODE_OF_CONDUCT.md —— 改编自 Contributor Covenant 2.1
* .github/ —— Bug 报告 / 功能建议表单 + PR 模板
* .editorconfig —— 与 .gitattributes 保持一致
* 全部 Python / Shell 源文件加 SPDX-License-Identifier: MIT 头
* README 增加「开源与许可」章节与许可标识
154 行
9.1 KiB
Markdown
154 行
9.1 KiB
Markdown
# 变更日志
|
||
|
||
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/):`主版本.次版本.修订号`。
|
||
|
||
- **主版本**:不兼容的变更(数据库迁移需手工介入、接口契约变化)
|
||
- **次版本**:向后兼容的功能新增
|
||
- **修订号**:向后兼容的缺陷修复
|
||
|
||
---
|
||
|
||
## [1.1.0] — 2026-09-14
|
||
|
||
**主题:项目定名 `workbuddy-portal` · 容器化 · 文档体系**
|
||
|
||
### 新增
|
||
|
||
- **Docker 化**:多阶段 `Dockerfile`(依赖层与运行层分离,改业务代码不触发重装依赖)、
|
||
`docker-compose.yml`(单服务、**数据放 Docker 命名卷**、健康检查、日志轮转)、
|
||
`docker-compose.hostdir.yml`(可选叠加层:把数据/日志放到宿主机目录,仅建议 Linux)、
|
||
`docker/entrypoint.sh`(幂等初始化 → exec 交接)、`docker/healthcheck.py`(纯标准库)、
|
||
`.dockerignore`、`.env.example`
|
||
- **容器环境变量**:`WB_HOST` `WB_PORT` `WB_DATA_DIR` `WB_LOG_DIR` `WB_DB`
|
||
`WB_ADMIN_USER` `WB_ADMIN_PASSWORD` `WB_DISABLE_SCHEDULER` `WB_IMPORT_CREDS` `WB_IMPORT_XLSX`
|
||
- **文档体系** `docs/`:
|
||
[用户使用手册](USER-GUIDE.md)(含 9 张界面截图,**全部用合成示例数据渲染**)、
|
||
[部署与运维指南](DEPLOYMENT.md)(含推镜像到 Gitea 注册表的完整流程)、
|
||
[架构与设计说明](ARCHITECTURE.md)、
|
||
[接口参考](API.md)、
|
||
[常见问题](FAQ.md)
|
||
- **开源声明体系**(仓库根目录):[LICENSE](../LICENSE)(MIT)、
|
||
[THIRD-PARTY-NOTICES.md](../THIRD-PARTY-NOTICES.md)(依赖清单与再分发合规自查)、
|
||
[CONTRIBUTING.md](../CONTRIBUTING.md)(含「必须遵守的不变量」与五层自检方法)、
|
||
[SECURITY.md](../SECURITY.md)、[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)、
|
||
`.github/` 下的 Issue 表单与 PR 模板、`.editorconfig`,
|
||
以及给全部 Python / Shell 源文件加 `SPDX-License-Identifier: MIT` 头
|
||
- **`tools/demo_data.py`**:生成**完全合成**的示例库(假模型名 / 假 Prompt / 偏斜的积分分布),
|
||
写入 `data/demo/`(已在 `.gitignore` 内)。文档截图与本地调试都基于它,
|
||
任何人不需要真实账号就能复现整套界面
|
||
- **`.gitattributes`**:强制 `*.sh` / `Dockerfile` / 各类源码为 LF
|
||
(带 CRLF 的 `.sh` 在容器里会报 `exec format error`,极难定位)
|
||
|
||
### 变更
|
||
|
||
- **文档数据脱敏**:9 张界面截图全部改用合成示例数据重拍;
|
||
`docs/API.md`、`docs/DEPLOYMENT.md` 示例响应里的真实模型名与真实统计数字一并替换为示例口径。
|
||
此前截图中含**真实 Prompt 全文**、本机路径与本机用户名,属于不该公开的内容
|
||
- **`.gitignore` 补强**:只写 `data/*.sqlite` 会漏掉子目录,改为同时保留 `data/**/*.sqlite`
|
||
等规则,并新增 `data/demo/` 忽略——否则 `tools/demo_data.py` 的产物会被误提交
|
||
|
||
- **项目定名**:`wb_usage_portal` → **`workbuddy-portal`**;
|
||
Python 包 `wb_usage` → **`workbuddy_portal`**;会话 cookie
|
||
`wb_usage_sid` → `workbuddy_portal_sid`(升级后需要重新登录)
|
||
- **界面品牌**统一为 **WorkBuddy Portal**(此前为「WorkBuddy 用量门户」)
|
||
- 项目标识收敛到 `config.PROJECT_NAME` / `PROJECT_TITLE` / `PROJECT_DESC` 单一来源,
|
||
模板通过 `app_name` 等上下文变量引用,不再多处硬编码
|
||
- 数据 / 日志目录支持环境变量覆盖(`WB_DATA_DIR` / `WB_LOG_DIR` / `WB_DB`)
|
||
- 版本号 1.0.0 → **1.1.0**
|
||
- 大屏页标题改为「WorkBuddy Portal · 积分消耗大屏」
|
||
|
||
### 修复
|
||
|
||
| # | 症状 | 根因 |
|
||
|---|---|---|
|
||
| 1 | `/records/export` **必然 500** | 生成器在请求上下文销毁后才被迭代,复用 `db.get_db()` 撞「数据库已关闭」。改为生成器内自建连接 |
|
||
| 2 | **大屏页图表全白** | `/dashboard` 无尾斜杠,`src="vendor/echarts.min.js"` 被解析成 `/vendor/…` → 404 |
|
||
| 3 | `/users` 500 | 路由已注册但 `users.html` 不存在 |
|
||
| 4 | 明细页日期筛选失效 | 视图传 `f.frm`、模板读 `f.from`;导出链接拼 `?frm=` 而接口只认 `from` |
|
||
| 5 | 配置页 3 个维护按钮全死 | 模板调 `WBU.bindMaint()`,`app.js` 里没有该函数 |
|
||
| 6 | 审计只能看最近 40 条 | `LIMIT 40` 写死 |
|
||
| 7 | 明细页多跑一条无用 `SELECT` | `day_list()` 取了没人用 |
|
||
| 8 | 登录页锁定阈值写死 | 未从配置注入 |
|
||
|
||
> **#2 值得单独一提**:前两层测试都没抓到(离线断言只看状态码 + `<html>`,
|
||
> 真实 HTTP 只看状态码),是加上 Playwright 截图后才发现的。
|
||
> 据此给 `tools/smoke.py` 补了「页面所有 `src`/`href` 资源引用逐个断言 200」一节。
|
||
|
||
另外在容器实测中发现并修掉一个**部署期才暴露**的问题:
|
||
|
||
| 症状 | 根因 | 处理 |
|
||
|---|---|---|
|
||
| 容器跑着跑着页面全 500,日志里 `sqlite3.OperationalError: unable to open database file` | 数据原本用**绑定挂载**;Windows + Docker Desktop 走 **9p**,宿主的 Windows 进程只要访问过这个 WAL 库(**纯读也会触发**),容器侧下一次连接就重建不了 `-shm`,且**不会自愈** | 改为 **Docker 命名卷**(容器独占数据目录);需要宿主目录时叠加 `docker-compose.hostdir.yml`(仅建议 Linux) |
|
||
|
||
最小复现:容器正常 → 宿主跑一次 `manage.py stats` → 容器立刻打不开库、且重启前不再恢复。
|
||
已写进 [DEPLOYMENT.md 第九节](DEPLOYMENT.md#windows-绑定挂载的坑容器打不开数据库)。
|
||
|
||
### 安全
|
||
|
||
- 新增 `security.safe_next()`:登录跳转的 `next` 拒绝 `//evil.com`(协议相对 URL)、
|
||
`/\evil.com`、绝对地址与含 CR/LF 的值
|
||
- 缺 CSRF 的写请求统一 400(此前部分路径漏检)
|
||
- 默认**开启**云端 HTTPS 证书校验(`ssl_verify=1`)。Cookie 是账号凭证,不该裸奔
|
||
- 登录失败计数表加上限(4096 个 IP)与 TTL(1 小时),防内存被大量来源 IP 撑爆
|
||
- `/logout` 拆分为 POST(执行)+ GET(仅提示),防 `<img src="/logout">` 静默退出
|
||
- `settings` 的内部簿记键(`slot:*`)读写两侧都过滤,不再从 `/api/settings` 泄漏
|
||
|
||
### 内部质量
|
||
|
||
- 设置项**写时校验 + 读时兜底**:`config.normalize_setting()`(范围 + 单位,非法值 400
|
||
并列出全部错误)+ 采集路径全面改用 `db.get_int()`,杜绝「一个手滑的数字让采集整个跑不起来」
|
||
- 全局 `ValueError` → 400 处理器:手写 query string 不再能把 500 页面暴露出去
|
||
- 日期归一化 `norm_day()` / `norm_window()`(容忍 `2026/09/08`、带时间、起止写反)
|
||
- CSV 导出改用 `csv.writer` 流式写入(此前手工拼字符串,`model`/`client` 含逗号会串列)
|
||
- `bundle` 明细加下限(`BUNDLE_RECORDS_CAP = 20000`),并返回
|
||
`recordsTotal` / `recordsCap` / `recordsTruncated`,不静默丢数据
|
||
- 轮询日志尾部改用 `collections.deque(maxlen=n)`,不再把整个文件读进内存
|
||
- 修掉一条非法 CSS 声明 `font: 13px/1.5 inherit`(简写里 `inherit` 不能当字族,
|
||
整条被浏览器丢弃,输入框一直用默认字体)
|
||
|
||
### 工具
|
||
|
||
- 新增 `tools/smoke.py`:**离线回归 99 项断言**(全页面只读渲染 + 模板残留 +
|
||
历史缺陷防回归 ①~⑭ + 静态资源逐个 200 + CSV 列 + 页面 class ↔ `app.css` 选择器对账),
|
||
不需要先起服务
|
||
- `tools/check_live.py` 扩充到 **56 项断言**,新增用户管理 / 审计 / 流式导出 /
|
||
开放重定向 / CSRF 四节
|
||
- 新增 `tools/shots.py`:Playwright 登录后逐页截图 + 收集 console / pageerror
|
||
(内建可执行文件探测,规避驱动与本机浏览器版本错位)
|
||
|
||
---
|
||
|
||
## [1.0.0] — 2026-09-14
|
||
|
||
**主题:从「脚本 + CSV + 静态大屏」演化为独立可部署的门户**
|
||
|
||
### 新增
|
||
|
||
- 独立 Flask 应用:`create_app` 工厂 + `views` / `api` 双蓝图
|
||
- SQLite(WAL)作为**唯一数据正本**,主键去重、断点续采、聚合全部下推 SQL
|
||
- 进程内调度线程(20 秒轮询 + 槽位去重 + 启动补跑),**不再依赖外部计划任务**
|
||
- 单写者文件锁 `data/collect.lock`(含 30 分钟僵尸锁抢占)
|
||
- 登录鉴权(`pbkdf2:sha256:200000`)、全站 CSRF、同 IP 失败限速
|
||
- 后台页面:概览 / 数据明细 / 任务管理 / 配置管理 / 日志管理
|
||
- 独立 ECharts 交互大屏 `/dashboard`(离线自带 echarts,不依赖 CDN)
|
||
- CLI:`init` `serve` `collect` `migrate-csv` `import-xlsx` `import-creds`
|
||
`fill-prompt` `export-csv` `stats` `status` `passwd` `vacuum`
|
||
- 从旧版 CSV / 官网 xlsx / 编辑器设置导入的迁移通道
|
||
- 从 VSCode / Cursor / Trae 的 `settings.json` 接管 Cookie 与 User-Agent
|
||
|
||
### 变更
|
||
|
||
- 数据正本从 CSV 改为 SQLite;CSV 降级为导出物(`data/exports/`)
|
||
- 采集从外部脚本改入 Web 进程;删除全部外部自动化与计划任务
|
||
- 运行期配置(Cookie、调度、采集参数)从文件搬进数据库,由后台页面维护
|
||
|
||
---
|
||
|
||
## 版本对照
|
||
|
||
| 版本 | 数据正本 | 调度 | 部署 | 鉴权 |
|
||
|---|---|---|---|---|
|
||
| 1.1.0 | SQLite(WAL) | 进程内 | Docker Compose / 裸机 | 登录 + CSRF + 角色 |
|
||
| 1.0.0 | SQLite(WAL) | 进程内 | 裸机 | 登录 + CSRF |
|
||
| < 1.0 | CSV 文件 | 外部计划任务 | 脚本 | 无(仅局域网) |
|