# 网盘系统 (WangPan) - 项目全局参考文件 > **用途**:本文档记录项目规范、命名约定、常见问题与解决方案,供 TraeAI 在每次调整代码前阅读参考。 > > **规则**:每次修改项目代码前,TraeAI 必须读取本文件以确保遵循项目规范。 --- ## 一、项目概览 | 项目 | 说明 | |------|------| | 名称 | WangPan(网盘系统) | | 架构 | 微服务(Spring Boot 3.2.5 + Spring Cloud Gateway) | | 语言 | Java 17 | | 构建工具 | Maven | | 包名根路径 | `com.wangpan` | | 数据库 | MySQL 8.0.33 | | 对象存储 | MinIO | | ORM | MyBatis-Plus 3.5.7 | | 配置加密 | Jasypt(Jasypt Spring Boot Starter) | ### 模块结构与端口 | 模块 | 端口 | 说明 | |------|------|------| | `wangpan-common` | - | 公共模块(DO/Mapper/Repository/Entity/Util/Interceptor) | | `wangpan-user-service` | 8081 | 用户认证服务(登录、注册、Token管理) | | `wangpan-drive-service` | 8082 | 网盘管理服务(文件管理、电影管理) | | `wangpan-tmdb-service` | 8083 | TMDB 刮削服务(电影元数据抓取) | | `wangpan-gateway` | 8888 | API 网关(统一路由入口) | --- ## 二、命名规范 ### 2.1 包命名 ``` com.wangpan # 根包 ├── common # 公共模块 │ ├── annotation # 自定义注解 │ ├── config # 配置类 │ ├── dal # 数据访问层 │ │ ├── dataobject # 数据对象(DO) │ │ ├── mapper # MyBatis Mapper 接口 │ │ └── repository # Repository 层(封装数据访问) │ ├── entity # 业务实体 │ ├── interceptor # 拦截器 │ ├── listener # 监听器 / 定时任务 │ └── util # 工具类 ├── controller # 控制器(各子模块各自持有) ├── service # 业务服务层(各子模块各自持有) └── Application.java # 启动类(各子模块各自持有) ``` **规则**: - 各子服务的 Controller 和 Service 放在各自模块的 `src/main/java/com/wangpan/` 下 - 公共的 DO、Mapper、Repository、Entity、Util 放在 `wangpan-common` 中 - 所有 Mapper XML 文件统一放在 `wangpan-common/src/main/resources/com/wangpan/common/dal/mapper/` 下 ### 2.2 类命名 | 类型 | 命名规则 | 示例 | |------|---------|------| | DO(数据对象) | `{实体名}DO` | `DriveMovieDO`, `UserDO`, `MovieDO` | | Mapper 接口 | `{实体名}Mapper` | `DriveMovieMapper`, `UserMapper` | | Repository | `{实体名}Repository` | `DriveMovieRepository`, `UserRepository` | | Entity(业务实体) | `{实体名}` | `DriveMovie`, `User`, `Movie` | | Controller | `{模块名}Controller` | `DriveController`, `AuthController`, `TmdbController` | | Service | `{模块名}Service` | `DriveService`, `UserService`, `TmdbService` | | 工具类 | `{功能}Util` | `TokenUtil`, `MinioUtil`, `AuthUtil`, `EncryptUtil` | | 配置类 | `{功能}Config` | `WebMvcConfig`, `MyBatisPlusConfig` | | 启动类 | `{模块名}Application` | `UserServiceApplication`, `DriveServiceApplication` | | 拦截器 | `{功能}Interceptor` | `AuthInterceptor` | | 定时任务 | `{功能}Scheduler` | `TmdbScheduler`, `IconCleanupScheduler` | ### 2.3 方法命名 | 操作 | 命名规则 | 示例 | |------|---------|------| | 查询单个 | `findBy{条件}` | `findById`, `findByMovieId` | | 查询列表 | `queryBy{条件}` | `queryByUserId`, `queryByDriveAndFolder` | | 统计数量 | `countBy{条件}` | `countByDrive`, `countByUser` | | 插入 | `insert` | `insert(DriveMovie movie)` | | 更新 | `update` / `update{字段}` | `update`, `updateScrapeInfo` | | 逻辑删除 | `logicalDelete` | `logicalDelete(Long id, Long userId)` | | 物理删除 | `delete` | `delete(Long id, Long userId)` | | 移动 | `moveTo{目标}` | `moveToFolder` | ### 2.4 数据库命名 | 元素 | 命名规则 | 示例 | |------|---------|------| | 表名 | `wp_{模块}_{实体}` | `wp_drive_movie`, `wp_user`, `wp_tmdb_cache` | | 字段名 | 小写 + 下划线分隔 | `drive_id`, `user_id`, `create_time`, `movie_title` | | 主键 | `id`(BIGINT AUTO_INCREMENT) | `id` | | 索引 | `idx_{字段名}` | `idx_drive_id`, `idx_user_id`, `idx_status` | | 状态字段 | 字符串枚举值 | `'active'`, `'deleted'`, `'expired'` | ### 2.5 API 命名 | 模块 | 路径前缀 | 示例 | |------|---------|------| | 用户服务 | `/api/login`, `/api/register`, `/api/token/**`, `/api/users/**`, `/api/login-logs/**` | `POST /api/login` | | 网盘服务 | `/api/drive/**`, `/api/icon/**` | `GET /api/drive/movies/duplicates` | | TMDB服务 | `/api/movies/**`, `/api/tmdb/**`, `/api/tmdb-scraper/**` | `GET /api/movies/{id}` | --- ## 三、技术架构模式 ### 3.1 分层架构 ``` Controller → 接收请求、参数校验、调用 Service、返回 Result ↓ Service → 业务逻辑处理、事务管理 ↓ Repository → 数据访问封装、Entity ↔ DO 转换、MyBatis-Plus LambdaQueryWrapper 查询 ↓ Mapper → MyBatis-Plus BaseMapper 接口 + XML 复杂 SQL ↓ DO → 数据库表映射(@TableName 注解) ``` **关键规则**: - Controller 直接返回 `Result` 对象,不要直接返回原始数据 - Repository 继承 `ServiceImpl`,自动获得 MyBatis-Plus CRUD 能力 - Mapper 继承 `BaseMapper`,简单 CRUD 自动生成 - 复杂 SQL 写在 Mapper XML 中,方法签名定义在 Mapper 接口中 - Repository 负责 Entity ↔ DO 转换,对外暴露 Entity 或 Map 类型 ### 3.2 Result 统一响应 ```java // 成功 Result.success("操作成功", data); // 各类错误 Result.badRequest("参数错误"); // 400 Result.unauthorized("登录已过期"); // 401 Result.forbidden("权限不足"); // 403 Result.notFound("资源不存在"); // 404 Result.tooManyRequests("请求过于频繁"); // 429 Result.error("服务器错误"); // 500 Result.error(code, "自定义错误"); // 自定义状态码 ``` ### 3.3 认证模式 - 使用 `@RequireAuth` 注解标记需要登录的接口 - 使用 `@RequireAdmin` 注解标记需要管理员权限的接口 - `AuthInterceptor` 拦截器统一处理认证逻辑 - Controller 通过 `request.getAttribute("currentUser")` 获取当前用户 - Token 使用 `TokenUtil` 生成和验证 ### 3.4 MyBatis-Plus 查询模式 ```java // 简单查询 → LambdaQueryWrapper var wrapper = new LambdaQueryWrapper() .eq(DriveMovieDO::getDriveId, driveId) .eq(DriveMovieDO::getUserId, userId) .eq(DriveMovieDO::getStatus, "active") .orderByDesc(DriveMovieDO::getCreateTime); // 分页查询 var page = new Page(pageNo, pageSize); var result = page(page, wrapper); // 更新 → LambdaUpdateWrapper var wrapper = new LambdaUpdateWrapper() .eq(DriveMovieDO::getId, id) .set(DriveMovieDO::getStatus, "deleted"); ``` ### 3.5 Jasypt 配置加密模式 所有服务(必须一致): ```yaml jasypt: encryptor: password: ${JASYPT_ENCRYPTOR_PASSWORD} # 必须通过环境变量传入 algorithm: PBEWITHHMACSHA512ANDAES_256 # 统一使用强加密算法 key-obtention-iterations: 1000 pool-size: 1 salt-generator-classname: org.jasypt.salt.RandomSaltGenerator iv-generator-classname: org.jasypt.iv.RandomIvGenerator string-output-type: base64 ``` **加密密钥**:`wangpan-secret-key-2024` **启动命令**(必须带 Jasypt 密钥参数): ```bash java -DJASYPT_ENCRYPTOR_PASSWORD=wangpan-secret-key-2024 -jar xxx.jar ``` ### 3.6 MinIO 存储模式 - `MinioUtil` 作为 Spring Bean,使用 `@PostConstruct` 初始化 - 支持静态方法调用和 Spring Bean 注入两种方式 - 预签名 URL 有效期:7 天 - 存储桶按服务区分:`wangpan`(网盘服务)、`tmdb`(TMDB服务) ### 3.7 定时任务模式 ```java @Component public class TmdbScheduler { @Scheduled(fixedDelay = 6 * 60 * 60 * 1000, initialDelay = 5 * 60 * 1000) public void executeScrape() { ... } } ``` --- ## 四、MyBatis XML 映射规则(重要!) ### 4.1 列别名规则 XML 中 `SELECT` 返回的列别名 **必须与 Java 代码中 `Map.get()` 使用的 key 完全一致**: ```xml ``` ```java // Java 代码中必须使用相同的 key g.get("cnt") // ✅ 正确 - 与 XML 别名 cnt 一致 g.get("count") // ❌ 错误 - 别名是 cnt 不是 count ``` ### 4.2 数值类型转换 XML 中 `resultType="java.util.Map"` 返回的数值,Java 中获取时需要安全转换: ```java // ✅ 推荐:使用 Number 父类转换 ((Number) g.get("cnt")).intValue() // ❌ 不推荐:直接强制转换可能失败 (int) g.get("cnt") ``` ### 4.3 分页查询 XML 中使用 `LIMIT #{pageSize} OFFSET #{pageNo}` 时,分页偏移量计算规则: ```java // 前端传 pageNo 从 1 开始,XML 中 offset = (pageNo - 1) * pageSize // 或者前端直接传 offset 值 ``` --- ## 五、已发现的问题与解决方案 ### 问题 1:SQL 列别名与 Java Map key 不匹配导致 NPE **日期**:2026-06-15 **问题描述**:调用 `GET /api/drive/movies/duplicates` 返回 500,报错: ``` java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null ``` **根因**:`DriveMovieMapper.xml` 中 SQL 别名是 `cnt`,但 `DriveController.java` 中使用的 key 是 `count`。 **解决方案**: 1. 将 Controller 中的 `g.get("count")` 改为 `g.get("cnt")` 2. 使用安全转换 `((Number) g.get("cnt")).intValue()` 替代 `(int) g.get("cnt")` **影响文件**: - `wangpan-drive-service/.../controller/DriveController.java` 第 676 行 - `wangpan-common/.../mapper/DriveMovieMapper.xml` 第 10 行(SQL 别名 `cnt`) **教训**:修改 XML 中的 SQL 列别名时,必须同步修改所有引用该结果集的 Java 代码中的 Map key。 ### 问题 2:网关 Jasypt 加密算法不一致 **日期**:2026-06-15 **问题描述**:网关服务使用 `PBEWithMD5AndDES` 算法,其他服务使用 `PBEWITHHMACSHA512ANDAES_256`,不一致且 MD5+DES 算法已过时。 **解决方案**:统一所有服务使用 `PBEWITHHMACSHA512ANDAES_256`。 **影响文件**:`wangpan-gateway/src/main/resources/application.yml` ### 问题 3:硬编码默认 Jasypt 密钥 **日期**:2026-06-15 **问题描述**:所有服务配置了 `${JASYPT_ENCRYPTOR_PASSWORD:wangpan-secret-key-2024}` 默认值,存在安全风险。 **解决方案**:移除默认值,改为 `${JASYPT_ENCRYPTOR_PASSWORD}`,启动时强制通过命令行参数 `-DJASYPT_ENCRYPTOR_PASSWORD=xxx` 传入。 **影响文件**:所有服务的 `application.yml` ### 问题 4:日志级别过高 **日期**:2026-06-15 **问题描述**:所有服务日志级别为 `DEBUG`,生产环境会产生大量日志。 **解决方案**:改为 `INFO` 级别。 ### 问题 5:MyBatis-Plus SQL 日志输出到控制台 **日期**:2026-06-15 **问题描述**:配置了 `log-impl: org.apache.ibatis.logging.stdout.StdOutImpl`,生产环境会打印所有 SQL。 **解决方案**:移除该配置。 ### 问题 6:启动时 Jasypt 环境变量未传递 **日期**:2026-06-15 **问题描述**:使用 `$env:JASYPT_ENCRYPTOR_PASSWORD="xxx"` + `Start-Process` 启动服务时,子进程未继承环境变量,导致 `Failed to bind properties under 'spring.datasource.password'`。 **解决方案**:使用 `-DJASYPT_ENCRYPTOR_PASSWORD=xxx` 命令行参数替代环境变量。 ### 问题 7:TMDB 刮削查询了错误的电影表 **日期**:2026-06-18 **问题描述**:TMDB 刮削器(`MovieMapper`/`MovieDO`/`MovieRepository`)映射的是 `wp_movie` 表,但项目中实际使用的电影表是 `wp_drive_movie`(由 drive-service 管理)。`wp_movie` 表无数据,导致刮削始终返回"没有需要刮削的电影"。 **根因**:项目中存在两张结构相同的电影表(`wp_movie` 和 `wp_drive_movie`),但实际只有 `wp_drive_movie` 有数据。 **解决方案**: 1. 将 `MovieDO` 的 `@TableName` 从 `wp_movie` 改为 `wp_drive_movie` 2. 将所有 `MovieMapper` 的 SQL(含 XML 和注解)中的 `wp_movie` 改为 `wp_drive_movie` 3. 将 `TmdbCacheMapper.xml` 中 JOIN 的 `wp_movie` 改为 `wp_drive_movie` 4. 移除 `MovieMapper.CREATE_TABLE_SQL`(`wp_drive_movie` 由 `DriveMovieMapper` 负责建表) 5. 从 tmdb-service 的 `DatabaseInitializer` 中移除 `wp_movie` 表创建 6. 修改 `findMoviesNeedingScrape` 查询条件,移除 `scrape_type != 'none'` 限制,允许未刮削的电影被选中 **影响文件**: - `wangpan-common/.../dataobject/MovieDO.java` — `@TableName` 改为 `wp_drive_movie` - `wangpan-common/.../mapper/MovieMapper.java` — 移除 `CREATE_TABLE_SQL`,SQL 改表名 - `wangpan-common/.../mapper/MovieMapper.xml` — SQL 表名改为 `wp_drive_movie`,放开 `scrape_type` 限制 - `wangpan-common/.../mapper/TmdbCacheMapper.xml` — JOIN 表名改为 `wp_drive_movie` - `wangpan-common/.../mapper/TmdbCacheMapper.java` — 注释更新 - `wangpan-common/.../entity/Movie.java` — 注释更新 - `wangpan-tmdb-service/.../config/DatabaseInitializer.java` — 移除 `wp_movie` 建表 - `wangpan-tmdb-service/.../service/TmdbScraperService.java` — 注释更新 **教训**:项目中统一使用 `wp_drive_movie` 作为电影表,所有模块(drive-service、tmdb-service)都应操作同一张表。`wp_movie` 表已被废弃,不应再使用。 --- ## 六、配置文件关键信息 ### 6.1 数据库连接 - 地址:`118.178.238.159:33306` - 数据库名:`wangpan` - 连接参数:`useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true` - 所有密码使用 Jasypt `ENC(...)` 加密 ### 6.2 MinIO 连接 - 地址:`118.178.238.159:39000` - 访问密钥和秘密密钥使用 Jasypt `ENC(...)` 加密 - 存储桶:`wangpan`(网盘服务)、`tmdb`(TMDB服务) ### 6.3 网关路由规则 | 路由路径 | 转发目标 | |---------|---------| | `/api/login`, `/api/register`, `/api/token/**`, `/api/login-logs/**`, `/api/users/**` | `http://localhost:8081` | | `/api/drive/**`, `/api/icon/**` | `http://localhost:8082` | | `/api/movies/**`, `/api/tmdb/**`, `/api/tmdb-scraper/**` | `http://localhost:8083` | 所有路由 `StripPrefix=0`(不裁剪路径前缀)。 --- ## 七、TraeAI 操作规则 ### 7.1 每次修改代码前必须执行 1. **读取本文件**(`PROJECT_REFERENCE.md`)了解项目规范 2. 查找相关代码,理解现有模式后再修改 3. 修改代码时遵循项目命名规范 ### 7.2 修改规范 1. **禁止**创建不必要的新文件,优先编辑现有文件 2. **禁止**创建文档(*.md)除非用户明确要求 3. 修改 SQL 列别名时,**必须同步检查**所有 Java 代码中对 Map key 的引用 4. 修改配置文件时,**确保所有服务配置一致**(Jasypt 算法、日志级别等) 5. 使用 `Result` 类统一返回,不要返回原始类型 6. 数据库操作使用 MyBatis-Plus LambdaQueryWrapper/LambdaUpdateWrapper,不要拼接 SQL 字符串 7. 复杂 SQL 写在 Mapper XML 中,不要写在 Java 代码中 ### 7.3 编译与启动 ```bash # 编译 mvn clean install -DskipTests # 启动单个服务 java -DJASYPT_ENCRYPTOR_PASSWORD=wangpan-secret-key-2024 -jar xxx\target\xxx-1.0-SNAPSHOT.jar ``` ### 7.4 遇到新问题时 在本文档 **第五章** 中追加新问题记录,包含: - 日期 - 问题描述 - 根因 - 解决方案 - 影响文件 - 教训 --- ## 八、MyBatis-Plus 配置要点 ```yaml mybatis-plus: mapper-locations: classpath*:com/wangpan/common/dal/mapper/**/*.xml type-aliases-package: com.wangpan.common.dal.dataobject configuration: map-underscore-to-camel-case: true # 自动驼峰映射 global-config: db-config: id-type: auto # 主键自增 ``` **注意**:`mapper-locations` 使用 `classpath*:` 前缀,因为 Mapper XML 在 `wangpan-common` 模块中,所有子服务都需要加载。 --- ## 九、api-doc.html 维护规范 ### 9.1 文件位置 `wangpan-gateway/src/main/resources/META-INF/resources/api-doc.html` ### 9.2 历史不一致问题记录 **问题 8:api-doc.html 与代码多处不一致** **日期**:2026-06-18 **发现的不一致**: | 接口 | 文档描述 | 实际代码 | 严重程度 | |------|---------|---------|---------| | `GET /api/movies` 查询参数 `status` | `pending / scraped / failed` | `active / deleted`(status 是记录状态,scrapeType 是刮削状态) | **高** | | `GET /api/movies` 缺少参数 | 无 `sortBy`/`sortOrder` | 支持 `sortBy` 和 `sortOrder` 排序参数 | 中 | | `POST /api/movies` 请求体 | 只有 `{ "title": "..." }` | 支持 title, subtitle, resourceLibrary, maintainer, status, category, scrapeType, rating, posterUrl, tags | **高** | | `PUT /api/movies/{id}` 请求体 | 空白 | 支持全部字段的部分更新 | **高** | | `GET /api/movies/stats` 响应 | 空白 | 返回 totalCount, scrapedCount, pendingCount | 中 | | `POST /api/tmdb-scraper/scrape` | 仅请求体方式 | 同时支持 URL 参数 `?movieId=1` 和请求体 | 中 | | `POST /api/tmdb-scraper/batch-scrape` | `movieIds` 为必填 | `movieIds` 可选,不传则自动查找 | 中 | | 多个 `/api/tmdb-scraper/*` 端点 | 响应文档空白 | stats, trigger, scraper-logs, cache delete 等缺少响应 | 中 | | 路由表缺少 `/api/icon/**` | 未列出 | `/api/icon/**` → drive-service | 低 | **解决方案**:逐一修复 api-doc.html 中所有不一致的字段、参数和响应示例。 **教训**:每次新增或修改 Controller 接口时,必须同步更新 api-doc.html。 --- > **最后更新**:2026-06-18