Files
wechat-scan/CODEBUDDY.md
T
gjm 6c5770b754 chore: 新增 .ai-memory 记忆备份,换机器可恢复
CodeBuddy 的记忆原本只存在本机用户目录(不进版本控制),换机器即丢失。
把 5 个记忆文件备份进仓库,clone 后让助手「从 .ai-memory/ 导入记忆」即可恢复。

- README 说明用途、导入方法与维护约定
- CODEBUDDY.md 补充同步约定(改完记忆须同步本目录)
- 内容仅含基础设施信息(IP/域名/用户名),无任何密码或密钥
2026-09-27 20:27:45 +08:00

7.1 KiB
Raw Blame History

CODEBUDDY.md

This file provides guidance to CodeBuddy Code when working with code in this repository.

项目概述

微信公众号扫码授权服务,基于 FastAPI,为 Windows MFC 桌面程序提供微信扫码授权与使用扣减。已实现微信服务器验证与消息/事件接收(/wechat)、扫码授权(/auth)、使用扣减(/usage)、只读管理后台(/admin)以及首次关注赠送 7 天免费授权。MySQL 已接入(aiomysql 连接池,5 张表);Redis 仅在配置层声明,尚未使用。

常用命令

# 安装依赖(建议先创建/激活虚拟环境 venv)
pip install -r requirements.txt

# 建库建表(MySQL 8,创建 wechat_api 库与 5 张表)
mysql -u root -p < sql/schema.sql

# 本地开发(uvicorn reload,监听 127.0.0.1:8000)
python run_local.py
# 等价于:
uvicorn wechat:app --reload --host 127.0.0.1 --port 8000

# 服务器部署(不 reload,单 worker,监听 127.0.0.1:8000,由 Nginx 反代)
python run_server.py

# 初始化本地配置
cp .env.example .env   # 然后填入真实值

当前仓库没有测试框架、lint 或构建配置;如需运行单个测试,需先引入 pytest 等工具。

架构

config.py       # 从 .env 读取配置(dotenv),模块级常量
db.py           # aiomysql 连接池:init_pool / close_pool / acquire(autocommit)
wechat_api.py   # 微信开放接口:access_token 内存缓存 + 临时二维码创建
wechat.py       # FastAPI app 本体:lifespan + /wechat 路由 + 签名校验 + XML 解析/构造
auth.py         # /auth/* 路由 + 扫码授权业务逻辑(含 pending 激活)
usage.py        # /usage/consume 路由:会话校验与使用扣减
admin.py        # /admin 只读管理后台:Basic Auth + 服务端渲染 HTML
run_local.py    # 开发启动入口(reload=True)
run_server.py   # 生产启动入口(reload=False, workers=1)
sql/schema.sql  # 建表脚本(5 张表,含索引与外键)
  • 配置:所有敏感值经 config.py 从 .env 读取,.env 已被 .gitignore 排除。.env.example 是字段模板。新增配置项需同时更新这两处。
  • 应用入口:两个启动脚本均以 "wechat:app" 字符串形式加载 wechat.py 中的 app,因此模块名/对象名不可随意重命名。lifespan 在启动时初始化 MySQL 连接池——MySQL 不可达或库表不存在时应用会直接启动失败。
  • 路由挂载:wechat.py 通过 include_router 挂载 auth.router、usage.router 与 admin.router。新增 MFC 侧接口应新建独立模块的 router,而不是塞进 wechat.py。
  • 微信交互协议:
    • 所有请求先经 verify_signature()(token+timestamp+nonce 字典序拼接后 SHA1 比对)校验,失败返回 403。
    • GET 校验通过后原样返回 echostr。
    • POST 解析微信推送的 XML(MsgType/FromUserName/Event 等),通过 _reply_text() 构造文本回复 XML 返回。新增消息类型处理应在 wechat_message() 的事件/消息分支中扩展。
    • subscribe 事件 EventKey 形如 qrscene_<scene_str>,SCAN 事件 EventKey 直接是 <scene_str>,由 _parse_scene_key() 统一提取。
  • 注意:_reply_text() 中 ToUserName/FromUserName 是反置的(回复时收发方互换),这是微信协议要求。

授权与扣减状态机

  • 互斥原则:同一用户同一时刻最多一条 status = 'active' 的授权。
  • 惰性激活:auth.activate_pending_authorization() 先把已失效的 active 标记为 expired/exhausted,再无 active 时按 FIFO 激活一条 pending。调用点有三处:/auth/status、扫码事件 handle_scan()、/usage/consume。必须在 get_active_authorization() 之前调用(后者带惰性置失效的副作用)。时间授权激活时按原时长从当前时刻重新锚定。
  • 免费授权幂等:靠 UPDATE users SET has_claimed_free = 1 WHERE id = ? AND has_claimed_free = 0 的 rowcount == 1 作闸门,只有把 0 改 1 的那一次才真正插入授权。
  • 积分扣减:条件 UPDATE ... WHERE id = ? AND status = 'active' AND remaining_points > 0 + rowcount 判定,禁止应用层先读后写。status 的赋值必须写在 remaining_points 自减之前——MySQL 的 SET 从左到右求值,否则 IF 会读到已减 1 的值,判空差 1。
  • session_token:随机生成、存 sessions 表、有效期 24 小时、绑定签发时的 device_id。/usage/consume 严格校验 token 存在、未过期且 device_id 一致,任一不符返回 invalid_token。
  • usage_logs:积分授权每次调用都写(计费凭证);时间授权按 (user_id, device_id) 在 USAGE_LOG_THROTTLE_SECONDS(默认 60 秒)窗口内节流。
  • 失败语义:/usage/consume 一律返回 HTTP 200,用 {"ok": false, "reason": "expired | exhausted | invalid_token"} 表达失败。

管理后台(/admin)

只读单页,用于查看用户与授权现状:概览统计、用户+当前授权、最近使用记录、会话令牌、扫码场景。

  • 鉴权:HTTP Basic Auth,凭据取自 .env 的 ADMIN_USER / ADMIN_PASSWORD,用 secrets.compare_digest 做定时安全比较。
  • ADMIN_PASSWORD 为空时 /admin 返回 503 而非放行——不要把它当成可选项,否则等于把全库用户数据公开。
  • 只提供 GET,没有任何写操作(改授权/加积分/封号一律不做)。
  • 不引模板引擎:HTML 由 f-string 拼装,所有入库字段经 html.escape();CSS 内联,不依赖任何 CDN(服务器出网不可靠)。
  • 页脚会显示数据库名;页头显示数据生成时间。不做自动刷新。
  • admin.py 被 wechat.py import,因此不要在 admin.py 里反向 import wechat(循环依赖)。

数据库

5 张表(定义见 sql/schema.sql):users、authorizations(授权,type 分 time/points)、auth_scenes(扫码场景,300 秒一次性)、usage_logs(使用日志)、sessions(会话令牌)。

  • auth_scenes 被授权后 status 不再回到 pending,重复扫码只处理第一次。
  • 外键:删除 user 会级联删除其 authorizations / usage_logs / sessions。
  • 场景过期、授权到期/耗尽的惰性标记在读取时完成,没有后台定时任务。

AI 记忆备份(.ai-memory/)

.ai-memory/ 是 CodeBuddy 持久记忆的仓库副本——记忆本体存在本机 ~/.codebuddy/projects/<工作目录名>/memory/,不进版本控制。

  • 改完记忆必须同步:把记忆目录的文件复制到 .ai-memory/ 并提交,否则换机器会丢。
  • 该目录会进 Git,因此绝不可写入密码、token、私钥——那些一律留在服务器 .env。
  • 新环境恢复:clone 后让助手「从 .ai-memory/ 导入记忆」。

约定

  • 代码注释与文档字符串使用中文。
  • 生产环境仅监听 127.0.0.1,对外暴露依赖 Nginx 反向代理。新增对外路由(如 /auth/、/usage/)时,必须同步在服务器 Nginx 配置里加对应的 location 块,否则公网请求会落到占位响应。