docs: 完善 CODEBUDDY.md(事务/并发/接口错误码/权限) #1
No files matched your search
@@ -1,4 +1,5 @@
|
||||
- [项目当前进度与待办](project_progress.md) — 三阶段状态、ICP 备案阻塞、出包前必须去掉 IP 覆盖等;**新对话先读这个**
|
||||
- [项目当前进度与待办](project_progress.md) — 三阶段状态、域名 HTTPS 已上线、正式号受限、出包前必须去掉 IP 覆盖等;**新对话先读这个**
|
||||
- [公众号主体与接口权限现状](project_wechat_account.md) — 正式号是个人订阅号/未认证/AppSecret 冻结;带参数二维码只有认证服务号可用,待选路线 A/B
|
||||
- [生产服务器部署环境](project_server_deploy.md) — ethereal-realm.top 的路径/服务/MySQL/nginx 实况,自建 Git 代码服务器拓扑,及 ICP 拦截、pypi 不可达、MySQL 非 Docker、nginx 双 server 块等坑
|
||||
- [CAD 插件微信授权集成](project_cad_wxauth_integration.md) — d:/Ethereal-Realm 的 CadOcr+cadAgent 接入进度、两端分工、先编 CadOcr 的顺序约束、本机只能编 R230+
|
||||
- [用户的命令行习惯与协作方式](user_shell_workflow.md) — 用 PuTTY,长命令会被折行截断,sudo 操作要写成脚本;已打通微信远程控制(回复走 WechatReply)
|
||||
|
||||
@@ -36,6 +36,7 @@ CodeBuddy 每次修改记忆时都会顺手同步,不需要你提醒。
|
||||
|---|---|
|
||||
| `MEMORY.md` | 索引,助手每次对话自动加载 |
|
||||
| `project_progress.md` | 三阶段进度、阻塞项、待办(新对话先读这个) |
|
||||
| `project_wechat_account.md` | 公众号主体类型/接口权限现状(订阅号受限、待选路线) |
|
||||
| `project_server_deploy.md` | 生产服务器实况与反直觉的坑 |
|
||||
| `project_cad_wxauth_integration.md` | CAD 插件集成进度与构建约束 |
|
||||
| `user_shell_workflow.md` | 协作习惯(PuTTY 长命令折行、sudo 走脚本等) |
|
||||
|
||||
@@ -4,7 +4,7 @@ description: wx-scan-authorize 三阶段状态快照、当前阻塞项与下一
|
||||
type: project
|
||||
---
|
||||
|
||||
`d:/wx-scan-authorize`(微信公众号扫码授权服务)截至 **2026-09-27 晚** 的状态。
|
||||
`d:/wx-scan-authorize`(微信公众号扫码授权服务)截至 **2026-10-10** 的状态。
|
||||
|
||||
**三阶段进度**
|
||||
|
||||
@@ -15,31 +15,44 @@ type: project
|
||||
| 3 | 充值(微信公众号支付) | **未开始** |
|
||||
| 附加 | 只读管理后台 `/admin`(计划外新增) | 2026-09-27 已上线 |
|
||||
|
||||
代码状态:本地与服务器均在 `f167550`,工作区干净,远端是自建 Git 服务器
|
||||
(见 [生产服务器部署环境](project_server_deploy.md))。
|
||||
代码状态:本地与服务器均在 `f167550`,工作区干净。远端已从 GitHub 迁到自建代码服务器,
|
||||
现又在应用服务器上跑起 **Gitea**(`git.ethereal-realm.top`,nginx 反代 127.0.0.1:3000),
|
||||
详见 [生产服务器部署环境](project_server_deploy.md)。
|
||||
|
||||
客户端在 `d:/Ethereal-Realm`:CadOcr + cadAgent 已完成编码与客户端侧端到端验证,
|
||||
`test_wxauth.exe` 已从控制台程序改造成 Win32 对话框,**相关改动已提交 SVN r40**,
|
||||
模型文件按用户决定未入库并已加 `svn:ignore`(r41)。详见
|
||||
`test_wxauth.exe` 已从控制台程序改造成 Win32 对话框,相关改动已提交 SVN r40/r41。详见
|
||||
[CAD 插件微信授权集成](project_cad_wxauth_integration.md)。
|
||||
|
||||
**域名与 HTTPS 已上线(2026-10-10)**
|
||||
|
||||
ICP 备案已通过,域名直连可用,不再依赖 Cloudflare 隧道:
|
||||
- nginx `wechat-api.conf` 已改为 **80→443 跳转 + 443 业务**(Certbot 管理),
|
||||
旧的 `app.conf`(IP 直连全量代理)已停用为 `app.conf.bak`。
|
||||
- 证书 Let's Encrypt,有效至 2027-01-07,`certbot-renew.timer` 自动续期。
|
||||
- 已验证:`https://ethereal-realm.top/wechat` 可达(422=缺参数,正常)、`/admin` 401、
|
||||
`/docs` 200。
|
||||
- **待收尾**:`cloudflared-quick` 隧道仍在运行,应停用;
|
||||
`https://ethereal-realm.top/docs` 仍暴露(用户此前选「暂不处理」)。
|
||||
- 注意:IP 直连(`http://39.107.55.179`)已失效(`app.conf` 停用),MFC 插件里写死的
|
||||
IP 联调地址会受影响。
|
||||
|
||||
**阻塞项与待办**
|
||||
|
||||
1. **ICP 备案进行中**(用户 2026-09-27 已提交,等待通过)——当前最大的外部变量:
|
||||
- 通过前:公众号后台「服务器配置」URL 指向 Cloudflare 快速隧道
|
||||
(URL 每次重启会变,捞取命令见 server_deploy 记忆)
|
||||
- 通过后:切回 `http://ethereal-realm.top/wechat`,并撤掉隧道
|
||||
2. **阶段 3 充值暂缓**。用户原话:「阶段3现放着吧,因为微信公众号想要收费的话,
|
||||
需要营业执照,现在域名的备案也没有做好。」→ 等营业执照 + 备案。
|
||||
1. **正式公众号受限,无法切换**(2026-10-10,当前最大阻塞):正式号是**个人订阅号、
|
||||
未认证、AppSecret 冻结**,而「带参数二维码」只有认证服务号能用 → 当前流程跑不通。
|
||||
详见 [公众号主体与接口权限现状](project_wechat_account.md)。**待用户选路线 A(办个体户
|
||||
注册服务号 + 认证)或路线 B(改流程用验证码绑定)**。
|
||||
2. **阶段 3 充值暂缓**。微信支付要求认证服务号 + 营业执照,与上面同一主体问题。
|
||||
3. **正式出包前必须去掉编译覆盖** `-DCADOCR_WX_BASE_URL=http://39.107.55.179`
|
||||
——当前本地 DLL 是 IP 联调版,把服务端地址写死成了 IP。
|
||||
——当前本地 DLL 是 IP 联调版,把服务端地址写死成了 IP;且该 IP 入口现已失效,
|
||||
需改为域名 `https://ethereal-realm.top`。
|
||||
4. **AutoCAD 里人工验证 `cadAgent.arx`** ——我这边没有 AutoCAD,只能用户做。
|
||||
用户 2026-09-27 明确表示暂不做。
|
||||
5. **IP 直连暴露 `/docs` 与 `/openapi.json`** ——用户选择暂不处理(详见
|
||||
server_deploy 记忆的 nginx 双 server 块章节)。
|
||||
5. **IP 直连暴露 `/docs` 与 `/openapi.json`** ——现域名也暴露(`location /` 全量代理)。
|
||||
用户此前选暂不处理,可随手关掉。
|
||||
|
||||
**Why:** 会话内的任务列表不跨对话持久化,关闭对话后这些状态不会自动带过去;
|
||||
只有写进记忆才能在新对话里续上。用户 2026-09-27 明确问过「关掉对话后怎么重新开始」。
|
||||
只有写进记忆才能在新对话里续上。
|
||||
|
||||
**How to apply:** 新对话开始时先读本文件确认进度。**动手前先问用户 ICP 备案是否
|
||||
已通过**——它决定公众号 URL 该配隧道还是配域名,是当前唯一会改变操作方式的变量。
|
||||
**How to apply:** 新对话开始时先读本文件确认进度。动手前先确认公众号主体问题的路线选择,
|
||||
它决定下一步是「办主体」还是「改代码」。
|
||||
@@ -16,7 +16,23 @@ type: project
|
||||
- nginx 配置:`/etc/nginx/conf.d/wechat-api.conf`,`server_name ethereal-realm.top`
|
||||
- `deploy` 在 `wheel` 组,`sudo` 需要密码 → 涉及 sudo 的操作必须让用户手动执行
|
||||
|
||||
**代码仓库拓扑(2026-09-27 起)**
|
||||
**代码仓库拓扑(2026-10-10 起:已迁到 Gitea)**
|
||||
|
||||
远端已改为**应用服务器上自建的 Gitea**:`origin = ssh://git@39.107.55.179:222/gjm/wechat-scan.git`
|
||||
(Gitea 监听 `:3000`,web 入口 `git.ethereal-realm.top`)。旧的 `47.93.46.28:1216` 自建裸仓库
|
||||
(见下方历史记录)已不再是当前远端。
|
||||
|
||||
- **推送必须走 PR 流程**,不要直接 `git push origin <branch>`:
|
||||
`git push origin HEAD:refs/for/main/<分支名>` → Gitea 自动创建/更新 PR
|
||||
(实测 `refs/for/main/feature/home-ai-dev` → 生成 PR #1,web `http://39.107.55.179:3000/gjm/wechat-scan/pulls/1`)。
|
||||
- **本地 git 要用对密钥**:Gitea 认的是 `~/.ssh/id_ed25519_guojianming`
|
||||
(注释 `guojianming.go@gmail.com`,Gitea 用户名 `home-Lenovo-Legion-r9000`);
|
||||
默认的 `~/.ssh/id_ed25519`(`deploy@aly-db`)只对 `deploy@` 登录有效,对 Gitea 会
|
||||
`Permission denied (publickey)`。推送时指定:
|
||||
`GIT_SSH_COMMAND="ssh -i ~/.ssh/id_ed25519_guojianming -o IdentitiesOnly=yes" git push origin HEAD:refs/for/main/<分支名>`
|
||||
- 当前开发分支是 `feature/home-ai-dev`,`main` 跟踪 `origin/main`。
|
||||
|
||||
**代码仓库拓扑(2026-09-27 起,历史)**
|
||||
|
||||
用户自建了一台 Linux 代码服务器 `gjm@47.93.46.28:1216`(CentOS 7.2,git 1.8.3.1),
|
||||
裸仓库在 `/srv/git/wx-scan-authorize.git`(owner `gjm`)。
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: 公众号主体与接口权限现状
|
||||
description: 正式公众号是个人订阅号、未认证、AppSecret 冻结;核心结论「带参数二维码只有已认证服务号可用」,故当前扫码登录流程无法在订阅号上跑通,待用户选路线 A/B
|
||||
type: project
|
||||
---
|
||||
|
||||
2026-10-10 用户正式注册了微信公众号,AppID `wxb4bd18ef8255c03f`
|
||||
(AppID 是公开标识、非密钥,可记录;**AppSecret 属敏感值,未认证前处于冻结状态,不入库**)。
|
||||
|
||||
**账号状态**
|
||||
- 类型:**订阅号**(个人主体,用户没有个体户/营业执照)
|
||||
- **未完成微信认证**;用户正在申请「改名」,改名通过后才做「个人认证」
|
||||
- **AppSecret 冻结**:官方提示「该账号尚未完成主体认证,前往微信公众平台完成认证后重试」
|
||||
→ 拿不到 `access_token`
|
||||
|
||||
**决定性结论(已查证微信官方开放社区,2026-10-10)**
|
||||
- **「生成带参数的二维码」接口只有「已完成微信认证的服务号」可用**;认证订阅号、
|
||||
个人订阅号均不可用。
|
||||
- 因此 **换到该订阅号跑不通当前扫码登录流程**;改名 + 个人认证也解决不了(仍是订阅号)。
|
||||
- 未认证个人订阅号**可以**用「服务器配置 + 接收消息 + 被动回复」(**不需要 AppSecret**),
|
||||
但开启服务器配置后就不能同时用后台自定义菜单(对本项目无影响)。
|
||||
|
||||
**可选路线(待用户拍板)**
|
||||
- **A(推荐,符合原设计)**:办个体户 → 注册「服务号」→ 微信认证 300 元/年。
|
||||
保留「扫码即授权、用户免手输」体验,并解锁阶段 3 微信支付(支付同样要求认证服务号)。
|
||||
- **B(不办个体户,但要改代码)**:改造登录流程绕开带参数二维码 ——
|
||||
MFC 显示随机验证码 → 用户关注公众号后把验证码发给公众号 →
|
||||
服务端用 `openid` + 验证码绑定设备并签发 session。未认证订阅号即可跑,
|
||||
但服务端与 MFC 端都要改,UX 多一步。
|
||||
|
||||
**Why:** 主体类型与接口权限是「从代码里看不出来」的外部约束,直接决定架构能否上线;
|
||||
照着现有代码改一堆,最后会卡在这个接口权限上。
|
||||
|
||||
**How to apply:** 讨论「切正式号 / 上线」时先看本文件。**不要再建议「用订阅号试试」**,
|
||||
除非确定走路线 B。微信测试号(拥有全部接口)只能作过渡,不能上生产。
|
||||
+32
-13
@@ -6,12 +6,21 @@ This file provides guidance to CodeBuddy Code when working with code in this rep
|
||||
|
||||
微信公众号扫码授权服务,基于 FastAPI,为 Windows MFC 桌面程序提供微信扫码授权与使用扣减。已实现微信服务器验证与消息/事件接收(`/wechat`)、扫码授权(`/auth`)、使用扣减(`/usage`)、只读管理后台(`/admin`)以及首次关注赠送 7 天免费授权。MySQL 已接入(aiomysql 连接池,5 张表);Redis 仅在配置层声明,尚未使用。
|
||||
|
||||
`REQUIREMENTS.md` 是需求与设计文档(表结构、接口契约、业务流程、阶段划分),接口字段与流程以其为准;本文件只记录「需要读多个源文件才能拼出」的架构约束与易踩的坑。
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 安装依赖(建议先创建/激活虚拟环境 venv)
|
||||
# 创建并激活虚拟环境(Windows)
|
||||
python -m venv venv
|
||||
source venv/Scripts/activate # bash;PowerShell 用 venv\Scripts\Activate.ps1
|
||||
|
||||
# 安装依赖
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 初始化本地配置(然后填入真实值)
|
||||
cp .env.example .env
|
||||
|
||||
# 建库建表(MySQL 8,创建 wechat_api 库与 5 张表)
|
||||
mysql -u root -p < sql/schema.sql
|
||||
|
||||
@@ -22,9 +31,6 @@ 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 等工具。
|
||||
@@ -42,36 +48,47 @@ admin.py # /admin 只读管理后台:Basic Auth + 服务端渲染 HTML
|
||||
run_local.py # 开发启动入口(reload=True)
|
||||
run_server.py # 生产启动入口(reload=False, workers=1)
|
||||
sql/schema.sql # 建表脚本(5 张表,含索引与外键)
|
||||
REQUIREMENTS.md # 需求与设计文档(表结构 / 接口契约 / 业务流程 / 阶段划分)
|
||||
```
|
||||
|
||||
- **配置**:所有敏感值经 `config.py` 从 `.env` 读取,`.env` 已被 `.gitignore` 排除。`.env.example` 是字段模板。新增配置项需同时更新这两处。
|
||||
- **应用入口**:两个启动脚本均以 `"wechat:app"` 字符串形式加载 `wechat.py` 中的 `app`,因此模块名/对象名不可随意重命名。`lifespan` 在启动时初始化 MySQL 连接池——**MySQL 不可达或库表不存在时应用会直接启动失败**。
|
||||
- **数据访问**:`db.acquire()` 借出的连接是 `autocommit=True`,单条写语句自动提交;需要多条语句原子性时必须显式 `await conn.begin()` / `commit()`(异常 `rollback()`)。`auth.py` / `usage.py` 的事务都是这个模式。
|
||||
- **路由挂载**:`wechat.py` 通过 `include_router` 挂载 `auth.router`、`usage.router` 与 `admin.router`。新增 MFC 侧接口应新建独立模块的 router,而不是塞进 `wechat.py`。
|
||||
- **微信交互协议**:
|
||||
- 所有请求先经 `verify_signature()`(token+timestamp+nonce 字典序拼接后 SHA1 比对)校验,失败返回 403。
|
||||
- 所有请求先经 `verify_signature()`(token+timestamp+nonce 字典序拼接后 SHA1 比对)校验,失败返回 403;`WECHAT_TOKEN` 为空时校验恒失败。
|
||||
- GET 校验通过后原样返回 `echostr`。
|
||||
- POST 解析微信推送的 XML(`MsgType`/`FromUserName`/`Event` 等),通过 `_reply_text()` 构造文本回复 XML 返回。新增消息类型处理应在 `wechat_message()` 的事件/消息分支中扩展。
|
||||
- POST 解析微信推送的 XML(`MsgType`/`FromUserName`/`Event` 等),通过 `_reply_text()` 构造文本回复 XML 返回。新增消息类型处理应在 `wechat_message()` 的事件/消息分支中扩展(现处理 `subscribe` / `SCAN` / `unsubscribe` / `CLICK` 与文本 echo)。
|
||||
- `subscribe` 事件 EventKey 形如 `qrscene_<scene_str>`,`SCAN` 事件 EventKey 直接是 `<scene_str>`,由 `_parse_scene_key()` 统一提取。
|
||||
- **微信 access_token**:`wechat_api.get_access_token()` 在**进程内**内存缓存(有效期 7200 秒,提前 300 秒失效),命中 40001/42001 时清空缓存下次重取。部署按单 worker 设计(`run_server.py` 也是 `workers=1`),多进程会各存一份缓存。
|
||||
- **注意**:`_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。
|
||||
- **并发串行化**:`handle_scan()` 对 scene 行加 `SELECT ... FOR UPDATE`(同一 scene 只处理一次);`activate_pending_authorization()` 对 users 行加 `FOR UPDATE`(串行化同一用户的并发激活)。二者都要求调用方已开启事务。
|
||||
- **免费授权幂等**:靠 `UPDATE users SET has_claimed_free = 1 WHERE id = ? AND has_claimed_free = 0` 的 `rowcount == 1` 作闸门,只有把 0 改 1 的那一次才真正插入授权;已有 active 授权时,新发的免费授权以 `pending` 保存。
|
||||
- **积分扣减**:条件 `UPDATE ... WHERE id = ? AND status = 'active' AND remaining_points > 0` + `rowcount` 判定,禁止应用层先读后写。**`status` 的赋值必须写在 `remaining_points` 自减之前**——MySQL 的 SET 从左到右求值,否则 `IF` 会读到已减 1 的值,判空差 1。同理,激活 pending 时 `end_at` 必须写在 `start_at` 之前。
|
||||
- **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"}` 表达失败。
|
||||
- **失败语义**:`/usage/consume` 一律返回 HTTP 200,用 `{"ok": false, "reason": "expired | exhausted | invalid_token"}` 表达失败;无 active 授权时 `_reason_without_active()` 按最近一条失效授权的类型决定 reason 是 `exhausted` 还是 `expired`。
|
||||
|
||||
## 接口错误码(MFC 侧)
|
||||
|
||||
- `POST /auth/create_scene`:微信接口不可用时返回 **502**。
|
||||
- `GET /auth/status`:scene 不存在返回 **404**;状态取值 `pending | authorized | expired | need_purchase`,其中 `need_purchase` = 免费已领过且当前无有效授权(引导充值,阶段 3)。
|
||||
- `POST /usage/consume`:失败仍为 HTTP 200,靠 body 里的 `ok=false`。
|
||||
|
||||
## 管理后台(/admin)
|
||||
|
||||
只读单页,用于查看用户与授权现状:概览统计、用户+当前授权、最近使用记录、会话令牌、扫码场景。
|
||||
|
||||
- **鉴权**:HTTP Basic Auth,凭据取自 `.env` 的 `ADMIN_USER` / `ADMIN_PASSWORD`,用 `secrets.compare_digest` 做定时安全比较。
|
||||
- **鉴权**:HTTP Basic Auth,凭据取自 `.env` 的 `ADMIN_USER` / `ADMIN_PASSWORD`,用 `secrets.compare_digest` 对 **UTF-8 字节**做定时安全比较(对含非 ASCII 的 str 直接比会抛 `TypeError`)。
|
||||
- **`ADMIN_PASSWORD` 为空时 `/admin` 返回 503 而非放行**——不要把它当成可选项,否则等于把全库用户数据公开。
|
||||
- **realm 必须 ASCII**:Starlette 按 latin-1 编码响应头,中文 realm 会直接抛异常(当前为 `CadOcr Admin`)。
|
||||
- **只提供 GET**,没有任何写操作(改授权/加积分/封号一律不做)。
|
||||
- **不引模板引擎**:HTML 由 f-string 拼装,所有入库字段经 `html.escape()`;CSS 内联,不依赖任何 CDN(服务器出网不可靠)。
|
||||
- **不引模板引擎**:HTML 由 f-string 拼装,所有入库字段经 `html.escape()`;CSS 内联,不依赖任何 CDN(服务器出网不可靠)。会话令牌在页面上只显示首尾(`_token()`),不给全量。
|
||||
- 页脚会显示数据库名;页头显示数据生成时间。不做自动刷新。
|
||||
- `admin.py` 被 `wechat.py` import,因此**不要在 `admin.py` 里反向 import `wechat`**(循环依赖)。
|
||||
|
||||
@@ -80,16 +97,18 @@ sql/schema.sql # 建表脚本(5 张表,含索引与外键)
|
||||
5 张表(定义见 `sql/schema.sql`):`users`、`authorizations`(授权,type 分 time/points)、`auth_scenes`(扫码场景,300 秒一次性)、`usage_logs`(使用日志)、`sessions`(会话令牌)。
|
||||
|
||||
- `auth_scenes` 被授权后 status 不再回到 pending,重复扫码只处理第一次。
|
||||
- 外键:删除 user 会级联删除其 authorizations / usage_logs / sessions。
|
||||
- 外键:删除 user 会级联删除其 authorizations / usage_logs / sessions;`auth_scenes.user_id`、`sessions.scene_id`、`usage_logs.authorization_id` 为 `ON DELETE SET NULL`。
|
||||
- 场景过期、授权到期/耗尽的惰性标记在读取时完成,没有后台定时任务。
|
||||
- `REQUIREMENTS.md` 里的 `orders` 表属于阶段 3,`sql/schema.sql` 尚未包含。
|
||||
|
||||
## AI 记忆备份(.ai-memory/)
|
||||
|
||||
`.ai-memory/` 是 CodeBuddy 持久记忆的仓库副本——记忆本体存在本机
|
||||
`~/.codebuddy/projects/<工作目录名>/memory/`,不进版本控制。
|
||||
`~/.codebuddy/projects/<工作目录名>/memory/`,不进版本控制。目录说明见 `.ai-memory/README.md`。
|
||||
|
||||
- **改完记忆必须同步**:把记忆目录的文件复制到 `.ai-memory/` 并提交,否则换机器会丢。
|
||||
- 该目录**会进 Git**,因此**绝不可写入密码、token、私钥**——那些一律留在服务器 `.env`。
|
||||
- `.gitattributes` 强制 `.ai-memory/*.md` 使用 LF,便于逐字节比对;改动时保持 LF。
|
||||
- 新环境恢复:clone 后让助手「从 `.ai-memory/` 导入记忆」。
|
||||
|
||||
## 约定
|
||||
|
||||
Reference in new issue
Block a user