# 技能约束备忘录 本文档汇总 tag-system 与 fullstack-merge-deploy 两个技能的硬性约束,实施过程必须严格遵守。 --- ## 一、tag-system 技能约束 ### 1.1 三表结构固定 - **tag_group**:标签组(维度) - **tag**:标签节点(自引用树) - **tag_assignment**:通用打标关联(entity_type + entity_id + tag_id) 字段命名:`group_id`、`parent_id`、`sort_order`、`entity_type`、`entity_id` 全部小写下划线。 ### 1.2 根节点处理 - 创建标签组时**自动生成**一个 `parent_id=NULL` 的同名根节点 - 用户操作的标签都是根节点的子孙 - 导出时跳过根节点,直接输出子节点 - 前端 `buildTree()` 时遇到 `parent_id=null` 的视为根 ### 1.3 写操作统一用 POST 非 RESTful 风格,所有写操作用 POST: ``` POST /api/tags/groups/{id}/edit 而非 PUT POST /api/tags/groups/{id}/delete 而非 DELETE POST /api/tags/{id}/edit POST /api/tags/{id}/delete POST /api/tags/{id}/move ``` 请求体定义为 Controller 内部 `static class` + Lombok `@Data`。 ### 1.4 打标规范 - **全量替换**:每次保存提交完整 `tagIds` 列表,后端先 deleteAll 再 insert - **多标签**:一个实体可打 0~N 个标签 - **同组多标**:同一组内可多选 - **跨组打标**:可选择不同组的标签 - **非叶子可标**:任意层级节点都可选中 ### 1.5 防循环移动 `moveTag` 必须实现 `isDescendant()` 检查,向上遍历 parentId 链,防止形成环。 ### 1.6 导入策略 四种:`create` / `overwrite` / `merge` / `skip` - merge 算法:递归按名称匹配,同名保留 + 递归子节点,新名追加 - 幂等安全:同名标签不会重复创建 ### 1.7 前端组件结构 ``` api/tag.js 所有标签 API 封装 stores/tag.js Pinia store(含 buildTree) components/tag/ TreeNodeItem.vue 递归树节点 TagSelector.vue 标签选择器弹窗 views/TagsView.vue 标签管理页面(左右分栏) ``` ### 1.8 扩展实体类型 新增业务对象打标(如 article、knowledge)只需: - 前端 `TagSelector` 传入 `entity-type="article"` 和 `:entity-id="article.id"` - 后端无需改动,通用 API 自动支持 --- ## 二、fullstack-merge-deploy 技能约束 ### 2.1 端口 `application.yml` 中 `server.port: 7945` ### 2.2 前端构建输出 `frontend/vite.config.js` 中 `build.outDir` 必须直接指向后端 static: ```javascript build: { outDir: path.resolve(__dirname, '../backend/src/main/resources/static'), emptyOutDir: true } ``` ### 2.3 SPA Fallback(两文件配合) **绝对不能**: - ❌ `addResourceHandlers("/**")` — 会饿死 @Controller - ❌ `addViewControllers + forward:` — Spring Boot 3.x 返回空 body - ❌ `/**/{var}` 模式 — PathPatternParser 不支持 `**` 后接路径变量 **必须**: - ✅ `WebMvcConfig.java` 只配 CORS - ✅ `SpaController.java` 用 `@RequestMapping("/{x:[^.]+}")` 多段模式返回 ClassPathResource("/static/index.html") ### 2.4 正则约束 ```java @RequestMapping(value = { "/{x:[^.]+}", "/{x:[^.]+}/{y:[^.]+}", "/{x:[^.]+}/{y:[^.]+}/{z:[^.]+}" }) ``` 注意:Java 字符串中 `[^.]` 不要写成 `[^\.]`。 ### 2.5 run 脚本要点 - `run.bat` 开头必须 `chcp 65001 >nul`(中文 UTF-8 输出) - 严禁做"清理 static + 从 dist 复制"的步骤(Vite 已直出) - 流程仅两步:`npm run build` → `mvn package -DskipTests` ### 2.6 .gitattributes 必须创建 ``` *.bat text eol=crlf *.cmd text eol=crlf *.sh text eol=lf ``` ### 2.7 静态资源 - `spring-boot-starter-web` 已内置 `classpath:/static/` 处理 - 无需额外依赖 - Maven 自动将 static/ 打入 JAR --- ## 三、本项目特有约束 ### 3.1 Windows 环境 - 永远不用 `>/dev/null` 或 `>nul` 重定向到 nul(除 `chcp 65001 >nul` 这种已知安全场景,cmd 内部识别) - `$HOME` 显式替换为 `C:/Users/Administrator` - 不批量杀进程,必须按端口精确筛选 ### 3.2 LLM 调用 - 必须支持 OpenAI 兼容协议:`POST {base_url}/chat/completions` - 请求体:`{ model, messages, temperature, max_tokens }` - 响应:`{ choices: [{ message: { content } }] }` - 超时:连接 5s,读取 60s(文章聚合可能较长) ### 3.3 链接抓取 - Jsoup + 自实现 readability 规则(不引额外重型依赖) - 必须设置 User-Agent 与超时 - 失败时返回友好错误,前端弹窗让用户手动贴入正文 ### 3.4 Diff 计算 - 使用 `org.bitbucket.cowwoc:diff-match-patch:1.2` 或 `org.eclipse.mylyn.github:org.eclipse.mylyn.wikitext:diff-match-patch` - diff 按行计算(非按字符),便于阅读 - 同时存全量快照(按用户决策) ### 3.5 不启动服务 按 AGENTS.md 规则: - 不要主动启动前后端服务 - 测试需要时启动,完成后关闭 - 由用户手动启动