diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..47dda8f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,141 @@ +# AGENTS.md +本文件面向在本仓库中工作的自动化编码代理(agent)。 +目标:在不破坏现有行为的前提下,稳定、可回归地进行开发与维护。 + +## 0. 仓库概览 +- 仓库名:`quick-share`(DataTool - 房间数据传输助手)。 +- 架构:前后端分离。 + - `backend/`:Spring Boot 3.2.5 + Java 17。 + - `frontend/`:Vue 3 + TypeScript + Vite + Pinia + TailwindCSS。 +- 通信约定: + - REST:`/api/room/**`。 + - WebSocket(STOMP/SockJS):`/ws`,应用前缀 `/app`,订阅前缀 `/topic`。 + +## 1. 环境与启动 +### 1.1 版本要求 +- Java:17+。 +- Maven:3.8+。 +- Node.js:18+。 +- npm:9+。 + +### 1.2 本地启动(开发) +- 后端:`cd backend && mvn spring-boot:run`。 +- 前端:`cd frontend && npm install && npm run dev`。 +- 默认地址: + - 后端 `http://localhost:8080` + - 前端 `http://localhost:5173` + +## 2. Build / Lint / Test 命令 +说明:仓库未配置前端 lint/test 脚本;后端使用 Maven 标准测试流程。 + +### 2.1 后端(Maven) +- 全量编译+测试:`cd backend && mvn clean test`。 +- 全量验证(CI 推荐):`cd backend && mvn clean verify`。 +- 打包(跳过测试):`cd backend && mvn clean package -DskipTests`。 +- 运行单个测试类:`cd backend && mvn -Dtest=RoomServiceTest test`。 +- 运行单个测试方法:`cd backend && mvn -Dtest=RoomServiceTest#shouldCreateRoom test`。 +- 故障排查:可追加 `-e`(详细异常)或 `-X`(debug 日志)。 + +### 2.2 前端(Vite + TypeScript) +- 安装依赖:`cd frontend && npm install`。 +- 开发启动:`cd frontend && npm run dev`。 +- 生产构建:`cd frontend && npm run build`。 +- 预览构建:`cd frontend && npm run preview`。 +- 类型检查:`cd frontend && npx tsc --noEmit`。 + +### 2.3 “单测”现状(重点) +- 后端:支持 `-Dtest=类名` / `-Dtest=类名#方法名` 精准执行。 +- 前端:当前未安装 Vitest/Jest,且无 `npm test` 脚本。 +- 若任务要求“跑前端单测”,先确认是否已新增测试框架再执行。 + +## 3. 通用开发原则 +- 只做最小必要改动,避免无关重构。 +- 不随意改对外协议字段名(REST/WS JSON)。 +- 优先复用现有 store/service/utils,避免重复造轮子。 +- 前后端联动改动时,保持契约同步(字段、枚举、路径)。 +- 错误处理优先“可读、可定位”,不要静默吞关键异常。 + +## 4. 前端规范(Vue 3 + TypeScript) +### 4.1 类型与 TS +- `frontend/tsconfig.json` 已启用 `strict: true`,新增代码必须通过。 +- 禁用 `any`;必要时使用 `unknown` 并做类型收窄。 +- API 响应、消息模型、store 状态优先显式接口定义。 +- 公共类型放在 `frontend/src/types/`(如 `room.ts`)。 +- 类型导入优先 `import type`。 + +### 4.2 组件与状态管理 +- 使用 `script setup lang="ts"`(与现有组件一致)。 +- 组件文件名使用 `PascalCase.vue`。 +- 组合式 API 优先:`ref` / `computed` / `watch`。 +- 跨组件共享状态集中到 Pinia(当前核心在 `wsStore`)。 +- WebSocket 逻辑优先复用 `RoomWsClient` + `wsStore`,避免旁路实现。 + +### 4.3 导入、路径与格式 +- 导入顺序建议: + 1) 第三方依赖。 + 2) `@/` 别名模块。 + 3) 相对路径模块。 +- 优先使用 `@/` 别名,减少深层 `../../`。 +- 现有 TS/Vue 代码风格:2 空格缩进、单引号、语句末尾分号。 + +### 4.4 命名约定 +- 变量/函数:`camelCase`。 +- 类型/接口:`PascalCase`。 +- 常量:`UPPER_SNAKE_CASE`(阈值、key 前缀等)。 +- 布尔语义命名:`is/has/can/should` 前缀。 + +### 4.5 样式与 UI +- 样式体系:Tailwind + 少量 CSS 变量(见 `frontend/src/assets/main.css`)。 +- 复用语义色:`primary` / `success` / `danger` / `warning`。 +- 不引入新 UI 框架(除非任务明确要求)。 +- 响应式写法遵循已有断点习惯:`sm` / `md` / `lg`。 + +### 4.6 前端错误处理 +- `fetch` 后必须检查 `res.ok`,失败时给出可理解错误。 +- `catch` 中保留必要诊断信息,同时确保 UI 状态可恢复。 +- 对上传/下载/队列等流程,错误后要维护一致状态(进度、loading、重试入口)。 + +## 5. 后端规范(Spring Boot + Java) +### 5.1 Java 风格 +- 使用 Java 17 特性时保持克制,优先可读性。 +- 缩进 4 空格;类名 `PascalCase`,成员 `camelCase`,常量 `UPPER_SNAKE_CASE`。 +- DTO/不可变结构优先 `record`(项目已采用)。 + +### 5.2 分层职责 +- `controller`:参数校验、协议转换、状态码。 +- `service`:核心业务规则、状态流转。 +- `config`:框架配置与属性绑定。 +- 避免把复杂业务塞入 Controller。 + +### 5.3 异常与 I/O +- 参数/状态非法:优先 `IllegalArgumentException` 或明确状态码。 +- I/O 失败:保留异常链,不吞异常。 +- 对外返回结构保持稳定,避免随意改 key。 +- 文件/流操作使用 `try-with-resources` 并处理清理逻辑。 + +### 5.4 并发与状态 +- 房间会话是并发场景,遵循现有并发容器策略(如 `ConcurrentHashMap`)。 +- 共享状态修改必须考虑线程安全与资源释放(离房、断连、文件清理)。 + +## 6. 协议与兼容性 +- 消息结构以 `frontend/src/types/room.ts` 与后端 `MessagePayload` 对齐。 +- 修改消息字段时必须同步更新: + - 前端类型定义。 + - 前端消息处理分支。 + - 后端发送/接收逻辑。 +- 现有架构约束:文件内容走 HTTP,消息元数据走 WebSocket,不要随意改。 + +## 7. 变更后自检 +- 后端改动:至少执行 `cd backend && mvn test`(或受影响单测)。 +- 前端改动:至少执行 `cd frontend && npm run build` 与 `cd frontend && npx tsc --noEmit`。 +- 文档改动:核对命令、端口、路径、环境变量名是否与仓库一致。 +- 最终确认:未误改协议字段、路由路径、事件类型、配置 key。 + +## 8. Cursor / Copilot 规则同步 +已检查以下位置: +- `.cursorrules` +- `.cursor/rules/` +- `.github/copilot-instructions.md` + +当前仓库未发现上述文件,因此本 `AGENTS.md` 为主要代理执行规范。 +若后续新增 Cursor/Copilot 规则,请将关键约束并入本文件并保持一致。 diff --git a/frontend/src/utils/hash.ts b/frontend/src/utils/hash.ts new file mode 100644 index 0000000..bc38a9c --- /dev/null +++ b/frontend/src/utils/hash.ts @@ -0,0 +1,21 @@ +export async function sha256HexOfBlob(blob: Blob): Promise { + if (!('crypto' in globalThis) || !globalThis.crypto?.subtle) { + return null; + } + + try { + const buffer = await blob.arrayBuffer(); + const digest = await globalThis.crypto.subtle.digest('SHA-256', buffer); + return bytesToHex(new Uint8Array(digest)); + } catch { + return null; + } +} + +function bytesToHex(bytes: Uint8Array): string { + let hex = ''; + for (const byte of bytes) { + hex += byte.toString(16).padStart(2, '0'); + } + return hex; +}