diff --git a/PROJECT_REFERENCE.md b/PROJECT_REFERENCE.md new file mode 100644 index 0000000..17a0652 --- /dev/null +++ b/PROJECT_REFERENCE.md @@ -0,0 +1,475 @@ +# 网盘系统 (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 \ No newline at end of file diff --git a/wangpan-common/src/main/java/com/wangpan/common/dal/dataobject/MovieDO.java b/wangpan-common/src/main/java/com/wangpan/common/dal/dataobject/MovieDO.java index 08137dc..5588411 100644 --- a/wangpan-common/src/main/java/com/wangpan/common/dal/dataobject/MovieDO.java +++ b/wangpan-common/src/main/java/com/wangpan/common/dal/dataobject/MovieDO.java @@ -10,13 +10,14 @@ import java.time.LocalDateTime; /** * 电影数据对象(Data Object) - * 对应数据库表:wp_movie + * 对应数据库表:wp_drive_movie(网盘电影表) * * 设计说明: * - DO 字段名使用 Java 驼峰命名,MyBatis-Plus 自动映射为数据库 snake_case 列 * - DO 仅负责数据映射,不包含业务逻辑 + * - 注意:本 DO 仅映射 wp_drive_movie 表中 TMDB 刮削相关的字段,其他字段由 DriveMovieDO 映射 */ -@TableName(value = "wp_movie", autoResultMap = true) +@TableName(value = "wp_drive_movie", autoResultMap = true) public class MovieDO implements Serializable { private static final long serialVersionUID = 1L; diff --git a/wangpan-common/src/main/java/com/wangpan/common/dal/mapper/MovieMapper.java b/wangpan-common/src/main/java/com/wangpan/common/dal/mapper/MovieMapper.java index 6e3478b..5e8389f 100644 --- a/wangpan-common/src/main/java/com/wangpan/common/dal/mapper/MovieMapper.java +++ b/wangpan-common/src/main/java/com/wangpan/common/dal/mapper/MovieMapper.java @@ -18,41 +18,6 @@ import java.util.Map; @Mapper public interface MovieMapper extends BaseMapper { - /** 建表SQL(保留供 DatabaseInitializer 使用) */ - String CREATE_TABLE_SQL = """ - CREATE TABLE IF NOT EXISTS wp_movie ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '电影ID', - drive_id BIGINT NOT NULL COMMENT '所属网盘ID', - folder_id BIGINT NOT NULL DEFAULT 0 COMMENT '所属文件夹ID(0=根目录)', - user_id BIGINT NOT NULL COMMENT '所属用户ID', - title VARCHAR(300) NOT NULL COMMENT '电影标题', - subtitle VARCHAR(300) DEFAULT NULL COMMENT '电影副标题', - original_name VARCHAR(300) DEFAULT NULL COMMENT '原始文件名', - minio_object_name VARCHAR(500) DEFAULT NULL COMMENT 'MinIO对象名称', - minio_url TEXT DEFAULT NULL COMMENT 'MinIO访问URL(加密存储)', - mime_type VARCHAR(100) DEFAULT NULL COMMENT 'MIME类型', - file_size BIGINT NOT NULL DEFAULT 0 COMMENT '文件大小(字节)', - extension VARCHAR(20) DEFAULT NULL COMMENT '文件扩展名', - description TEXT DEFAULT NULL COMMENT '电影描述', - resource_library VARCHAR(100) DEFAULT NULL COMMENT '资源库来源', - maintainer VARCHAR(100) DEFAULT NULL COMMENT '维护者', - status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态:active/deleted', - category VARCHAR(50) DEFAULT NULL COMMENT '分类', - scrape_type VARCHAR(20) DEFAULT 'none' COMMENT '刮削类型:tmdb/manual/none', - rating INT DEFAULT NULL COMMENT '评分(0-100)', - poster_url TEXT DEFAULT NULL COMMENT '海报URL(加密存储)', - tags VARCHAR(500) DEFAULT NULL COMMENT '标签(逗号分隔)', - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - INDEX idx_drive_id (drive_id), - INDEX idx_user_id (user_id), - INDEX idx_folder_id (drive_id, folder_id), - INDEX idx_status (status), - INDEX idx_rating (rating), - INDEX idx_scrape_type (scrape_type) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='电影表' - """; - /** * 查找用户所有网盘下的重复电影(按 title 分组) * 复杂SQL,在XML中实现 @@ -74,24 +39,24 @@ public interface MovieMapper extends BaseMapper { /** * 更新电影评分 */ - @Update("UPDATE wp_movie SET rating = #{rating}, update_time = NOW() WHERE id = #{id}") + @Update("UPDATE wp_drive_movie SET rating = #{rating}, update_time = NOW() WHERE id = #{id}") int updateRating(@Param("id") Long id, @Param("rating") Integer rating); /** * 更新电影海报URL */ - @Update("UPDATE wp_movie SET poster_url = #{posterUrl}, update_time = NOW() WHERE id = #{id}") + @Update("UPDATE wp_drive_movie SET poster_url = #{posterUrl}, update_time = NOW() WHERE id = #{id}") int updatePosterUrl(@Param("id") Long id, @Param("posterUrl") String posterUrl); /** * 更新电影标签 */ - @Update("UPDATE wp_movie SET tags = #{tags}, update_time = NOW() WHERE id = #{id}") + @Update("UPDATE wp_drive_movie SET tags = #{tags}, update_time = NOW() WHERE id = #{id}") int updateTags(@Param("id") Long id, @Param("tags") String tags); /** * 更新刮削类型标记 */ - @Update("UPDATE wp_movie SET scrape_type = #{scrapeType}, update_time = NOW() WHERE id = #{id}") + @Update("UPDATE wp_drive_movie SET scrape_type = #{scrapeType}, update_time = NOW() WHERE id = #{id}") int updateScrapeType(@Param("id") Long id, @Param("scrapeType") String scrapeType); } diff --git a/wangpan-common/src/main/java/com/wangpan/common/dal/mapper/TmdbCacheMapper.java b/wangpan-common/src/main/java/com/wangpan/common/dal/mapper/TmdbCacheMapper.java index ba74b4b..284c48b 100644 --- a/wangpan-common/src/main/java/com/wangpan/common/dal/mapper/TmdbCacheMapper.java +++ b/wangpan-common/src/main/java/com/wangpan/common/dal/mapper/TmdbCacheMapper.java @@ -22,7 +22,7 @@ public interface TmdbCacheMapper extends BaseMapper { String CREATE_TABLE_SQL = """ CREATE TABLE IF NOT EXISTS wp_tmdb_cache ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '记录ID', - movie_id BIGINT NOT NULL COMMENT '关联wp_movie表的电影ID', + movie_id BIGINT NOT NULL COMMENT '关联wp_drive_movie表的电影ID', user_id BIGINT NOT NULL COMMENT '关联用户ID', tmdb_id INT DEFAULT NULL COMMENT 'TMDB电影ID', title VARCHAR(200) NOT NULL COMMENT '电影标题(来自TMDB)', diff --git a/wangpan-common/src/main/java/com/wangpan/common/dal/repository/DriveMovieRepository.java b/wangpan-common/src/main/java/com/wangpan/common/dal/repository/DriveMovieRepository.java index 3b99baf..eafeca9 100644 --- a/wangpan-common/src/main/java/com/wangpan/common/dal/repository/DriveMovieRepository.java +++ b/wangpan-common/src/main/java/com/wangpan/common/dal/repository/DriveMovieRepository.java @@ -146,16 +146,17 @@ public class DriveMovieRepository extends ServiceImpl() .eq(DriveMovieDO::getId, id) - .set(DriveMovieDO::getTitle, title) - .set(DriveMovieDO::getSubtitle, subtitle) - .set(DriveMovieDO::getDescription, description) - .set(DriveMovieDO::getRating, rating) - .set(DriveMovieDO::getTags, tags) - .set(DriveMovieDO::getCategory, category) + .set(title != null, DriveMovieDO::getTitle, title) + .set(originalName != null, DriveMovieDO::getOriginalName, originalName) + .set(subtitle != null, DriveMovieDO::getSubtitle, subtitle) + .set(description != null, DriveMovieDO::getDescription, description) + .set(rating != null, DriveMovieDO::getRating, rating) + .set(tags != null, DriveMovieDO::getTags, tags) + .set(category != null, DriveMovieDO::getCategory, category) .set(DriveMovieDO::getUpdateTime, LocalDateTime.now()); return update(wrapper); } @@ -287,7 +288,7 @@ public class DriveMovieRepository extends ServiceImpl toMap(DriveMovieDO movieDO) { var map = new LinkedHashMap(); @@ -308,8 +309,10 @@ public class DriveMovieRepository extends ServiceImpl { return remove(wrapper); } + // ==================== 查询所有在用 logoUrl ==================== + + /** + * 查询所有 Icon 的 logoUrl(用于清理无用图片) + * + * @return 所有在用 logoUrl 列表(去重) + */ + public List findAllLogoUrls() { + var wrapper = new LambdaQueryWrapper() + .select(IconDO::getLogoUrl) + .isNotNull(IconDO::getLogoUrl) + .ne(IconDO::getLogoUrl, ""); + + var list = list(wrapper); + return list.stream() + .map(IconDO::getLogoUrl) + .filter(url -> url != null && !url.isEmpty()) + .distinct() + .toList(); + } + // ==================== Entity ↔ DO 转换 ==================== private IconDO toIconDO(Icon icon) { diff --git a/wangpan-common/src/main/java/com/wangpan/common/entity/Movie.java b/wangpan-common/src/main/java/com/wangpan/common/entity/Movie.java index 386eafb..36f014b 100644 --- a/wangpan-common/src/main/java/com/wangpan/common/entity/Movie.java +++ b/wangpan-common/src/main/java/com/wangpan/common/entity/Movie.java @@ -6,7 +6,7 @@ import java.util.Objects; /** * 电影实体类 - * 对应数据库表:wp_movie(用户电影库表) + * 对应数据库表:wp_drive_movie(网盘电影表) * * 功能: * 1. 封装电影的基本信息(标题、评分、海报等) diff --git a/wangpan-common/src/main/java/com/wangpan/common/util/MinioUtil.java b/wangpan-common/src/main/java/com/wangpan/common/util/MinioUtil.java index a5959cd..ea4825a 100644 --- a/wangpan-common/src/main/java/com/wangpan/common/util/MinioUtil.java +++ b/wangpan-common/src/main/java/com/wangpan/common/util/MinioUtil.java @@ -186,6 +186,19 @@ public class MinioUtil { return instance != null ? instance.bucketName : null; } + /** + * 删除 MinIO 中的文件(静态方法) + * + * @param objectName 对象名称(文件路径) + * @return 删除成功返回 true,失败返回 false + */ + public static boolean deleteFile(String objectName) { + if (instance == null) { + throw new IllegalStateException("MinioUtil未初始化,请确保Spring容器已启动"); + } + return instance.doDeleteFile(objectName); + } + // ==================== 内部实现方法 ==================== private String doUpload(InputStream inputStream, String objectName, String contentType) { @@ -238,4 +251,25 @@ public class MinioUtil { return endpoint + "/" + bucketName + "/" + objectName; } } + + /** + * 删除 MinIO 中的文件(内部实现) + */ + private boolean doDeleteFile(String objectName) { + try { + getClient().removeObject( + RemoveObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .build() + ); + + LOGGER.info("[MinioUtil] 文件删除成功: " + objectName); + return true; + + } catch (Exception e) { + LOGGER.log(Level.WARNING, "[MinioUtil] 文件删除失败: " + objectName, e); + return false; + } + } } diff --git a/wangpan-common/src/main/resources/com/wangpan/common/dal/mapper/MovieMapper.xml b/wangpan-common/src/main/resources/com/wangpan/common/dal/mapper/MovieMapper.xml index c3b6589..16b76fb 100644 --- a/wangpan-common/src/main/resources/com/wangpan/common/dal/mapper/MovieMapper.xml +++ b/wangpan-common/src/main/resources/com/wangpan/common/dal/mapper/MovieMapper.xml @@ -8,7 +8,7 @@ - + diff --git a/wangpan-common/src/main/resources/com/wangpan/common/dal/mapper/TmdbCacheMapper.xml b/wangpan-common/src/main/resources/com/wangpan/common/dal/mapper/TmdbCacheMapper.xml index 4072efb..0ab6997 100644 --- a/wangpan-common/src/main/resources/com/wangpan/common/dal/mapper/TmdbCacheMapper.xml +++ b/wangpan-common/src/main/resources/com/wangpan/common/dal/mapper/TmdbCacheMapper.xml @@ -19,7 +19,7 @@