# 2026-11-03 账号管理与权限配置 ## 需求 「增加账号管理、权限配置的功能」—— 之前系统完全没有身份认证, 同一局域网任何人打开网址都能看全部数据、还能改阈值。 ## 设计:三级角色 | 角色 | 查看 | 导出 | 导入 | 同步 | 改阈值/文案 | 账号管理 | |---|---|---|---|---|---|---| | 管理员 admin | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 运营 editor | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | | 查看 viewer | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ## 实现 ### 新增 `backend/auth.py` - `hash_password()`:PBKDF2-HMAC-SHA256,12 万次迭代,每账号独立 16 字节盐 - `verify_password()`:`hmac.compare_digest` 防时序攻击 - `check_strength()`:至少 6 位、不能全是数字 - 角色与权限表(`ROLES` / `PERMS` / `GRANTS` / `can()`) - 会话令牌:`secrets.token_urlsafe(32)`,**库里只存 SHA-256 哈希** ### `db.py` 新增两张表 - `users`:用户名、密码哈希、盐、角色、启用状态、是否需改密、最近登录 - `sessions`:令牌哈希、用户、过期时间、IP;过期自动清理 ### `server.py` - 每个请求解析 `sid` Cookie → 还原当前用户 - 路由权限表 `PERM_RULES`,未列出的 `/api/*` 默认只要登录 - 未登录 → 401 `need_login`;权限不足 → 403 并说明当前角色 - 新接口:`/api/login`、`/api/logout`、`/api/me`(GET/PUT)、`/api/users`(GET/POST/PUT/DELETE)、`/api/auth/state` - 未登录访问 `/` 直接给 `login.html` - 首次启动 `ensure_admin()` 创建 `admin / admin123`,标记 `must_change=1` - 保护规则:不能删除/停用自己;不能把最后一个管理员降级或停用;改密码会踢掉该账号已有会话 ### 前端 - 新增 `login.html`(首次使用会显示初始账号提示) - 顶部显示「当前账号 · 角色」+ 改密码 / 退出 - 按权限隐藏左侧栏入口(指标配置、文案配置、账号管理、导入、同步、导出) - 登录后若 `must_change` 弹出**不可关闭**的改密框 - 新增账号管理侧面板:新建、改角色、停用/启用、重置密码、删除、最近登录时间 ## 验证 - 未登录:`/` → 登录页;`/api/dashboard` → 401 - admin 登录 → `must_change` 改密框弹出,全部 6 个入口可见 - viewer:看板 200,导出/改阈值/账号管理/同步/导入 全部 403 - editor:看板 + 导出 + 同步 200,改阈值/账号管理 403 - 改密:旧密码校验通过后生效,原密码随即失效;弱密码被拒("密码至少 6 位") - 账号面板:3 行正确渲染,角色下拉 3 个选项 ## 踩坑 1. `import auth` 没加进 server.py,登录报 `name 'auth' is not defined` 2. `/api/users` 把 `GRANTS` 里的 `set` 直接序列化,`_json` 的 `default=str` 把集合转成了 `"['view']"` 这样的字符串,前端拿到的是字符数组 —— 改为 `sorted(v)` 3. 权限测试时误触发了真实同步(`POST /api/sync` 返回 200 = 真跑了), 随后重启服务把同步打断,留下一个孤儿 ego 进程,已清理;数据未受影响 ## 导出入口合并 ### 问题 用户问:左侧栏【导出原始数据】跟【导入数据】里的导出是不是重复了。 ### 结论 有重叠但不完全等价: - 左侧栏那个 = 当前周期**最新一次**导入的原件 - 导入历史每行的按钮 = **指定批次**的原件(同一周期导过多次时能区分) 2026-11-02 就有两个批次(01:04 和 21:32),左侧栏只能拿到最新那份。 ### 处理 合并入口,左侧栏去掉【导出原始数据】(6 项 → 5 项),导入面板里新增: - 【下载当前周期最新一份】按钮(高频操作仍然一键) - 一段说明 + 每条历史记录的【原始文件】按钮(低频、需要选批次时用) 能力没有减少,只是入口收敛到「导入/导出是一对」的地方。 ### 验证 - 左侧栏现在 5 项:文案配置 / 指标配置 / 同步数据 / 导入数据 / 账号管理 - 导入面板里「导出原始文件」区块和按钮正常,历史 9 条记录按钮齐全 - 权限映射同步改为 `btnExportCur`,查看角色看不到这个按钮 ## 新建账号改为一次性表单 ### 反馈 「新建账号时,把所有要填的字段一次性列出,不要填完再跳下一个。角色用选择的,不要输入」 原来用三次 `prompt()` 依次问:用户名 → 密码 → 角色(还得手打 admin/editor/viewer,很容易打错)。 ### 改动 新增通用表单弹窗 `formModal({title, fields, validate, onSubmit})`,新建账号一次列出 5 个字段: | 字段 | 类型 | 说明 | |---|---|---| | 登录账号 | 文本 | 必填,限字母数字 `_ . -`,2~32 位 | | 显示名称 | 文本 | 选填,不填用账号名 | | 初始密码 | 密码 | 必填,≥6 位、不能全数字 | | 确认密码 | 密码 | 必填,两次必须一致 | | 角色 | **下拉选择** | 查看 / 运营 / 管理员,每项带一句说明 | 其他细节: - 错误提示在表单内就地显示,不弹 alert - 回车即提交,Esc/点遮罩可关闭 - 「重置密码」也一并改成同样的表单(原来也是 prompt) ### 验证 - 两次密码不一致 → 「两次输入的密码不一样」 - 账号名含空格 → 「登录账号只能用字母、数字、_ . -,2~32 位」 - 正常提交 → 弹窗关闭,列表立即出现「测试督导 test_dd」(已删除) ## 修关闭按钮 + 右上角退出登录 ### 问题 1:账号管理面板的 × 点了没反应 `closePanels()` 里只写了 `['thPanel', 'imPanel', 'txPanel']`,**漏了 `usPanel`**。 现在补上,同时左侧栏按钮的高亮状态也一并复位。 ### 问题 2:退出登录不明显 原来「改密码 / 退出」是标题下方的一行小链接,藏在 `hsub` 里。 改成右上角一个账号区: ``` [ 近30天 · 截至 11-03(1416店)▾ ] [ 临 临时验证/管理员 改密码 退出登录 ] ``` - 头像取名字首字,金色圆角块 - 名字 + 角色两行 - 「退出登录」做成描边按钮,hover 变红 - 头部改为 `flex-wrap:nowrap` + `.hleft{flex:1}`,保证账号区始终在右上角 (窄屏 <1100px 时才折行) ### 验证 - 点 × → 面板关闭(`classList.contains('on')` false) - 左侧栏按钮二次点击也能收起 - 右上角账号区 `rightTop=0, rightRight=0`,与标题同一行 ## admin 账号设为内置账号 ### 需求 「admin 账号的删除按钮要去掉,admin 账号是必须有的」 ### 改动 - 前端:`admin` 这一行不再显示【停用】【删除】,改为一个灰底说明「内置账号,不可停用/删除」 - 后端:`_user_delete` / `_user_update` 直接拦截,返回 「admin 是内置账号,不能删除」/「不能停用」 ### 验证 用另一个管理员账号调用接口: - `DELETE /api/users/1` → `admin 是内置账号,不能删除` - `PUT {"active":false}` → `admin 是内置账号,不能停用` 账号列表不变,admin 仍启用。 ## 部署到服务器时账号与数据会怎样(实测) 分别模拟了两种情况: ### 情况 A:只拷代码,不带 `data/` 目录 - 启动时自动创建内置账号 **admin / admin123**,并强制首次登录改密 - **门店数据为空**:0 家门店、0 个周期,需要先同步或导入 ### 情况 B:整个目录拷过去(含 `data/app.db`) - 不会重新初始化,**沿用原有的所有账号和密码** (实测:admin、lynch 都在;admin 的密码是改过的那个,`admin123` 已失效) - **全部历史数据都在**:1416 家门店、5651 条指标、4 个周期、9 个批次 - 原始文件也一起带过去(`data/processed/`),导出功能可用 ### 结论 一次迁移请连同 `data/` 一起拷;只要代码的话是干净的空白系统。 `data/` 里包含:`app.db`(账号+指标)、`processed/`(原始文件)、`inbox/`、`factory_thresholds.json`。 ## 服务器干净部署(方案 A)交付 ### 用户选择 「只拷代码,不带 data 目录」—— 服务器上是一个全新的空白系统。 ### 新增文件 | 文件 | 用途 | |---|---| | `start.sh` | Linux/服务器启动脚本,非交互、支持 `PORT` 环境变量和 `--local` | | `deploy/store-ops.service` | systemd 服务模板(自动重启、日志进 journal) | | `DEPLOY.md` | 完整部署说明:环境要求、上传、启动、systemd、端口放行、备份、排错 | ### 关键决策:包内文件名全部用 ASCII 第一次打包时中文文件名在 zip 里是乱码(`unzip -l` 显示 `store-ops/�?��?�工�?�?�.bat`), 和最早那个迁移包是同一个坑。服务器上乱码文件名会很难用,所以: - `门店检测工作台.service` → `store-ops.service` - `部署说明.md` → `DEPLOY.md` - 移除 `启动工作台.bat`、`开放局域网访问.bat`、`启动工作台.command`(服务器用不上) - 重打包后用 `zipfile` 校验:**中文文件名 0 个** ### 服务器上没有 ego-browser 的处理 自动同步依赖 ego-browser,服务器通常没有。原来会每天 01:00 报一次错。 现在启动时先检测: ``` 数据同步: 未启用(这台机器上没有 ego-browser) 请用【导入数据】上传从生意经导出的 Excel ``` 并且不再启动 daily_sync_loop 线程。实测(模拟 HOME/PATH 里没有 ego)通过。 ### 验证(从 zip 全新解压跑一遍) - 解压后 21 个文件,文件名全部正常 - 首次启动自动建 `admin / admin123`,强制改密 - 登录成功,看板返回空数据(引导页正常) - 部署包 432KB,不含 data 目录 ### 已知限制(已写进 DEPLOY.md) 服务器上没有自动同步,三种替代方案:手动导入 / 本机同步后上传 / 服务器装无头浏览器(不推荐)。