Răsfoiți Sursa

first commit

ChenYu 7 luni în urmă
comite
81e02eb49a
100 a modificat fișierele cu 6805 adăugiri și 0 ștergeri
  1. BIN
      .DS_Store
  2. 6 0
      .gitignore
  3. 28 0
      Dockerfile
  4. 137 0
      README.md
  5. 78 0
      doc/ADMIN_API.md
  6. 185 0
      doc/AI_ALBUM_API.md
  7. 198 0
      doc/AI_ORDER_V2_API.md
  8. 120 0
      doc/AI_SUBSCRIBE_API.md
  9. 98 0
      doc/LOGIN_API.md
  10. 159 0
      doc/MODULE_API.md
  11. 116 0
      doc/POINTS_CONFIG_API.md
  12. 128 0
      doc/POINTS_PRODUCT_API.md
  13. 266 0
      doc/REDEMPTION_CODE_API.md
  14. 59 0
      doc/STORAGE_API.md
  15. 169 0
      doc/STYLE_API.md
  16. 148 0
      doc/SUBSCRIBE_ACTION_API.md
  17. 139 0
      doc/TAG_API.md
  18. 89 0
      doc/USER_API.md
  19. 78 0
      doc/USER_FEEDBACK_API.md
  20. 88 0
      doc/USER_INVITE_API.md
  21. 89 0
      doc/USER_MESSAGE_API.md
  22. 78 0
      doc/USER_POINTS_ORDER_API.md
  23. 82 0
      doc/USER_POINTS_RECORD_API.md
  24. 108 0
      doc/USER_POINTS_STATEMENT_API.md
  25. 84 0
      doc/USER_POINTS_USAGE_API.md
  26. 82 0
      doc/USER_REDEMPTION_RECORD_API.md
  27. 88 0
      doc/USER_SUBSCRIBE_ORDER_API.md
  28. 257 0
      docs/dashboard-api.md
  29. 252 0
      pom.xml
  30. 13 0
      src/main/java/com/aitori/Application.java
  31. 11 0
      src/main/java/com/aitori/annotation/LogRecord.java
  32. 15 0
      src/main/java/com/aitori/annotation/SkipLogRecord.java
  33. 15 0
      src/main/java/com/aitori/annotation/SkipResponseWrapper.java
  34. 54 0
      src/main/java/com/aitori/aop/log/ForestLogHandler.java
  35. 35 0
      src/main/java/com/aitori/aop/log/LogAspect.java
  36. 234 0
      src/main/java/com/aitori/aop/log/LogHandler.java
  37. 41 0
      src/main/java/com/aitori/aop/log/LogInfo.java
  38. 19 0
      src/main/java/com/aitori/client/AuthClient.java
  39. 22 0
      src/main/java/com/aitori/client/HealthModelClient.java
  40. 88 0
      src/main/java/com/aitori/config/GlobalResponseWrapper.java
  41. 50 0
      src/main/java/com/aitori/config/JacksonConfig.java
  42. 152 0
      src/main/java/com/aitori/config/Knife4jOpenApiCustomizer.java
  43. 20 0
      src/main/java/com/aitori/config/MybatisPlusConfig.java
  44. 78 0
      src/main/java/com/aitori/config/RedisConfig.java
  45. 27 0
      src/main/java/com/aitori/config/SchedulerConfig.java
  46. 58 0
      src/main/java/com/aitori/config/VirtualThreadConfig.java
  47. 25 0
      src/main/java/com/aitori/config/WebMvcConfig.java
  48. 32 0
      src/main/java/com/aitori/constants/CacheConstants.java
  49. 55 0
      src/main/java/com/aitori/controller/DashboardController.java
  50. 50 0
      src/main/java/com/aitori/controller/LoginController.java
  51. 32 0
      src/main/java/com/aitori/controller/StorageController.java
  52. 32 0
      src/main/java/com/aitori/controller/TAiAlbumController.java
  53. 51 0
      src/main/java/com/aitori/controller/TAiModuleController.java
  54. 34 0
      src/main/java/com/aitori/controller/TAiOrderV2Controller.java
  55. 61 0
      src/main/java/com/aitori/controller/TAiStyleController.java
  56. 45 0
      src/main/java/com/aitori/controller/TAiSubscribeController.java
  57. 55 0
      src/main/java/com/aitori/controller/TAiTagController.java
  58. 45 0
      src/main/java/com/aitori/controller/TPointsConfigController.java
  59. 45 0
      src/main/java/com/aitori/controller/TPointsProductController.java
  60. 55 0
      src/main/java/com/aitori/controller/TRedemptionCodeController.java
  61. 49 0
      src/main/java/com/aitori/controller/TSubscribeActionController.java
  62. 43 0
      src/main/java/com/aitori/controller/TSurveyController.java
  63. 32 0
      src/main/java/com/aitori/controller/TUserController.java
  64. 29 0
      src/main/java/com/aitori/controller/TUserFeedbackController.java
  65. 29 0
      src/main/java/com/aitori/controller/TUserInviteRecordController.java
  66. 29 0
      src/main/java/com/aitori/controller/TUserMessageController.java
  67. 29 0
      src/main/java/com/aitori/controller/TUserPointsOrderController.java
  68. 29 0
      src/main/java/com/aitori/controller/TUserPointsRecordController.java
  69. 15 0
      src/main/java/com/aitori/controller/TUserPointsUsageController.java
  70. 29 0
      src/main/java/com/aitori/controller/TUserRedemptionRecordController.java
  71. 29 0
      src/main/java/com/aitori/controller/TUserSubscribeOrderController.java
  72. 46 0
      src/main/java/com/aitori/controller/TUserSurveyController.java
  73. 57 0
      src/main/java/com/aitori/dto/R.java
  74. 41 0
      src/main/java/com/aitori/entity/SysUser.java
  75. 85 0
      src/main/java/com/aitori/entity/TAiAlbum.java
  76. 40 0
      src/main/java/com/aitori/entity/TAiModule.java
  77. 79 0
      src/main/java/com/aitori/entity/TAiModuleConfig.java
  78. 80 0
      src/main/java/com/aitori/entity/TAiOrderV2.java
  79. 166 0
      src/main/java/com/aitori/entity/TAiStyle.java
  80. 36 0
      src/main/java/com/aitori/entity/TAiSubscribe.java
  81. 41 0
      src/main/java/com/aitori/entity/TAiTag.java
  82. 43 0
      src/main/java/com/aitori/entity/TAiTagConfig.java
  83. 35 0
      src/main/java/com/aitori/entity/TPointsConfig.java
  84. 43 0
      src/main/java/com/aitori/entity/TPointsProduct.java
  85. 39 0
      src/main/java/com/aitori/entity/TRedemptionCode.java
  86. 45 0
      src/main/java/com/aitori/entity/TSubscribeAction.java
  87. 36 0
      src/main/java/com/aitori/entity/TSurvey.java
  88. 90 0
      src/main/java/com/aitori/entity/TUser.java
  89. 33 0
      src/main/java/com/aitori/entity/TUserFeedback.java
  90. 30 0
      src/main/java/com/aitori/entity/TUserInviteRecord.java
  91. 55 0
      src/main/java/com/aitori/entity/TUserMessage.java
  92. 40 0
      src/main/java/com/aitori/entity/TUserPointsOrder.java
  93. 39 0
      src/main/java/com/aitori/entity/TUserPointsRecord.java
  94. 42 0
      src/main/java/com/aitori/entity/TUserPointsUsage.java
  95. 39 0
      src/main/java/com/aitori/entity/TUserRedemptionRecord.java
  96. 42 0
      src/main/java/com/aitori/entity/TUserSubscribeOrder.java
  97. 51 0
      src/main/java/com/aitori/entity/TUserSurvey.java
  98. 42 0
      src/main/java/com/aitori/entity/message/WorkflowTaskMessage.java
  99. 45 0
      src/main/java/com/aitori/entity/request/AiAlbumQueryRequest.java
  100. 45 0
      src/main/java/com/aitori/entity/request/AiOrderV2QueryRequest.java

BIN
.DS_Store


+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+.idea/
+./out/
+target
+quantum/
+/.vscode/
+/logs/

+ 28 - 0
Dockerfile

@@ -0,0 +1,28 @@
+FROM eclipse-temurin:21-jre
+
+LABEL maintainer="chenyu"
+
+ENV TZ=Asia/Shanghai
+ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 \
+  -XX:+UseContainerSupport \
+  -XX:+HeapDumpOnOutOfMemoryError \
+  -XX:HeapDumpPath=/app/logs/heapdump.hprof"
+
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \
+    && apt-get update \
+    && apt-get install -y --no-install-recommends ffmpeg \
+    && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+
+RUN mkdir -p /app/logs \
+    && chown -R 1000:1000 /app
+
+COPY target/*.jar /app/app.jar
+RUN chown 1000:1000 /app/app.jar
+
+USER 1000
+
+EXPOSE 8080 8090
+
+ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

+ 137 - 0
README.md

@@ -0,0 +1,137 @@
+# Aitori BI V2
+
+Aitori 后台管理服务,基于 Spring Boot 3.5.5 + Java 21 构建,提供用户管理、AI 订单、相册、风格模版、积分体系、订阅配置等业务的后端 API,并通过 Redis Pub/Sub 监听工作流任务回调,实现异步结果处理。
+
+## 技术栈
+
+| 类别 | 技术 |
+|------|------|
+| 语言 / 框架 | Java 21 / Spring Boot 3.5.5 |
+| ORM | MyBatis-Plus 3.5.12 |
+| 认证 | Sa-Token 1.44.0 |
+| 数据库 | MySQL |
+| 缓存 / 消息 | Redis(Lettuce 连接池 + Pub/Sub) |
+| API 文档 | Knife4j 4.5.0 + SpringDoc OpenAPI 2.8.9 |
+| HTTP 客户端 | Forest 1.8.0 |
+| 分布式锁 | ShedLock 7.3.0 |
+| 对象存储 | Google Cloud Storage |
+| 视频处理 | FFmpeg(运行时依赖) |
+| 监控 | Micrometer + Prometheus |
+| 并发模型 | Java 21 虚拟线程 |
+
+## 项目结构
+
+```
+com.aitori
+├── annotation/        # 自定义注解(日志、响应包装控制)
+├── aop/log/           # AOP 操作日志切面
+├── client/            # 外部 HTTP 客户端接口
+├── config/            # 配置类(Redis、MyBatis-Plus、Knife4j、虚拟线程等)
+├── constants/         # 常量定义
+├── controller/        # REST API 控制器
+├── dto/               # 统一响应包装 R<T>
+├── entity/            # 实体、VO、Request 对象
+├── exception/         # 全局异常处理
+├── listener/          # Redis 消息监听器
+├── mapper/            # MyBatis-Plus Mapper 接口与 XML
+├── service/           # 业务逻辑接口与实现
+└── utils/             # 工具类(Google Storage、JWT、视频缩略图)
+```
+
+## API 模块
+
+| 模块 | 路径前缀 | 说明 |
+|------|----------|------|
+| 登录 | `/v2/admin/login` | 验证码、登录认证 |
+| Dashboard | `/v2/dashboard` | 数据总览、趋势、任务概览 |
+| 用户管理 | `/v2/user` | 用户列表、状态管理 |
+| 用户反馈 | `/v2/user/feedback` | 反馈列表 |
+| 用户消息 | `/v2/user/message` | 消息列表 |
+| 用户问卷 | `/v2/user/survey` | 问卷列表 |
+| 邀请记录 | `/v2/user/invite` | 邀请记录 |
+| 兑换记录 | `/v2/user/redemption/record` | 兑换记录 |
+| 积分流水 | `/v2/user/points/record` | 积分流水查询 |
+| 积分订单 | `/v2/user/points/order` | 积分订单 |
+| 订阅订单 | `/v2/user/subscribe/order` | 订阅订单 |
+| 订单 V2 | `/v2/ai/order/v2` | AI 订单管理 |
+| 相册 | `/v2/ai/album` | 相册管理 |
+| 订阅配置 | `/v2/ai/subscribe` | 订阅套餐 CRUD |
+| 模块管理 | `/v2/module` | 模块 CRUD + 排序 |
+| 标签管理 | `/v2/tag` | 标签 CRUD |
+| 风格管理 | `/v2/style` | 风格模版 CRUD |
+| 积分来源配置 | `/v2/points/config` | 积分来源 CRUD |
+| 积分产品 | `/v2/points/product` | 积分产品 CRUD |
+| 兑换码 | `/v2/redemption/code` | 兑换码 CRUD + 批量生成 |
+| 订阅广告 | `/v2/subscribe/action` | 订阅广告 CRUD |
+| 问卷 | `/v2/survey` | 问卷列表 |
+| 文件上传 | `/google/upload/img` | 上传至 Google Cloud Storage |
+
+## 环境变量
+
+| 变量名 | 必填 | 默认值 | 说明 |
+|--------|------|--------|------|
+| `ACTIVE_ENV` | 否 | `dev` | Spring Profile |
+| `MYSQL_HOST` | 否 | `127.0.0.1` | MySQL 地址 |
+| `MYSQL_PROT` | 否 | `3306` | MySQL 端口 |
+| `MYSQL_DATABASE` | 否 | `aitori` | 数据库名 |
+| `MYSQL_USERNAME` | 否 | `root` | 数据库用户 |
+| `MYSQL_PASSWORD` | 是 | - | 数据库密码 |
+| `REDIS_HOST` | 否 | `127.0.0.1` | Redis 地址 |
+| `REDIS_PORT` | 否 | `6379` | Redis 端口 |
+| `REDIS_PASSWORD` | 是 | - | Redis 密码 |
+| `KNIFE4J_PASSWORD` | 否 | `Goodsense1` | API 文档访问密码 |
+
+## 本地开发
+
+### 前置依赖
+
+- JDK 21+
+- Maven 3.9+
+- MySQL 8.x
+- Redis 7.x
+- FFmpeg(用于视频缩略图生成)
+
+### 启动
+
+```bash
+# 编译打包
+mvn clean package -DskipTests
+
+# 运行
+java -jar target/aitori-bi-v2-1.2.1.jar
+```
+
+服务启动后:
+- 应用端口:`8080`
+- 管理端口:`8090`(Actuator / Prometheus)
+- API 文档:`http://localhost:8080/doc.html`
+
+## Docker 部署
+
+```bash
+# 构建镜像
+docker build -t aitori-bi-v2 .
+
+# 运行容器
+docker run -d \
+  -p 8080:8080 \
+  -p 8090:8090 \
+  -e MYSQL_HOST=your-mysql-host \
+  -e MYSQL_PASSWORD=your-password \
+  -e REDIS_HOST=your-redis-host \
+  -e REDIS_PASSWORD=your-redis-password \
+  aitori-bi-v2
+```
+
+## API 文档
+
+详细接口文档位于 `doc/` 目录下,按模块拆分:
+
+- `LOGIN_API.md` - 登录认证
+- `USER_API.md` - 用户管理
+- `AI_ORDER_V2_API.md` - AI 订单 V2
+- `AI_ALBUM_API.md` - 相册管理
+- `STYLE_API.md` - 风格管理
+- `MODULE_API.md` - 模块管理
+- `TAG_API.md` - 标签管理
+- 更多文档请参阅 `doc/` 目录

+ 78 - 0
doc/ADMIN_API.md

@@ -0,0 +1,78 @@
+# 后台管理 API 文档
+
+本文档描述了新增的后台管理相关接口,用于查看用户反馈、积分、订单等数据。
+
+## 1. 全局说明
+
+*   **基础路径**: `/v2`
+*   **响应格式**: 统一使用 `GlobalResponseWrapper`
+*   **通用查询参数**:
+    *   `page`: 页码 (默认 1)
+    *   `size`: 每页大小 (默认 10)
+    *   `userId`: 用户ID (可选)
+    *   `keyword`: 搜索关键字 (可选,具体匹配字段见接口说明)
+    *   `startTime`: 开始时间 (yyyy-MM-dd HH:mm:ss)
+    *   `endTime`: 结束时间 (yyyy-MM-dd HH:mm:ss)
+
+---
+
+## 2. 接口列表
+
+### 2.1 用户反馈列表
+*   **URL**: `/user/feedback/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户反馈。
+*   **Keyword匹配**: 标题 (`title`) 或 内容 (`content`)
+
+### 2.2 用户邀请记录列表
+*   **URL**: `/user/invite/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询邀请记录。
+*   **UserId说明**: 匹配邀请人ID (`userId`) 或 被邀请人ID (`inviteeId`)
+
+### 2.3 用户消息列表
+*   **URL**: `/user/message/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户消息。
+*   **Keyword匹配**: 消息内容 (`content`)
+
+### 2.4 用户积分订单列表
+*   **URL**: `/user/points/order/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询积分充值订单。
+*   **Keyword匹配**: 交易ID (`transactionId`)
+
+### 2.5 用户积分变动记录列表
+*   **URL**: `/user/points/record/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询积分获取/消耗记录流水。
+
+### 2.6 用户积分使用记录列表
+*   **URL**: `/user/points/usage/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询积分具体使用情况(如兑换商品等)。
+*   **Keyword匹配**: 标题 (`title`) 或 订单ID (`orderId`)
+
+### 2.7 用户兑换记录列表
+*   **URL**: `/user/redemption/record/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询兑换码兑换记录。
+*   **Keyword匹配**: 兑换码 (`redemptionCode`)
+
+### 2.8 用户订阅订单列表
+*   **URL**: `/user/subscribe/order/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询会员订阅订单。
+*   **Keyword匹配**: 交易ID (`transactionId`) / 原始交易ID (`originalTransactionId`) / 产品ID (`productId`)
+
+### 2.9 用户问卷提交记录列表
+*   **URL**: `/user/survey/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户提交的问卷答案。
+*   **Keyword匹配**: 标题 (`title`)
+
+### 2.10 问卷列表
+*   **URL**: `/survey/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询问卷定义(题目)。
+*   **Keyword匹配**: 标题 (`title`)

+ 185 - 0
doc/AI_ALBUM_API.md

@@ -0,0 +1,185 @@
+# 相册管理 API 文档
+
+本文档描述相册管理查询接口规范。
+
+## 1. 全局响应结构
+
+所有接口返回值均由统一响应结构包装,格式如下:
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {},
+  "timestamp": 1709628736123
+}
+```
+
+---
+
+## 2. 相册列表查询
+
+*   **URL**: `/v2/ai/album/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询相册列表,关联用户信息(昵称、邮箱、苹果用户ID、平台、语言)及风格基础信息(风格名称、风格封面图、标准定价积分)。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `id` | long | 否 | 相册ID(精确匹配) |
+| `userId` | long | 否 | 用户ID |
+| `title` | string | 否 | 标题(模糊匹配) |
+| `taskId` | string | 否 | 任务ID(精确匹配) |
+| `orderId` | string | 否 | 订单ID(精确匹配) |
+| `status` | integer | 否 | 写真状态(1:处理中 2:生成中 3:已完成) |
+| `startTime` | string | 否 | 开始时间(yyyy-MM-dd HH:mm:ss) |
+| `endTime` | string | 否 | 结束时间(yyyy-MM-dd HH:mm:ss) |
+
+### 响应参数(`data` 字段)
+
+返回 Mybatis-Plus 分页对象,包含 `records`、`total`、`size`、`current`、`pages` 等字段。
+
+`records` 中每条数据(`AiAlbumVO`)结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | long | 相册ID |
+| `title` | string | 标题 |
+| `userId` | long | 用户ID |
+| `styleId` | integer | 风格ID |
+| `styleName` | string | 风格名称 |
+| `styleCoverImg` | string | 风格封面图 |
+| `standardPlanPoints` | long | 标准定价(积分) |
+| `taskId` | string | 任务ID |
+| `status` | integer | 写真状态(1:处理中 2:生成中 3:已完成) |
+| `pictures` | string | 写真照片 |
+| `videos` | string | 视频 |
+| `accelerate` | integer | 是否加速(0:否 1:是) |
+| `timestamp` | long | 预计花费时间(秒) |
+| `createTime` | string | 创建时间 |
+| `updateTime` | string | 最后更新时间 |
+| `orderId` | string | 订单ID |
+| `type` | integer | 类型(1:分身相册 2:普通相册) |
+| `expectTime` | string | 预计完成时间 |
+| `videoImage` | string | 视频的原始图片 |
+| `nickname` | string | 用户昵称 |
+| `email` | string | 用户邮箱 |
+| `appleUserId` | string | 苹果用户ID |
+| `platform` | string | 平台 |
+| `language` | string | 语言 |
+
+### 示例请求
+
+```http
+GET /v2/ai/album/list?page=1&size=10&userId=10001&status=3
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "records": [
+      {
+        "id": 90001,
+        "title": "春日写真集",
+        "userId": 10001,
+        "styleId": 301,
+        "styleName": "春日写真",
+        "styleCoverImg": "https://cdn.example.com/style-cover.jpg",
+        "standardPlanPoints": 1990,
+        "taskId": "TASK_998877",
+        "status": 3,
+        "pictures": "[\"https://cdn.example.com/p1.jpg\"]",
+        "videos": "[\"https://cdn.example.com/v1.mp4\"]",
+        "accelerate": 1,
+        "timestamp": 300,
+        "createTime": "2026-03-10 09:30:00",
+        "updateTime": "2026-03-10 09:36:20",
+        "orderId": "ORD_202603100001",
+        "type": 2,
+        "expectTime": "2026-03-10 09:40:00",
+        "videoImage": "[\"https://cdn.example.com/raw1.jpg\"]",
+        "nickname": "Tom",
+        "email": "tom@example.com",
+        "appleUserId": "apple_user_001",
+        "platform": "ios",
+        "language": "zh-CN"
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1,
+    "pages": 1
+  },
+  "timestamp": 1773033000000
+}
+```
+
+---
+
+## 3. 相册详情查询
+
+*   **URL**: `/v2/ai/album/get/{id}`
+*   **Method**: `GET`
+*   **描述**: 根据相册ID获取相册详情(返回结构同 `AiAlbumVO`)。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | long | 是 | 相册ID |
+
+### 响应参数(`data` 字段)
+
+返回 `AiAlbumVO`,字段定义同上。
+
+### 示例请求
+
+```http
+GET /v2/ai/album/get/90001
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "id": 90001,
+    "title": "春日写真集",
+    "userId": 10001,
+    "styleId": 301,
+    "styleName": "春日写真",
+    "styleCoverImg": "https://cdn.example.com/style-cover.jpg",
+    "standardPlanPoints": 1990,
+    "taskId": "TASK_998877",
+    "status": 3,
+    "pictures": "[\"https://cdn.example.com/p1.jpg\"]",
+    "videos": "[\"https://cdn.example.com/v1.mp4\"]",
+    "accelerate": 1,
+    "timestamp": 300,
+    "createTime": "2026-03-10 09:30:00",
+    "updateTime": "2026-03-10 09:36:20",
+    "orderId": "ORD_202603100001",
+    "type": 2,
+    "expectTime": "2026-03-10 09:40:00",
+    "videoImage": "[\"https://cdn.example.com/raw1.jpg\"]",
+    "nickname": "Tom",
+    "email": "tom@example.com",
+    "appleUserId": "apple_user_001",
+    "platform": "ios",
+    "language": "zh-CN"
+  },
+  "timestamp": 1773033000000
+}
+```

+ 198 - 0
doc/AI_ORDER_V2_API.md

@@ -0,0 +1,198 @@
+# 订单V2 API 文档
+
+本文档描述订单V2管理相关接口规范。
+
+## 1. 全局响应结构
+
+所有接口返回值均由统一响应结构包装,格式如下:
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {},
+  "timestamp": 1709628736123
+}
+```
+
+---
+
+## 2. 订单V2列表查询
+
+*   **URL**: `/v2/ai/order/v2/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询订单V2列表,并关联返回风格信息(风格名称、封面、标准定价积分)。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `id` | string | 否 | 订单ID(精确匹配) |
+| `userId` | integer | 否 | 用户ID |
+| `status` | integer | 否 | 订单状态(1:待支付 2:已支付 0:已退款) |
+| `taskStatus` | integer | 否 | 任务状态(0异常失败,1任务执行中,2执行完成,3执行失败) |
+| `taskId` | string | 否 | 任务ID(精确匹配) |
+| `orderLang` | string | 否 | 订单语言 |
+| `startTime` | string | 否 | 开始时间(yyyy-MM-dd HH:mm:ss) |
+| `endTime` | string | 否 | 结束时间(yyyy-MM-dd HH:mm:ss) |
+
+### 响应参数(`data` 字段)
+
+返回 Mybatis-Plus 分页对象,包含 `records`、`total`、`size`、`current`、`pages` 等字段。
+
+`records` 中每条数据(`AiOrderV2VO`)结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | string | 订单ID |
+| `userId` | integer | 用户ID |
+| `title` | string | 商品标题 |
+| `status` | integer | 订单状态(1:待支付 2:已支付 0:已退款) |
+| `payPoints` | long | 支付积分 |
+| `payTime` | string | 支付时间 |
+| `albumId` | integer | 相册ID |
+| `styleId` | integer | 模版ID |
+| `taskId` | string | 任务ID |
+| `taskStatus` | integer | 任务状态(0异常失败,1任务执行中,2执行完成,3执行失败) |
+| `orderLang` | string | 订单语言 |
+| `errorMessage` | string | 错误信息 |
+| `result` | string | 结果数据 |
+| `region` | string | 地区 |
+| `language` | string | 语言 |
+| `appVersion` | string | app版本 |
+| `phoneModel` | string | 手机型号 |
+| `phoneOsVersion` | string | 手机系统版本 |
+| `createTime` | string | 创建时间 |
+| `styleName` | string | 风格名称 |
+| `styleCoverImg` | string | 风格封面图 |
+| `standardPlanPoints` | long | 风格标准定价积分 |
+
+### 示例请求
+
+```http
+GET /v2/ai/order/v2/list?page=1&size=10&userId=10001&status=2
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "records": [
+      {
+        "id": "ORD_202603090001",
+        "userId": 10001,
+        "title": "写真生成套餐",
+        "status": 2,
+        "payPoints": 1990,
+        "payTime": "2026-03-09 10:08:30",
+        "albumId": 12,
+        "styleId": 301,
+        "taskId": "TASK_998877",
+        "taskStatus": 2,
+        "orderLang": "zh-CN",
+        "errorMessage": null,
+        "result": "{\"images\":[\"https://cdn.example.com/a.jpg\"]}",
+        "region": "CN",
+        "language": "zh-CN",
+        "appVersion": "1.2.0",
+        "phoneModel": "iPhone 15 Pro",
+        "phoneOsVersion": "iOS 18.3",
+        "createTime": "2026-03-09 10:05:00",
+        "styleName": "春日写真",
+        "styleCoverImg": "https://cdn.example.com/style-cover.jpg",
+        "standardPlanPoints": 1990
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1,
+    "pages": 1
+  },
+  "timestamp": 1773033000000
+}
+```
+
+---
+
+## 3. 订单V2详情查询
+
+*   **URL**: `/v2/ai/order/v2/get/{id}`
+*   **Method**: `GET`
+*   **描述**: 根据订单ID获取订单V2详情。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | string | 是 | 订单ID |
+
+### 响应参数(`data` 字段)
+
+返回 `TAiOrderV2` 对象,字段如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | string | 订单ID |
+| `userId` | integer | 用户ID |
+| `title` | string | 商品标题 |
+| `status` | integer | 订单状态(1:待支付 2:已支付 0:已退款) |
+| `payPoints` | long | 支付积分 |
+| `payTime` | string | 支付时间 |
+| `albumId` | integer | 相册ID |
+| `styleId` | integer | 模版ID |
+| `taskId` | string | 任务ID |
+| `taskStatus` | integer | 任务状态(0异常失败,1任务执行中,2执行完成,3执行失败) |
+| `orderLang` | string | 订单语言 |
+| `errorMessage` | string | 错误信息 |
+| `result` | string | 结果数据 |
+| `region` | string | 地区 |
+| `language` | string | 语言 |
+| `appVersion` | string | app版本 |
+| `phoneModel` | string | 手机型号 |
+| `phoneOsVersion` | string | 手机系统版本 |
+| `createTime` | string | 创建时间 |
+
+### 示例请求
+
+```http
+GET /v2/ai/order/v2/get/ORD_202603090001
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "id": "ORD_202603090001",
+    "userId": 10001,
+    "title": "写真生成套餐",
+    "status": 2,
+    "payPoints": 1990,
+    "payTime": "2026-03-09 10:08:30",
+    "albumId": 12,
+    "styleId": 301,
+    "taskId": "TASK_998877",
+    "taskStatus": 2,
+    "orderLang": "zh-CN",
+    "errorMessage": null,
+    "result": "{\"images\":[\"https://cdn.example.com/a.jpg\"]}",
+    "region": "CN",
+    "language": "zh-CN",
+    "appVersion": "1.2.0",
+    "phoneModel": "iPhone 15 Pro",
+    "phoneOsVersion": "iOS 18.3",
+    "createTime": "2026-03-09 10:05:00"
+  },
+  "timestamp": 1773033000000
+}
+```

+ 120 - 0
doc/AI_SUBSCRIBE_API.md

@@ -0,0 +1,120 @@
+# AI会员订阅配置 API 文档
+
+本文档专门描述 AI 会员订阅配置相关的接口。
+
+## 1. 基础信息
+
+*   **URL 前缀**: `/v2/ai/subscribe`
+*   **响应格式**: 统一使用 `GlobalResponseWrapper` (实际返回类型根据 Controller 定义可能是 IPage 或 boolean,但通常会被全局包装)
+
+---
+
+## 2. 接口列表
+
+### 2.1 获取订阅配置列表
+
+*   **URL**: `/list`
+*   **Method**: `GET`
+*   **描述**: 查询 AI 会员订阅配置列表。
+
+#### 请求参数
+
+无
+
+#### 响应参数 (`data` 字段)
+
+返回 `AiSubscribeVO` 对象列表,元素结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | integer | 主键ID |
+| `productId` | string | Apple产品id |
+| `subscribeType` | integer | 订阅等级(0:连续包月,1:连续包年) |
+| `tokens` | integer | 发放积分数量 |
+| `tokenExpirationTime` | integer | 积分过期时间(天) |
+| `actionType` | integer | 订阅action类型(0:默认,1:推荐) |
+
+#### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "data": [
+    {
+      "id": 1,
+      "productId": "com.aitori.monthly",
+      "subscribeType": 0,
+      "tokens": 100,
+      "tokenExpirationTime": 30,
+      "actionType": 0
+    }
+  ]
+}
+```
+
+### 2.2 新增订阅配置
+
+*   **URL**: `/add`
+*   **Method**: `POST`
+*   **Content-Type**: `application/json`
+*   **描述**: 新增一条订阅配置。
+
+#### 请求参数 (Body)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `productId` | string | 是 | Apple产品id |
+| `subscribeType` | integer | 是 | 订阅等级(0:连续包月,1:连续包年) |
+| `tokens` | integer | 是 | 发放积分数量 |
+| `tokenExpirationTime` | integer | 是 | 积分过期时间(天) |
+| `actionType` | integer | 否 | 订阅action类型(0:默认,1:推荐),默认0 |
+
+#### 响应参数
+
+| 类型 | 说明 |
+| :--- | :--- |
+| boolean | 操作结果 (true/false) |
+
+### 2.3 更新订阅配置
+
+*   **URL**: `/update`
+*   **Method**: `PUT`
+*   **Content-Type**: `application/json`
+*   **描述**: 更新已存在的订阅配置。
+
+#### 请求参数 (Body)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 主键ID |
+| `productId` | string | 否 | Apple产品id |
+| `subscribeType` | integer | 否 | 订阅等级(0:连续包月,1:连续包年) |
+| `tokens` | integer | 否 | 发放积分数量 |
+| `tokenExpirationTime` | integer | 否 | 积分过期时间(天) |
+| `actionType` | integer | 否 | 订阅action类型(0:默认,1:推荐) |
+
+#### 响应参数
+
+| 类型 | 说明 |
+| :--- | :--- |
+| boolean | 操作结果 (true/false) |
+
+### 2.4 删除订阅配置
+
+*   **URL**: `/delete`
+*   **Method**: `DELETE`
+*   **Content-Type**: `application/json`
+*   **描述**: 批量删除订阅配置。
+
+#### 请求参数 (Body)
+
+| 类型 | 必填 | 说明 |
+| :--- | :--- | :--- |
+| `integer[]` | 是 | 主键ID数组,例如 `[1, 2, 3]` |
+
+#### 响应参数
+
+| 类型 | 说明 |
+| :--- | :--- |
+| boolean | 操作结果 (true/false) |

+ 98 - 0
doc/LOGIN_API.md

@@ -0,0 +1,98 @@
+# 系统管理员登录 API 文档
+
+本文档描述了系统管理员登录相关的接口规范。
+
+## 1. 全局响应结构
+
+所有接口的返回值均由 `GlobalResponseWrapper` 统一包装,遵循以下 JSON 结构:
+
+```json
+{
+  "code": 200,          // 状态码 (200: 成功, 其他: 失败)
+  "message": "success", // 响应消息
+  "success": true,      // 成功标识
+  "data": { ... },      // 具体业务数据
+  "timestamp": 1709628736123 // 响应时间戳
+}
+```
+
+---
+
+## 2. 获取图片验证码
+
+*   **URL**: `/v2/admin/login`
+*   **Method**: `GET`
+*   **描述**: 获取登录所需的图形验证码及唯一标识。
+
+### 响应参数 (`data` 字段)
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `uuid` | string | 验证码唯一标识 (登录时需带回) |
+| `img` | string | Base64 格式的验证码图片 |
+
+### 响应示例
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
+    "img": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
+  },
+  "timestamp": 1709628736123
+}
+```
+
+---
+
+## 3. 系统管理员登录
+
+*   **URL**: `/v2/admin/login`
+*   **Method**: `POST`
+*   **Content-Type**: `application/json`
+*   **描述**: 提交用户名、密码及验证码进行登录。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `username` | string | 是 | 用户名 |
+| `password` | string | 是 | 密码 (建议前端加密传输,当前后端实现为明文比对) |
+| `captcha` | string | 是 | 验证码字符 (不区分大小写) |
+| `uuid` | string | 是 | 获取验证码接口返回的 `uuid` |
+| `rememberMe` | boolean | 否 | 是否记住我 (默认 `false`) |
+
+### 请求示例
+
+```json
+{
+  "username": "admin",
+  "password": "your_password",
+  "captcha": "ABCD",
+  "uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
+  "rememberMe": true
+}
+```
+
+### 响应参数 (`data` 字段)
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `token` | string | 登录凭证 (后续请求需携带) |
+
+### 响应示例
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
+  },
+  "timestamp": 1709628738888
+}
+```

+ 159 - 0
doc/MODULE_API.md

@@ -0,0 +1,159 @@
+# 首页模块管理 API 文档
+
+本文档描述了首页模块管理及其配置相关的接口规范。
+
+## 1. 全局响应结构
+
+所有接口的返回值均由 `GlobalResponseWrapper` 统一包装,遵循以下 JSON 结构:
+
+```json
+{
+  "code": 200,          // 状态码 (200: 成功, 其他: 失败)
+  "message": "success", // 响应消息
+  "success": true,      // 成功标识
+  "data": { ... },      // 具体业务数据
+  "timestamp": 1709628736123 // 响应时间戳
+}
+```
+
+---
+
+## 2. 新增/更新模块 (包含配置项)
+
+*   **URL**: `/v2/module/add`
+*   **Method**: `POST`
+*   **Content-Type**: `application/json`
+*   **描述**: 新增或更新模块信息。若提供了 `configs`,则会将这些配置项绑定到该模块下(会覆盖旧的配置)。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 否 | 模块ID (更新时必填,新增时无需传) |
+| `name` | string | 否 | 模块名称 |
+| `subTitle` | string | 否 | 子标题 |
+| `moduleType` | string | 否 | 模块类型 1:幻灯片 2:列表 3:卡片 4:条幅 5:标签 6:vip类型 7:排版1 8:排版2 9:排版3 |
+| `sort` | integer | 否 | 排序 |
+| `lang` | string | 否 | 语言包 (JSON字符串) |
+| `configs` | array | 否 | 模块配置列表 |
+
+#### configs 结构
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `type` | integer | 是 | 类型 1:标签 2:风格 3:条幅 4:vip框 |
+| `tagId` | integer | 否 | 标签id |
+| `styleId` | integer | 否 | 风格id |
+| `effectId` | integer | 否 | 滤镜id |
+| `coverImg` | string | 否 | 封面图 |
+| `cardCoverImg` | string | 否 | 卡片模块下封面图 |
+| `sort` | integer | 否 | 排序 |
+| `linkType` | integer | 否 | 链接类型 1:打开标签详情 2:打开风格详情 3: 打开网页 |
+| `url` | string | 否 | 跳转URL |
+| `tagName` | string | 否 | 标签名称 |
+| `tagDescribe` | string | 否 | 标签描述 |
+| `styleName` | string | 否 | 风格名称 |
+| `styleDescribe` | string | 否 | 风格描述 |
+| `effectName` | string | 否 | 滤镜名称 |
+| `effectDescribe` | string | 否 | 滤镜描述 |
+| `urlTitle` | string | 否 | url标题 |
+| `content` | string | 否 | 内容 |
+| `description` | string | 否 | 内容描述 |
+
+### 响应参数
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `data` | boolean | 是否成功 |
+
+---
+
+## 3. 批量更新排序
+
+*   **URL**: `/v2/module/sort`
+*   **Method**: `POST`
+*   **Content-Type**: `application/json`
+*   **描述**: 批量更新模块的排序值。
+
+### 请求参数 (数组)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 模块ID |
+| `sort` | integer | 是 | 排序值 |
+
+**请求示例**:
+
+```json
+[
+  { "id": 1, "sort": 1 },
+  { "id": 2, "sort": 2 },
+  { "id": 3, "sort": 3 }
+]
+```
+
+### 响应参数
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `data` | boolean | 是否成功 |
+
+---
+
+## 4. 删除模块
+
+*   **URL**: `/v2/module/delete/{id}`
+*   **Method**: `DELETE`
+*   **描述**: 根据 ID 删除模块,同时会删除该模块下的所有配置项。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 模块ID |
+
+### 响应参数
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `data` | boolean | 是否成功 |
+
+---
+
+## 5. 获取单个模块详情
+
+*   **URL**: `/v2/module/get/{id}`
+*   **Method**: `GET`
+*   **描述**: 根据 ID 获取模块详情(包含配置项)。返回的数据结构与新增/更新接口的请求参数结构完全一致,方便前端回显和再次提交。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 模块ID |
+
+### 响应参数 (`data` 字段)
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | integer | 模块ID |
+| `name` | string | 模块名称 |
+| `subTitle` | string | 子标题 |
+| `moduleType` | string | 模块类型 |
+| `sort` | integer | 排序 |
+| `lang` | string | 语言包 |
+| `configs` | array | 关联的配置列表 (结构同请求参数中的 configs) |
+
+---
+
+## 6. 获取所有模块列表
+
+*   **URL**: `/v2/module/list`
+*   **Method**: `GET`
+*   **描述**: 获取所有模块列表(包含配置项),已按 `sort` 字段升序排序。返回的数据结构与新增/更新接口的请求参数结构完全一致。
+
+### 响应参数 (`data` 字段)
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `data` | array | 模块详情列表 (结构同单个模块详情) |

+ 116 - 0
doc/POINTS_CONFIG_API.md

@@ -0,0 +1,116 @@
+# 积分来源配置 API 文档
+
+本文档专门描述积分来源配置相关的接口。
+
+## 1. 基础信息
+
+*   **URL 前缀**: `/v2/points/config`
+*   **响应格式**: 统一使用 `GlobalResponseWrapper`
+
+---
+
+## 2. 接口列表
+
+### 2.1 获取配置列表
+
+*   **URL**: `/list`
+*   **Method**: `GET`
+*   **描述**: 查询积分来源配置列表。
+
+#### 请求参数
+
+无
+
+#### 响应参数 (`data` 字段)
+
+返回 `PointsConfigVO` 对象列表,元素结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | integer | 主键ID |
+| `code` | integer | 来源代码 |
+| `points` | integer | 积分数量 |
+| `expireDays` | integer | 有效期天数 |
+| `desc` | string | 描述 |
+
+#### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "data": [
+    {
+      "id": 1,
+      "code": 3,
+      "points": 10,
+      "expireDays": 7,
+      "desc": "每日签到奖励"
+    }
+  ]
+}
+```
+
+### 2.2 新增配置
+
+*   **URL**: `/add`
+*   **Method**: `POST`
+*   **Content-Type**: `application/json`
+*   **描述**: 新增一条积分来源配置。
+
+#### 请求参数 (Body)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `code` | integer | 是 | 来源代码 (1-8) |
+| `points` | integer | 是 | 积分数量 |
+| `expireDays` | integer | 是 | 有效期天数 |
+| `desc` | string | 否 | 描述 |
+
+#### 响应参数
+
+| 类型 | 说明 |
+| :--- | :--- |
+| boolean | 操作结果 (true/false) |
+
+### 2.3 更新配置
+
+*   **URL**: `/update`
+*   **Method**: `PUT`
+*   **Content-Type**: `application/json`
+*   **描述**: 更新已存在的积分来源配置。
+
+#### 请求参数 (Body)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 主键ID |
+| `code` | integer | 否 | 来源代码 |
+| `points` | integer | 否 | 积分数量 |
+| `expireDays` | integer | 否 | 有效期天数 |
+| `desc` | string | 否 | 描述 |
+
+#### 响应参数
+
+| 类型 | 说明 |
+| :--- | :--- |
+| boolean | 操作结果 (true/false) |
+
+### 2.4 删除配置
+
+*   **URL**: `/delete`
+*   **Method**: `DELETE`
+*   **Content-Type**: `application/json`
+*   **描述**: 批量删除积分来源配置。
+
+#### 请求参数 (Body)
+
+| 类型 | 必填 | 说明 |
+| :--- | :--- | :--- |
+| `integer[]` | 是 | 主键ID数组,例如 `[1, 2, 3]` |
+
+#### 响应参数
+
+| 类型 | 说明 |
+| :--- | :--- |
+| boolean | 操作结果 (true/false) |

+ 128 - 0
doc/POINTS_PRODUCT_API.md

@@ -0,0 +1,128 @@
+# 积分产品 API 文档
+
+本文档专门描述积分产品管理相关的接口。
+
+## 1. 基础信息
+
+*   **URL 前缀**: `/v2/points/product`
+*   **响应格式**: 统一使用 `GlobalResponseWrapper`
+
+---
+
+## 2. 接口列表
+
+### 2.1 获取产品列表
+
+*   **URL**: `/list`
+*   **Method**: `GET`
+*   **描述**: 查询积分产品列表,默认按 `sort` 字段升序排列。
+
+#### 请求参数
+
+无
+
+#### 响应参数 (`data` 字段)
+
+返回 `PointsProductVO` 对象列表,元素结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | integer | 主键ID |
+| `points` | long | 点数 |
+| `price` | decimal | 价格 |
+| `originalPrice` | decimal | 原始价格 |
+| `isDiscounted` | integer | 是否打折 |
+| `applePayProductId` | string | 苹果支付产品id |
+| `sort` | integer | 排序值 |
+| `color` | string | 角标颜色值 |
+
+#### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "data": [
+    {
+      "id": 1,
+      "points": 1000,
+      "price": 9.99,
+      "originalPrice": 12.99,
+      "isDiscounted": 1,
+      "applePayProductId": "com.aitori.points.1000",
+      "sort": 1,
+      "color": "#FF0000"
+    }
+  ]
+}
+```
+
+### 2.2 新增产品
+
+*   **URL**: `/add`
+*   **Method**: `POST`
+*   **Content-Type**: `application/json`
+*   **描述**: 新增一条积分产品。
+
+#### 请求参数 (Body)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `points` | long | 是 | 点数 |
+| `price` | decimal | 是 | 价格 |
+| `originalPrice` | decimal | 否 | 原始价格 |
+| `isDiscounted` | integer | 否 | 是否打折 |
+| `applePayProductId` | string | 是 | 苹果支付产品id |
+| `sort` | integer | 否 | 排序 |
+| `color` | string | 否 | 角标颜色值 |
+
+#### 响应参数
+
+| 类型 | 说明 |
+| :--- | :--- |
+| boolean | 操作结果 (true/false) |
+
+### 2.3 更新产品
+
+*   **URL**: `/update`
+*   **Method**: `PUT`
+*   **Content-Type**: `application/json`
+*   **描述**: 更新已存在的积分产品。
+
+#### 请求参数 (Body)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 主键ID |
+| `points` | long | 否 | 点数 |
+| `price` | decimal | 否 | 价格 |
+| `originalPrice` | decimal | 否 | 原始价格 |
+| `isDiscounted` | integer | 否 | 是否打折 |
+| `applePayProductId` | string | 否 | 苹果支付产品id |
+| `sort` | integer | 否 | 排序 |
+| `color` | string | 否 | 角标颜色值 |
+
+#### 响应参数
+
+| 类型 | 说明 |
+| :--- | :--- |
+| boolean | 操作结果 (true/false) |
+
+### 2.4 删除产品
+
+*   **URL**: `/delete`
+*   **Method**: `DELETE`
+*   **Content-Type**: `application/json`
+*   **描述**: 批量删除积分产品。
+
+#### 请求参数 (Body)
+
+| 类型 | 必填 | 说明 |
+| :--- | :--- | :--- |
+| `integer[]` | 是 | 主键ID数组,例如 `[1, 2, 3]` |
+
+#### 响应参数
+
+| 类型 | 说明 |
+| :--- | :--- |
+| boolean | 操作结果 (true/false) |

+ 266 - 0
doc/REDEMPTION_CODE_API.md

@@ -0,0 +1,266 @@
+# 兑换码管理 API 文档
+
+本文档描述了兑换码管理相关的接口规范。
+
+## 1. 兑换码列表
+
+*   **URL**: `/v2/redemption/code/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询兑换码列表,支持按兑换码关键字和状态筛选。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `keyword` | string | 否 | 搜索关键字(模糊匹配兑换码) |
+| `status` | integer | 否 | 状态 0:禁用 1:启用 |
+
+### 响应参数 (`data` 字段)
+
+返回 Mybatis-Plus `Page` 对象,包含 `records` (记录列表), `total` (总数), `size` (页大小), `current` (当前页) 等。
+
+`records` 中的对象 (`RedemptionCodeVO`) 结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | long | 主键ID |
+| `code` | string | 兑换码 |
+| `points` | long | 积分 |
+| `maxUsage` | long | 最大使用次数 |
+| `usedCount` | long | 已使用次数 |
+| `expireTime` | string | 失效日期 |
+| `status` | integer | 状态 0:禁用 1:启用 |
+| `createTime` | string | 创建时间 |
+
+### 示例请求
+
+查询全部兑换码:
+
+```http
+GET /v2/redemption/code/list?page=1&size=10
+```
+
+按关键字搜索:
+
+```http
+GET /v2/redemption/code/list?page=1&size=10&keyword=ABC
+```
+
+按状态筛选:
+
+```http
+GET /v2/redemption/code/list?page=1&size=10&status=1
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "records": [
+      {
+        "id": 1,
+        "code": "ABC12345",
+        "points": 100,
+        "maxUsage": 10,
+        "usedCount": 3,
+        "expireTime": "2026-12-31 23:59:59",
+        "status": 1,
+        "createTime": "2026-01-01 00:00:00"
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1,
+    "pages": 1
+  },
+  "timestamp": 1698372000000
+}
+```
+
+---
+
+## 2. 新增兑换码
+
+*   **URL**: `/v2/redemption/code/add`
+*   **Method**: `POST`
+*   **描述**: 新增一个兑换码。
+
+### 请求参数 (JSON Body)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `code` | string | 是 | 兑换码(唯一) |
+| `points` | long | 否 | 积分 |
+| `maxUsage` | long | 否 | 最大使用次数 |
+| `expireTime` | string | 否 | 失效日期 (yyyy-MM-dd HH:mm:ss) |
+| `status` | integer | 否 | 状态 0:禁用 1:启用,默认 1 |
+
+### 示例请求
+
+```http
+POST /v2/redemption/code/add
+Content-Type: application/json
+
+{
+  "code": "NEWYEAR2026",
+  "points": 500,
+  "maxUsage": 100,
+  "expireTime": "2026-12-31 23:59:59",
+  "status": 1
+}
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": true,
+  "timestamp": 1698372000000
+}
+```
+
+---
+
+## 3. 批量新增兑换码
+
+*   **URL**: `/v2/redemption/code/addBatch`
+*   **Method**: `POST`
+*   **描述**: 批量新增兑换码。
+
+### 请求参数 (JSON Body)
+
+传入 `TRedemptionCode` 对象数组,每个对象字段同"新增兑换码"接口:
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `code` | string | 是 | 兑换码(唯一) |
+| `points` | long | 否 | 积分 |
+| `maxUsage` | long | 否 | 最大使用次数 |
+| `expireTime` | string | 否 | 失效日期 (yyyy-MM-dd HH:mm:ss) |
+| `status` | integer | 否 | 状态 0:禁用 1:启用,默认 1 |
+
+### 示例请求
+
+```http
+POST /v2/redemption/code/addBatch
+Content-Type: application/json
+
+[
+  {
+    "code": "BATCH001",
+    "points": 100,
+    "maxUsage": 50,
+    "expireTime": "2026-12-31 23:59:59",
+    "status": 1
+  },
+  {
+    "code": "BATCH002",
+    "points": 200,
+    "maxUsage": 30,
+    "expireTime": "2026-06-30 23:59:59",
+    "status": 1
+  }
+]
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": true,
+  "timestamp": 1698372000000
+}
+```
+
+---
+
+## 4. 更新兑换码
+
+*   **URL**: `/v2/redemption/code/update`
+*   **Method**: `PUT`
+*   **描述**: 根据 ID 更新兑换码信息。
+
+### 请求参数 (JSON Body)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | long | 是 | 主键ID |
+| `code` | string | 否 | 兑换码 |
+| `points` | long | 否 | 积分 |
+| `maxUsage` | long | 否 | 最大使用次数 |
+| `expireTime` | string | 否 | 失效日期 (yyyy-MM-dd HH:mm:ss) |
+| `status` | integer | 否 | 状态 0:禁用 1:启用 |
+
+### 示例请求
+
+```http
+PUT /v2/redemption/code/update
+Content-Type: application/json
+
+{
+  "id": 1,
+  "points": 1000,
+  "status": 0
+}
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": true,
+  "timestamp": 1698372000000
+}
+```
+
+---
+
+## 5. 删除兑换码
+
+*   **URL**: `/v2/redemption/code/delete`
+*   **Method**: `DELETE`
+*   **描述**: 根据 ID 批量删除兑换码。
+
+### 请求参数 (JSON Body)
+
+传入 ID 数组:
+
+```json
+[1, 2, 3]
+```
+
+### 示例请求
+
+```http
+DELETE /v2/redemption/code/delete
+Content-Type: application/json
+
+[1, 2, 3]
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": true,
+  "timestamp": 1698372000000
+}
+```

+ 59 - 0
doc/STORAGE_API.md

@@ -0,0 +1,59 @@
+# 文件存储 API 文档
+
+本文档描述了文件上传相关的接口规范。
+
+## 1. 全局响应结构
+
+所有接口的返回值均由 `GlobalResponseWrapper` 统一包装,遵循以下 JSON 结构:
+
+```json
+{
+  "code": 200,          // 状态码 (200: 成功, 其他: 失败)
+  "message": "success", // 响应消息
+  "success": true,      // 成功标识
+  "data": "https://...", // 具体业务数据 (文件URL)
+  "timestamp": 1709628736123 // 响应时间戳
+}
+```
+
+---
+
+## 2. 上传文件到 Google Cloud Storage
+
+*   **URL**: `/google/upload/img`
+*   **Method**: `POST`
+*   **Content-Type**: `multipart/form-data`
+*   **描述**: 上传文件到 Google Cloud Storage 存储桶,并返回可访问的 URL。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `file` | file | 是 | 需要上传的文件 (图片、视频等) |
+| `prefix` | string | 是 | 文件存储路径前缀 (例如: `avatar/`, `banner/`),建议以 `/` 结尾 |
+
+### 响应参数 (`data` 字段)
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `data` | string | 文件访问的完整 URL |
+
+### 请求示例 (cURL)
+
+```bash
+curl --location --request POST 'http://localhost:8080/google/upload/img' \
+--form 'file=@"/path/to/image.jpg"' \
+--form 'prefix="test/"'
+```
+
+### 响应示例
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": "https://storage.googleapis.com/aitori-public/test/a1b2c3d4e5f67890.jpg",
+  "timestamp": 1709628736123
+}
+```

+ 169 - 0
doc/STYLE_API.md

@@ -0,0 +1,169 @@
+# AI 风格管理 API 文档
+
+本文档描述了 AI 风格管理相关的接口规范。
+
+## 1. 全局响应结构
+
+所有接口的返回值均由 `GlobalResponseWrapper` 统一包装,遵循以下 JSON 结构:
+
+```json
+{
+  "code": 200,          // 状态码 (200: 成功, 其他: 失败)
+  "message": "success", // 响应消息
+  "success": true,      // 成功标识
+  "data": { ... },      // 具体业务数据
+  "timestamp": 1709628736123 // 响应时间戳
+}
+```
+
+---
+
+## 2. 新增风格
+
+*   **URL**: `/v2/style/add`
+*   **Method**: `POST`
+*   **Content-Type**: `application/json`
+*   **描述**: 新增 AI 风格。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `name` | string | 否 | 风格名字 |
+| `describe` | string | 否 | 风格描述 |
+| `author` | string | 否 | 作者 |
+| `coverImg` | string | 否 | 风格封面,可以是一个视频封面 |
+| `detailImg` | string | 否 | 详情图(逗号分割) |
+| `originalStandardPlanPrice` | number | 否 | 标准定价(原价) |
+| `originalStandardPlanPoints` | integer | 否 | 标准定价(原价)(积分) |
+| `standardPlanPrice` | number | 否 | 标准定价 |
+| `standardPlanPoints` | integer | 否 | 标准定价(积分) |
+| `originalFastPlanPrice` | number | 否 | 快速套餐价格(原价) |
+| `originalFastPlanPoints` | integer | 否 | 快速套餐价格(原价)(积分) |
+| `fastPlanPrice` | number | 否 | 快速套餐价格 |
+| `fastPlanPoints` | integer | 否 | 快速套餐价格(积分) |
+| `sort` | integer | 否 | 排序 |
+| `tagId` | string | 否 | 风格的tag(限免、上新、等) |
+| `use` | integer | 否 | 风格的使用量 |
+| `minPic` | integer | 否 | 风格所需最低图片数量 |
+| `maxPic` | integer | 否 | 风格所需最高图片数量 |
+| `fastPic` | integer | 否 | 快速套餐生成图片数量 |
+| `standardPic` | integer | 否 | 标准套餐生成图片数量 |
+| `templateZip` | string | 否 | 模版zip文件 |
+| `showType` | integer | 否 | 展示类型 0:默认 |
+| `textTitleBelowSampleImg` | string | 否 | 样图下面区域文本标题 |
+| `textContentBelowSampleImg` | string | 否 | 样图下面区域文本内容 |
+| `sampleImg` | string | 否 | 样图 |
+| `detailImgMask` | string | 否 | 详情图蒙版 |
+| `cardCoverImg` | string | 否 | 卡片模块下封面图 |
+| `platform` | string | 否 | 平台(iOS,安卓等) |
+| `minVersionNum` | string | 否 | 最小版本号 |
+| `maxVersionNum` | string | 否 | 最大版本号 |
+| `sampleImgTitle` | string | 否 | 样图标题 |
+| `sampleImgDesc` | string | 否 | 样图描述 |
+| `stylemode` | integer | 否 | stylemode |
+| `inneed` | integer | 否 | inneed |
+| `badge` | string | 否 | 角标 |
+| `color` | string | 否 | 角标颜色值 |
+| `lang` | string | 否 | 语言包 |
+| `apiType` | integer | 否 | ai平台(默认2) |
+| `aiContent` | string | 否 | api调用默认参数 |
+| `aiType` | string | 否 | 滤镜能力 |
+| `requirements` | string | 否 | requirements |
+| `styleType` | integer | 否 | 模版类型,0:可试用,1:vip |
+| `previewImg` | string | 否 | 视频封面预览图 |
+
+### 响应参数
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `data` | boolean | 是否成功 |
+
+---
+
+## 3. 删除风格
+
+*   **URL**: `/v2/style/delete/{id}`
+*   **Method**: `DELETE`
+*   **描述**: 根据 ID 删除风格。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 风格ID |
+
+### 响应参数
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `data` | boolean | 是否成功 |
+
+---
+
+## 4. 修改风格
+
+*   **URL**: `/v2/style/update`
+*   **Method**: `PUT`
+*   **Content-Type**: `application/json`
+*   **描述**: 修改风格信息。
+
+### 请求参数
+
+与新增风格参数一致,需包含 `id` 字段。
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 风格ID |
+| ... | ... | ... | 其他需修改的字段 |
+
+### 响应参数
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `data` | boolean | 是否成功 |
+
+---
+
+## 5. 获取单个风格
+
+*   **URL**: `/v2/style/get/{id}`
+*   **Method**: `GET`
+*   **描述**: 根据 ID 获取风格详情。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 风格ID |
+
+### 响应参数 (`data` 字段)
+
+返回 `TAiStyle` 对象,字段详情见新增接口参数说明。
+
+---
+
+## 6. 分页查询风格
+
+*   **URL**: `/v2/style/page`
+*   **Method**: `GET`
+*   **描述**: 分页查询风格,支持风格名称模糊查询。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `pageNum` | integer | 是 | 页码 (默认 1) |
+| `pageSize` | integer | 是 | 每页数量 (默认 10) |
+| `name` | string | 否 | 风格名称 (模糊查询) |
+| `id` | integer | 否 | 风格ID (精确查询) |
+
+### 响应参数 (`data` 字段)
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `records` | array | 风格列表 |
+| `total` | integer | 总记录数 |
+| `size` | integer | 每页数量 |
+| `current` | integer | 当前页码 |
+| `pages` | integer | 总页数 |

+ 148 - 0
doc/SUBSCRIBE_ACTION_API.md

@@ -0,0 +1,148 @@
+# 订阅广告配置 API 文档
+
+本文档描述了订阅广告配置相关的接口规范。
+
+## 1. 订阅广告列表
+
+*   **URL**: `/v2/subscribe/action/list`
+*   **Method**: `GET`
+*   **描述**: 查询所有订阅广告配置(不分页)。
+
+### 请求参数
+
+无
+
+### 响应参数 (`data` 字段)
+
+返回 `List<TSubscribeAction>` 数组。
+
+数组中每个对象的结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | integer | 主键ID |
+| `title` | string | 标题 |
+| `description` | string | 描述 |
+| `image` | string | 图片URL |
+| `type` | integer | 类型(0:首页广告,1:订阅页面) |
+| `lang` | string | 多语言配置 |
+| `createTime` | string | 创建时间 |
+
+### 示例请求
+
+```http
+GET /v2/subscribe/action/list
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": [
+    {
+      "id": 1,
+      "title": "Summer Sale",
+      "description": "50% off for annual plan",
+      "image": "https://example.com/banner.png",
+      "type": 1,
+      "lang": "{\"en\":\"Summer Sale\",\"zh\":\"夏季促销\"}",
+      "createTime": "2023-10-01 12:00:00"
+    }
+  ],
+  "timestamp": 1698806000000
+}
+```
+
+## 2. 添加订阅广告
+
+*   **URL**: `/v2/subscribe/action/add`
+*   **Method**: `POST`
+*   **描述**: 添加新的订阅广告配置。
+
+### 请求参数 (Body)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `title` | string | 是 | 标题 |
+| `description` | string | 否 | 描述 |
+| `image` | string | 否 | 图片URL |
+| `type` | integer | 是 | 类型(0:首页广告,1:订阅页面) |
+| `lang` | string | 否 | 多语言配置 |
+
+### 示例请求
+
+```http
+POST /v2/subscribe/action/add
+Content-Type: application/json
+
+{
+  "title": "New Year Sale",
+  "description": "Best deal ever",
+  "image": "https://example.com/newyear.png",
+  "type": 0,
+  "lang": "{\"en\":\"New Year Sale\"}"
+}
+```
+
+### 响应参数 (`data` 字段)
+
+返回 `boolean`,表示是否成功。
+
+## 3. 更新订阅广告
+
+*   **URL**: `/v2/subscribe/action/update`
+*   **Method**: `POST`
+*   **描述**: 更新现有的订阅广告配置。
+
+### 请求参数 (Body)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 主键ID |
+| `title` | string | 否 | 标题 |
+| `description` | string | 否 | 描述 |
+| `image` | string | 否 | 图片URL |
+| `type` | integer | 否 | 类型(0:首页广告,1:订阅页面) |
+| `lang` | string | 否 | 多语言配置 |
+
+### 示例请求
+
+```http
+POST /v2/subscribe/action/update
+Content-Type: application/json
+
+{
+  "id": 1,
+  "title": "Updated Title",
+  "type": 1
+}
+```
+
+### 响应参数 (`data` 字段)
+
+返回 `boolean`,表示是否成功。
+
+## 4. 删除订阅广告
+
+*   **URL**: `/v2/subscribe/action/delete`
+*   **Method**: `POST`
+*   **描述**: 根据ID删除订阅广告配置。
+
+### 请求参数 (Query)
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 主键ID |
+
+### 示例请求
+
+```http
+POST /v2/subscribe/action/delete?id=1
+```
+
+### 响应参数 (`data` 字段)
+
+返回 `boolean`,表示是否成功。

+ 139 - 0
doc/TAG_API.md

@@ -0,0 +1,139 @@
+# 标签管理 API 文档
+
+本文档描述了标签管理及其风格绑定相关的接口规范。
+
+## 1. 全局响应结构
+
+所有接口的返回值均由 `GlobalResponseWrapper` 统一包装,遵循以下 JSON 结构:
+
+```json
+{
+  "code": 200,          // 状态码 (200: 成功, 其他: 失败)
+  "message": "success", // 响应消息
+  "success": true,      // 成功标识
+  "data": { ... },      // 具体业务数据
+  "timestamp": 1709628736123 // 响应时间戳
+}
+```
+
+---
+
+## 2. 新增/更新标签 (包含绑定风格)
+
+*   **URL**: `/v2/tag/add`
+*   **Method**: `POST`
+*   **Content-Type**: `application/json`
+*   **描述**: 新增或更新标签信息。若提供了 `styleIds`,则会将这些风格绑定到该标签下(会覆盖旧的绑定关系)。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 否 | 标签ID (更新时必填,新增时无需传) |
+| `name` | string | 否 | 标签名 |
+| `describe` | string | 否 | 描述 |
+| `coverImg` | string | 否 | 标签封面图 |
+| `cardCoverImg` | string | 否 | 卡片封面图 |
+| `lang` | string | 否 | 语言包 (JSON字符串) |
+| `styleIds` | array | 否 | 绑定的风格ID列表 (e.g. `[1, 2, 3]`) |
+
+### 请求示例
+
+```json
+{
+  "name": "热门风格",
+  "describe": "最受欢迎的AI风格集合",
+  "coverImg": "https://example.com/cover.jpg",
+  "styleIds": [101, 102, 105]
+}
+```
+
+### 响应参数
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `data` | boolean | 是否成功 |
+
+---
+
+## 3. 删除标签
+
+*   **URL**: `/v2/tag/delete/{id}`
+*   **Method**: `DELETE`
+*   **描述**: 根据 ID 删除标签,同时会删除该标签下的风格绑定关系。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 标签ID |
+
+### 响应参数
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `data` | boolean | 是否成功 |
+
+---
+
+## 4. 获取单个标签详情
+
+*   **URL**: `/v2/tag/get/{id}`
+*   **Method**: `GET`
+*   **描述**: 根据 ID 获取标签的基本信息,包含绑定的风格列表。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | integer | 是 | 标签ID |
+
+### 响应参数 (`data` 字段)
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | integer | 标签ID |
+| `name` | string | 标签名 |
+| `describe` | string | 描述 |
+| `coverImg` | string | 封面图 |
+| `cardCoverImg` | string | 卡片封面图 |
+| `createTime` | string | 创建时间 |
+| `lang` | string | 语言包 |
+| `styles` | array | 绑定的风格列表 |
+
+#### styles 结构
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | integer | 配置ID |
+| `tagId` | integer | 标签ID |
+| `styleId` | integer | 风格ID |
+| `styleName` | string | 风格名称 |
+| `coverImg` | string | 风格封面图 |
+| `sort` | integer | 排序 |
+
+---
+
+## 5. 分页查询标签
+
+*   **URL**: `/v2/tag/page`
+*   **Method**: `GET`
+*   **描述**: 分页查询标签列表。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `pageNum` | integer | 是 | 页码 (默认 1) |
+| `pageSize` | integer | 是 | 每页数量 (默认 10) |
+| `name` | string | 否 | 标签名称 (模糊查询) |
+
+### 响应参数 (`data` 字段)
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `records` | array | 标签列表 |
+| `total` | integer | 总记录数 |
+| `size` | integer | 每页数量 |
+| `current` | integer | 当前页码 |
+| `pages` | integer | 总页数 |

+ 89 - 0
doc/USER_API.md

@@ -0,0 +1,89 @@
+# 用户管理 API 文档
+
+本文档描述了用户管理相关的接口规范,包括用户列表查询和状态修改。
+
+## 1. 全局响应结构
+
+所有接口的返回值均由 `GlobalResponseWrapper` 统一包装,遵循以下 JSON 结构:
+
+```json
+{
+  "code": 200,          // 状态码 (200: 成功, 其他: 失败)
+  "message": "success", // 响应消息
+  "success": true,      // 成功标识
+  "data": { ... },      // 具体业务数据
+  "timestamp": 1709628736123 // 响应时间戳
+}
+```
+
+---
+
+## 2. 用户列表查询
+
+*   **URL**: `/v2/user/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户列表,支持多种筛选条件。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `keyword` | string | 否 | 搜索关键字 (匹配:昵称/邮箱/苹果ID/交易ID) |
+| `status` | integer | 否 | 状态 0:禁用 1:可用 2:注销 |
+| `isVip` | integer | 否 | 是否会员 0:否 1:是 |
+| `platform` | string | 否 | 平台 (如 IOS) |
+| `createTimeStart` | string | 否 | 创建时间开始 (yyyy-MM-dd HH:mm:ss) |
+| `createTimeEnd` | string | 否 | 创建时间结束 (yyyy-MM-dd HH:mm:ss) |
+
+### 响应参数 (`data` 字段)
+
+返回 Mybatis-Plus `Page` 对象,包含 `records` (用户列表), `total` (总数), `size` (页大小), `current` (当前页) 等。
+
+`records` 中的用户对象结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | long | 用户ID |
+| `nickname` | string | 用户昵称 |
+| `email` | string | 用户账号/邮箱 |
+| `appleUserId` | string | 苹果id |
+| `originalTransactionId` | string | 交易id |
+| `lastLoginTime` | string | 最近一次登录时间 |
+| `lastLoginIp` | string | 最近一次登录IP地址 |
+| `role` | string | 用户身份信息 |
+| `status` | integer | 状态 0:禁用 1:可用 2:注销 |
+| `createTime` | string | 创建时间 |
+| `updateTime` | string | 更新时间 |
+| `avatar` | string | 头像 |
+| `gender` | integer | 性别 0:女 1:男 |
+| `msgSwitch` | integer | 消息推送开关 |
+| `commentPermission` | integer | 评论权限 |
+| `referralCode` | string | 推荐码 |
+| `loginCount` | long | 登录次数 |
+| `language` | string | 地区 |
+| `isVip` | integer | 是否会员 |
+| `platform` | string | 平台 |
+
+---
+
+## 3. 修改用户状态
+
+*   **URL**: `/v2/user/status`
+*   **Method**: `POST`
+*   **Content-Type**: `application/json`
+*   **描述**: 修改用户状态。仅允许设置为禁用 (0) 或可用 (1)。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | long | 是 | 用户ID |
+| `status` | integer | 是 | 状态 0:禁用 1:可用 |
+
+### 响应参数
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `data` | boolean | 是否成功 |

+ 78 - 0
doc/USER_FEEDBACK_API.md

@@ -0,0 +1,78 @@
+# 用户反馈 API 文档
+
+本文档专门描述用户反馈管理相关的接口。
+
+## 1. 基础信息
+
+*   **URL 前缀**: `/v2/user/feedback`
+*   **响应格式**: 统一使用 `GlobalResponseWrapper`
+
+---
+
+## 2. 接口列表
+
+### 2.1 获取用户反馈列表
+
+*   **URL**: `/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户反馈列表。接口会同时返回提交反馈的用户详细信息(昵称、邮箱、苹果ID、平台、语言等),并支持通过这些用户属性进行搜索。
+
+#### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `userId` | integer | 否 | 精确筛选用户ID |
+| `platform` | string | 否 | **筛选用户平台** (如 "IOS", "ANDROID") |
+| `language` | string | 否 | **筛选用户语言** (如 "zh-CN", "en-US") |
+| `keyword` | string | 否 | **多字段模糊搜索**。支持匹配:<br>1. 反馈标题 (`title`)<br>2. 反馈内容 (`content`)<br>3. 用户昵称 (`nickname`)<br>4. 用户邮箱 (`email`)<br>5. 苹果ID (`appleUserId`)<br>6. 原始交易ID (`originalTransactionId`) |
+| `startTime` | string | 否 | 开始时间 (yyyy-MM-dd HH:mm:ss) |
+| `endTime` | string | 否 | 结束时间 (yyyy-MM-dd HH:mm:ss) |
+
+#### 响应参数 (`data` 字段)
+
+返回 Mybatis-Plus `Page` 对象,`records` 列表中的元素结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | integer | 反馈ID |
+| `userId` | integer | 用户ID |
+| `title` | string | 反馈标题 |
+| `content` | string | 反馈内容 |
+| `createTime` | string | 创建时间 |
+| `nickname` | string | **用户昵称** |
+| `email` | string | **用户邮箱** |
+| `appleUserId` | string | **苹果ID** |
+| `originalTransactionId` | string | **原始交易ID** |
+| `platform` | string | **用户平台** |
+| `language` | string | **用户语言** |
+
+#### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "data": {
+    "records": [
+      {
+        "id": 101,
+        "userId": 10086,
+        "title": "充值未到账",
+        "content": "我刚刚充值了会员,但是没有生效...",
+        "createTime": "2023-10-27 10:00:00",
+        "nickname": "测试用户A",
+        "email": "test@example.com",
+        "appleUserId": "000000.89347589237589.0000",
+        "originalTransactionId": "200000000000000",
+        "platform": "IOS",
+        "language": "zh-CN"
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1
+  }
+}
+```

+ 88 - 0
doc/USER_INVITE_API.md

@@ -0,0 +1,88 @@
+# 用户邀请记录 API 文档
+
+本文档专门描述用户邀请记录管理相关的接口。
+
+## 1. 基础信息
+
+*   **URL 前缀**: `/v2/user/invite`
+*   **响应格式**: 统一使用 `GlobalResponseWrapper`
+
+---
+
+## 2. 接口列表
+
+### 2.1 获取用户邀请记录列表
+
+*   **URL**: `/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户邀请记录列表。接口会同时返回**邀请人**和**被邀请人**的详细信息,并支持通过这些用户属性进行搜索。
+
+#### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `userId` | integer | 否 | 精确筛选用户ID(匹配邀请人或被邀请人) |
+| `keyword` | string | 否 | **多字段模糊搜索**。支持匹配邀请人或被邀请人的以下字段:<br>1. 昵称 (`nickname`)<br>2. 邮箱 (`email`)<br>3. 苹果ID (`appleUserId`)<br>4. 原始交易ID (`originalTransactionId`) |
+| `startTime` | string | 否 | 开始时间 (yyyy-MM-dd HH:mm:ss) |
+| `endTime` | string | 否 | 结束时间 (yyyy-MM-dd HH:mm:ss) |
+
+#### 响应参数 (`data` 字段)
+
+返回 Mybatis-Plus `Page` 对象,`records` 列表中的元素结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | integer | 记录ID |
+| `userId` | integer | 邀请人ID |
+| `inviteeId` | integer | 被邀请人ID |
+| `createTime` | string | 创建时间 |
+| **邀请人信息** | | |
+| `inviterNickname` | string | 邀请人昵称 |
+| `inviterEmail` | string | 邀请人邮箱 |
+| `inviterAppleUserId` | string | 邀请人苹果ID |
+| `inviterOriginalTransactionId` | string | 邀请人原始交易ID |
+| `inviterIsVip` | integer | 邀请人是否VIP (0:否 1:是) |
+| `inviterPlatform` | string | 邀请人平台 |
+| `inviterLanguage` | string | 邀请人语言 |
+| **被邀请人信息** | | |
+| `inviteeNickname` | string | 被邀请人昵称 |
+| `inviteeEmail` | string | 被邀请人邮箱 |
+| `inviteeAppleUserId` | string | 被邀请人苹果ID |
+| `inviteeOriginalTransactionId` | string | 被邀请人原始交易ID |
+| `inviteeIsVip` | integer | 被邀请人是否VIP (0:否 1:是) |
+| `inviteePlatform` | string | 被邀请人平台 |
+| `inviteeLanguage` | string | 被邀请人语言 |
+
+#### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "data": {
+    "records": [
+      {
+        "id": 1001,
+        "userId": 10086,
+        "inviteeId": 10087,
+        "createTime": "2023-10-27 10:00:00",
+        "inviterNickname": "邀请大师",
+        "inviterEmail": "inviter@example.com",
+        "inviterIsVip": 1,
+        "inviterPlatform": "IOS",
+        "inviterLanguage": "zh-CN",
+        "inviteeNickname": "新用户小明",
+        "inviteeEmail": "newbie@example.com",
+        "inviteeIsVip": 0,
+        "inviteePlatform": "ANDROID",
+        "inviteeLanguage": "en-US"
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1
+  }
+}
+```

+ 89 - 0
doc/USER_MESSAGE_API.md

@@ -0,0 +1,89 @@
+# 用户消息 API 文档
+
+本文档专门描述用户消息管理相关的接口。
+
+## 1. 基础信息
+
+*   **URL 前缀**: `/v2/user/message`
+*   **响应格式**: 统一使用 `GlobalResponseWrapper`
+
+---
+
+## 2. 接口列表
+
+### 2.1 获取用户消息列表
+
+*   **URL**: `/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户消息列表。
+
+#### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `userId` | integer | 否 | 精确筛选用户ID |
+| `orderId` | string | 否 | 精确筛选订单ID |
+| `albumId` | integer | 否 | 精确筛选相册ID |
+| `startTime` | string | 否 | 开始时间 (yyyy-MM-dd HH:mm:ss) |
+| `endTime` | string | 否 | 结束时间 (yyyy-MM-dd HH:mm:ss) |
+
+#### 响应参数 (`data` 字段)
+
+返回 Mybatis-Plus `Page` 对象,`records` 列表中的元素结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | integer | 消息ID |
+| `userId` | integer | 用户ID |
+| `title` | string | 消息标题 |
+| `content` | string | 消息内容 |
+| `thumbUrl` | string | 缩略图 |
+| `type` | integer | 消息类型 (0:订单已支付 1:训练分身 2:训练分身完成 3:训练写真 4:训练写真完成) |
+| `orderId` | string | 订单ID |
+| `albumId` | integer | 相册ID |
+| `createTime` | string | 创建时间 |
+| `updateTime` | string | 最后更新时间 |
+| `readStatus` | integer | 已读状态 (0:未读 1:已读) |
+| `nickname` | string | **用户昵称** |
+| `email` | string | **用户邮箱** |
+| `appleUserId` | string | **苹果ID** |
+| `originalTransactionId` | string | **原始交易ID** |
+| `platform` | string | **用户平台** |
+| `language` | string | **用户语言** |
+
+#### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "data": {
+    "records": [
+      {
+        "id": 501,
+        "userId": 10086,
+        "title": "系统通知",
+        "content": "您的分身训练已完成...",
+        "thumbUrl": "https://example.com/thumb.jpg",
+        "type": 2,
+        "orderId": "20231027001",
+        "albumId": 101,
+        "createTime": "2023-10-27 10:00:00",
+        "updateTime": "2023-10-27 10:00:00",
+        "readStatus": 0,
+        "nickname": "测试用户A",
+        "email": "test@example.com",
+        "appleUserId": "000000.89347589237589.0000",
+        "originalTransactionId": "200000000000000",
+        "platform": "IOS",
+        "language": "zh-CN"
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1
+  }
+}
+```

+ 78 - 0
doc/USER_POINTS_ORDER_API.md

@@ -0,0 +1,78 @@
+# 用户积分订单 API 文档
+
+本文档描述了用户积分订单相关的接口规范。
+
+## 1. 积分订单列表查询
+
+*   **URL**: `/v2/user/points/order/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户的积分订单记录,包含订单详情及关联的用户基础信息。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `userId` | long | 否 | 用户ID |
+| `productId` | integer | 否 | 产品ID |
+| `startTime` | string | 否 | 开始时间 (yyyy-MM-dd HH:mm:ss) |
+| `endTime` | string | 否 | 结束时间 (yyyy-MM-dd HH:mm:ss) |
+
+### 响应参数 (`data` 字段)
+
+返回 Mybatis-Plus `Page` 对象,包含 `records` (订单列表), `total` (总数), `size` (页大小), `current` (当前页) 等。
+
+`records` 中的对象 (`UserPointsOrderVO`) 结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | long | 订单ID |
+| `userId` | long | 用户ID |
+| `productId` | integer | 产品ID |
+| `points` | integer | 积分数量 |
+| `transactionId` | string | 交易ID (第三方支付流水号) |
+| `payPrice` | decimal | 支付价格 |
+| `createTime` | string | 订单创建时间 |
+| `nickname` | string | 用户昵称 |
+| `email` | string | 用户邮箱 |
+| `appleUserId` | string | 苹果用户ID |
+| `originalTransactionId` | string | 原始交易ID |
+| `platform` | string | 平台 |
+| `language` | string | 语言 |
+
+### 示例请求
+
+```http
+GET /v2/user/points/order/list?page=1&size=10&userId=12345
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "records": [
+      {
+        "id": 1001,
+        "userId": 12345,
+        "productId": 1,
+        "points": 100,
+        "transactionId": "TXN_123456789",
+        "payPrice": 9.99,
+        "createTime": "2023-10-27 10:00:00",
+        "nickname": "User123",
+        "email": "user@example.com"
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1,
+    "pages": 1
+  },
+  "timestamp": 1698372000000
+}
+```

+ 82 - 0
doc/USER_POINTS_RECORD_API.md

@@ -0,0 +1,82 @@
+# 用户积分记录 API 文档
+
+本文档描述了用户积分变更记录相关的接口规范。
+
+## 1. 积分变动记录查询
+
+*   **URL**: `/v2/user/points/record/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户的积分变动记录,包含变动详情及关联的用户基础信息。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `userId` | long | 否 | 用户ID |
+| `source` | integer | 否 | 来源 1:购买 2:兑换码 3:签到 4:分享 5:邀请 6:被邀请 7:邀请人使用模板 8:注册 9:投稿 10:评论 11:会员 |
+| `startTime` | string | 否 | 开始时间 (yyyy-MM-dd HH:mm:ss) |
+| `endTime` | string | 否 | 结束时间 (yyyy-MM-dd HH:mm:ss) |
+
+### 响应参数 (`data` 字段)
+
+返回 Mybatis-Plus `Page` 对象,包含 `records` (记录列表), `total` (总数), `size` (页大小), `current` (当前页) 等。
+
+`records` 中的对象 (`UserPointsRecordVO`) 结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | long | 记录ID |
+| `userId` | long | 用户ID |
+| `points` | long | 变动点数 |
+| `source` | integer | 来源 1:购买 2:兑换码 3:签到 4:分享 5:邀请 6:被邀请 7:邀请人使用模板 8:注册 9:投稿 10:评论 11:会员 |
+| `expireTime` | string | 过期时间 |
+| `extParam` | string | 额外参数 |
+| `createTime` | string | 创建时间 |
+| `nickname` | string | 用户昵称 |
+| `email` | string | 用户邮箱 |
+| `appleUserId` | string | 苹果用户ID |
+| `originalTransactionId` | string | 原始交易ID |
+| `platform` | string | 平台 |
+| `language` | string | 语言 |
+
+### 示例请求
+
+```http
+GET /v2/user/points/record/list?page=1&size=10&userId=12345&source=1
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "records": [
+      {
+        "id": 1001,
+        "userId": 12345,
+        "points": 50,
+        "source": 1,
+        "expireTime": "2024-10-27 10:00:00",
+        "extParam": "{\"orderId\":\"123\"}",
+        "createTime": "2023-10-27 10:00:00",
+        "nickname": "User123",
+        "email": "user@example.com",
+        "appleUserId": "apple_123",
+        "originalTransactionId": "txn_123",
+        "platform": "IOS",
+        "language": "zh-CN"
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1,
+    "pages": 1
+  },
+  "timestamp": 1698372000000
+}
+```

+ 108 - 0
doc/USER_POINTS_STATEMENT_API.md

@@ -0,0 +1,108 @@
+# 用户积分流水 API 文档
+
+本文档描述了用户积分流水相关的接口规范,整合了积分收入和支出记录。
+
+## 1. 积分流水查询 (收入+支出)
+
+*   **URL**: `/v2/user/points/record/statement`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户的积分流水记录,统一展示积分的收入和支出情况。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `userId` | long | 否 | 用户ID |
+| `type` | integer | 否 | 流水类型 1:收入 2:支出(不传则查全部) |
+| `startTime` | string | 否 | 开始时间 (yyyy-MM-dd HH:mm:ss) |
+| `endTime` | string | 否 | 结束时间 (yyyy-MM-dd HH:mm:ss) |
+
+### 响应参数 (`data` 字段)
+
+返回 Mybatis-Plus `Page` 对象,包含 `records` (记录列表), `total` (总数), `size` (页大小), `current` (当前页) 等。
+
+`records` 中的对象 (`UserPointsStatementVO`) 结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `userId` | long | 用户ID |
+| `type` | integer | 流水类型 1:收入 2:支出 |
+| `points` | long | 变动点数 |
+| `title` | string | 标题/描述(仅支出记录有值,收入记录为 null,前端根据 businessType 自行展示) |
+| `businessType` | integer | 来源(仅收入记录)1:购买 2:兑换码 3:签到 4:分享 5:邀请 6:被邀请 7:邀请人使用模板 8:注册 9:投稿 10:评论 11:会员;支出记录为 null |
+| `createTime` | string | 创建时间 |
+| `nickname` | string | 用户昵称 |
+| `email` | string | 用户邮箱 |
+| `appleUserId` | string | 苹果用户ID |
+| `originalTransactionId` | string | 原始交易ID |
+| `platform` | string | 平台 |
+| `language` | string | 语言 |
+
+### 示例请求
+
+查询全部流水:
+
+```http
+GET /v2/user/points/record/statement?page=1&size=10&userId=12345
+```
+
+仅查询收入:
+
+```http
+GET /v2/user/points/record/statement?page=1&size=10&userId=12345&type=1
+```
+
+仅查询支出:
+
+```http
+GET /v2/user/points/record/statement?page=1&size=10&userId=12345&type=2
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "records": [
+      {
+        "userId": 12345,
+        "type": 1,
+        "points": 100,
+        "title": null,
+        "businessType": 1,
+        "createTime": "2023-10-27 10:00:00",
+        "nickname": "User123",
+        "email": "user@example.com",
+        "appleUserId": "apple_123",
+        "originalTransactionId": "txn_123",
+        "platform": "IOS",
+        "language": "zh-CN"
+      },
+      {
+        "userId": 12345,
+        "type": 2,
+        "points": 50,
+        "title": "购买道具",
+        "businessType": null,
+        "createTime": "2023-10-28 14:30:00",
+        "nickname": "User123",
+        "email": "user@example.com",
+        "appleUserId": "apple_123",
+        "originalTransactionId": "txn_123",
+        "platform": "IOS",
+        "language": "zh-CN"
+      }
+    ],
+    "total": 2,
+    "size": 10,
+    "current": 1,
+    "pages": 1
+  },
+  "timestamp": 1698372000000
+}
+```

+ 84 - 0
doc/USER_POINTS_USAGE_API.md

@@ -0,0 +1,84 @@
+# 用户积分使用记录 API 文档
+
+本文档描述了用户积分使用记录相关的接口规范。
+
+## 1. 积分使用记录查询
+
+*   **URL**: `/v2/user/points/usage/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户的积分使用记录,包含使用详情及关联的用户基础信息。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `userId` | long | 否 | 用户ID |
+| `keyword` | string | 否 | 关键词 (标题或订单ID) |
+| `startTime` | string | 否 | 开始时间 (yyyy-MM-dd HH:mm:ss) |
+| `endTime` | string | 否 | 结束时间 (yyyy-MM-dd HH:mm:ss) |
+
+### 响应参数 (`data` 字段)
+
+返回 Mybatis-Plus `Page` 对象,包含 `records` (记录列表), `total` (总数), `size` (页大小), `current` (当前页) 等。
+
+`records` 中的对象 (`UserPointsUsageVO`) 结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | long | 记录ID |
+| `userId` | long | 用户ID |
+| `points` | long | 消耗点数 |
+| `recordId` | long | 变更记录ID |
+| `orderId` | string | 订单ID |
+| `title` | string | 标题 |
+| `thumbUrl` | string | 缩略图 |
+| `createTime` | string | 创建时间 |
+| `nickname` | string | 用户昵称 |
+| `email` | string | 用户邮箱 |
+| `appleUserId` | string | 苹果用户ID |
+| `originalTransactionId` | string | 原始交易ID |
+| `platform` | string | 平台 |
+| `language` | string | 语言 |
+
+### 示例请求
+
+```http
+GET /v2/user/points/usage/list?page=1&size=10&userId=12345&keyword=购买
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "records": [
+      {
+        "id": 1001,
+        "userId": 12345,
+        "points": 50,
+        "recordId": 2001,
+        "orderId": "ORDER_123",
+        "title": "购买商品A",
+        "thumbUrl": "http://example.com/thumb.jpg",
+        "createTime": "2023-10-27 10:00:00",
+        "nickname": "User123",
+        "email": "user@example.com",
+        "appleUserId": "apple_123",
+        "originalTransactionId": "txn_123",
+        "platform": "IOS",
+        "language": "zh-CN"
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1,
+    "pages": 1
+  },
+  "timestamp": 1698372000000
+}
+```

+ 82 - 0
doc/USER_REDEMPTION_RECORD_API.md

@@ -0,0 +1,82 @@
+# 用户兑换记录 API 文档
+
+本文档描述了用户兑换记录相关的接口规范。
+
+## 1. 兑换记录列表
+
+*   **URL**: `/v2/user/redemption/record/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户兑换记录,包含兑换详情及关联的用户基础信息。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `userId` | long | 否 | 用户ID |
+| `redemptionCode` | string | 否 | 兑换码(支持模糊搜索) |
+| `startTime` | string | 否 | 开始时间 (yyyy-MM-dd HH:mm:ss) |
+| `endTime` | string | 否 | 结束时间 (yyyy-MM-dd HH:mm:ss) |
+
+### 响应参数 (`data` 字段)
+
+返回 Mybatis-Plus `Page` 对象,包含 `records` (记录列表), `total` (总数), `size` (页大小), `current` (当前页) 等。
+
+`records` 中的对象 (`UserRedemptionRecordVO`) 结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | long | 记录ID |
+| `userId` | long | 用户ID |
+| `redemptionId` | long | 兑换码ID |
+| `redemptionCode` | string | 兑换码 |
+| `points` | long | 获得点数 |
+| `expireTime` | string | 积分失效时间 |
+| `createTime` | string | 兑换时间 |
+| `nickname` | string | 用户昵称 |
+| `email` | string | 用户邮箱 |
+| `appleUserId` | string | 苹果用户ID |
+| `originalTransactionId` | string | 原始交易ID |
+| `platform` | string | 平台 |
+| `language` | string | 语言 |
+
+### 示例请求
+
+```http
+GET /v2/user/redemption/record/list?page=1&size=10&userId=10001&redemptionCode=VIP
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "records": [
+      {
+        "id": 5001,
+        "userId": 10001,
+        "redemptionId": 200,
+        "redemptionCode": "VIP888",
+        "points": 100,
+        "expireTime": "2024-12-31 23:59:59",
+        "createTime": "2023-11-01 10:00:00",
+        "nickname": "TestUser",
+        "email": "test@example.com",
+        "appleUserId": "apple_001",
+        "originalTransactionId": "txn_001",
+        "platform": "IOS",
+        "language": "en-US"
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1,
+    "pages": 1
+  },
+  "timestamp": 1698804000000
+}
+```

+ 88 - 0
doc/USER_SUBSCRIBE_ORDER_API.md

@@ -0,0 +1,88 @@
+# 用户订阅订单 API 文档
+
+本文档描述了用户订阅订单相关的接口规范。
+
+## 1. 订阅订单列表
+
+*   **URL**: `/v2/user/subscribe/order/list`
+*   **Method**: `GET`
+*   **描述**: 分页查询用户订阅订单,包含订单详情、关联的用户基础信息及订阅配置信息。
+
+### 请求参数
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `page` | integer | 否 | 页码,默认 1 |
+| `size` | integer | 否 | 每页大小,默认 10 |
+| `userId` | long | 否 | 用户ID |
+| `productId` | string | 否 | 产品ID |
+| `transactionId` | string | 否 | 交易ID |
+| `originalTransactionId` | string | 否 | 原始交易ID |
+| `startTime` | string | 否 | 开始时间 (yyyy-MM-dd HH:mm:ss) |
+| `endTime` | string | 否 | 结束时间 (yyyy-MM-dd HH:mm:ss) |
+
+### 响应参数 (`data` 字段)
+
+返回 Mybatis-Plus `Page` 对象,包含 `records` (记录列表), `total` (总数), `size` (页大小), `current` (当前页) 等。
+
+`records` 中的对象 (`UserSubscribeOrderVO`) 结构如下:
+
+| 字段名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `id` | long | 订单ID |
+| `userId` | long | 用户ID |
+| `productId` | string | 产品ID |
+| `subscribeId` | integer | 订阅ID |
+| `originalTransactionId` | string | 原始交易ID |
+| `transactionId` | string | 交易ID |
+| `expireTime` | string | 订阅过期时间 |
+| `createTime` | string | 创建时间 |
+| `nickname` | string | 用户昵称 |
+| `email` | string | 用户邮箱 |
+| `appleUserId` | string | 苹果用户ID |
+| `platform` | string | 平台 |
+| `language` | string | 语言 |
+| `subscribeType` | integer | 订阅类型(0:连续包月,1:连续包年) |
+| `subscribeTokens` | integer | 订阅赠送积分 |
+
+### 示例请求
+
+```http
+GET /v2/user/subscribe/order/list?page=1&size=10&userId=10001&productId=com.aitori.monthly
+```
+
+### 示例响应
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "success": true,
+  "data": {
+    "records": [
+      {
+        "id": 8001,
+        "userId": 10001,
+        "productId": "com.aitori.monthly",
+        "subscribeId": 1,
+        "originalTransactionId": "txn_orig_001",
+        "transactionId": "txn_curr_001",
+        "expireTime": "2024-11-30 23:59:59",
+        "createTime": "2023-11-01 10:00:00",
+        "nickname": "TestUser",
+        "email": "test@example.com",
+        "appleUserId": "apple_001",
+        "platform": "IOS",
+        "language": "en-US",
+        "subscribeType": 0,
+        "subscribeTokens": 500
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1,
+    "pages": 1
+  },
+  "timestamp": 1698805000000
+}
+```

+ 257 - 0
docs/dashboard-api.md

@@ -0,0 +1,257 @@
+# 仪表盘 API 文档
+
+基础路径:`/v2/dashboard`
+
+所有接口响应均由全局 `R<T>` 包装,实际返回格式为:
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "data": { ... },
+  "success": true,
+  "timestamp": 1710230400000
+}
+```
+
+以下文档中 **响应示例** 均为 `data` 字段内的内容。
+
+---
+
+## 1. 今日数据总览
+
+获取今日核心运营指标,每项指标包含今日值、昨日值及环比变化率。
+
+**请求**
+
+```
+GET /v2/dashboard/today-overview
+```
+
+**请求参数**:无
+
+**响应字段**
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| `newUsers` | `MetricItemVO` | 今日新增用户 |
+| `rechargePoints` | `MetricItemVO` | 今日充值积分(新增积分总量) |
+| `consumedPoints` | `MetricItemVO` | 今日消耗积分(用户使用消耗) |
+| `newVipCount` | `MetricItemVO` | 今日新增 VIP 人数 |
+
+**MetricItemVO 结构**
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| `today` | `Long` | 今日数值 |
+| `yesterday` | `Long` | 昨日数值 |
+| `changeRate` | `BigDecimal` | 环比变化率(%),正数增长,负数下降;昨日为 0 时:今日 > 0 则为 100,否则为 null |
+
+**响应示例**
+
+```json
+{
+  "newUsers": {
+    "today": 128,
+    "yesterday": 95,
+    "changeRate": 34.74
+  },
+  "rechargePoints": {
+    "today": 56000,
+    "yesterday": 42000,
+    "changeRate": 33.33
+  },
+  "consumedPoints": {
+    "today": 38500,
+    "yesterday": 41000,
+    "changeRate": -6.10
+  },
+  "newVipCount": {
+    "today": 12,
+    "yesterday": 8,
+    "changeRate": 50.00
+  }
+}
+```
+
+---
+
+## 2. 任务状态概览
+
+获取当前系统内所有订单任务按状态分组的数量统计。
+
+**请求**
+
+```
+GET /v2/dashboard/task-overview
+```
+
+**请求参数**:无
+
+**响应字段**(数组)
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| `taskStatus` | `Integer` | 任务状态值,`null` 表示待处理 |
+| `statusLabel` | `String` | 状态中文描述 |
+| `count` | `Long` | 该状态的任务数量 |
+
+**任务状态枚举**
+
+| taskStatus | statusLabel | 说明 |
+|---|---|---|
+| `null` | 待处理 | 尚未开始处理的订单 |
+| `0` | 异常失败 | 任务异常中断 |
+| `1` | 任务执行中 | 正在处理中 |
+| `2` | 执行完成 | 任务成功完成 |
+| `3` | 执行失败 | 任务执行后失败 |
+
+**响应示例**
+
+```json
+[
+  {
+    "taskStatus": null,
+    "statusLabel": "待处理",
+    "count": 15
+  },
+  {
+    "taskStatus": 0,
+    "statusLabel": "异常失败",
+    "count": 3
+  },
+  {
+    "taskStatus": 1,
+    "statusLabel": "任务执行中",
+    "count": 28
+  },
+  {
+    "taskStatus": 2,
+    "statusLabel": "执行完成",
+    "count": 1520
+  },
+  {
+    "taskStatus": 3,
+    "statusLabel": "执行失败",
+    "count": 12
+  }
+]
+```
+
+---
+
+## 3. 趋势数据
+
+按周(近 7 天)或月(近 30 天)维度获取每日新增用户和新增 VIP 数据,适配折线图 / 柱状图展示。无数据的日期自动补 0。
+
+**请求**
+
+```
+GET /v2/dashboard/trend?type=week
+```
+
+**请求参数**
+
+| 参数 | 类型 | 必填 | 默认值 | 说明 |
+|---|---|---|---|---|
+| `type` | `String` | 否 | `week` | 时间维度:`week`(近 7 天)/ `month`(近 30 天) |
+
+**响应字段**(数组)
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| `date` | `String` | 日期,格式 `yyyy-MM-dd` |
+| `newUsers` | `Long` | 当日新增用户数 |
+| `newVipCount` | `Long` | 当日新增 VIP 人数 |
+
+**响应示例**(`type=week`)
+
+```json
+[
+  { "date": "2026-03-06", "newUsers": 82,  "newVipCount": 5  },
+  { "date": "2026-03-07", "newUsers": 95,  "newVipCount": 7  },
+  { "date": "2026-03-08", "newUsers": 110, "newVipCount": 4  },
+  { "date": "2026-03-09", "newUsers": 78,  "newVipCount": 6  },
+  { "date": "2026-03-10", "newUsers": 130, "newVipCount": 11 },
+  { "date": "2026-03-11", "newUsers": 105, "newVipCount": 8  },
+  { "date": "2026-03-12", "newUsers": 42,  "newVipCount": 3  }
+]
+```
+
+---
+
+## 4. 系统数据概览
+
+获取系统整体累计数据,包括总用户、总模版、任务完成情况及会员用户数。
+
+**请求**
+
+```
+GET /v2/dashboard/system-overview
+```
+
+**请求参数**:无
+
+**响应字段**
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| `totalUsers` | `Long` | 总用户数 |
+| `totalTemplates` | `Long` | 总模版数(未删除) |
+| `completedTasks` | `Long` | 已完成任务数(task_status = 2) |
+| `failedTasks` | `Long` | 失败任务数(task_status = 0 或 3) |
+| `vipUsers` | `Long` | 当前会员用户数(is_vip = 1) |
+
+**响应示例**
+
+```json
+{
+  "totalUsers": 25680,
+  "totalTemplates": 134,
+  "completedTasks": 189432,
+  "failedTasks": 1256,
+  "vipUsers": 3842
+}
+```
+
+---
+
+## 5. 进行中任务数
+
+获取当前系统中正在执行的任务数量(task_status = 1),可用于实时监控任务队列。
+
+**请求**
+
+```
+GET /v2/dashboard/running-tasks
+```
+
+**请求参数**:无
+
+**响应字段**
+
+直接返回 `Long` 类型数值。
+
+**响应示例**
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "data": 28,
+  "success": true,
+  "timestamp": 1710230400000
+}
+```
+
+---
+
+## 接口总览
+
+| # | 方法 | 路径 | 说明 | 用途 |
+|---|---|---|---|---|
+| 1 | GET | `/v2/dashboard/today-overview` | 今日数据总览 | 仪表盘顶部统计卡片,展示今日与昨日对比 |
+| 2 | GET | `/v2/dashboard/task-overview` | 任务状态概览 | 饼图 / 环形图,展示各状态任务分布 |
+| 3 | GET | `/v2/dashboard/trend?type=week\|month` | 趋势数据 | 折线图 / 柱状图,展示用户和 VIP 增长趋势 |
+| 4 | GET | `/v2/dashboard/system-overview` | 系统数据概览 | 仪表盘底部统计卡片,展示系统累计数据 |
+| 5 | GET | `/v2/dashboard/running-tasks` | 进行中任务数 | 实时监控当前执行中的任务数量 |

+ 252 - 0
pom.xml

@@ -0,0 +1,252 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.5.5</version>
+        <relativePath/>
+    </parent>
+    <groupId>com.senseauto.lion</groupId>
+    <artifactId>aitori-bi-v2</artifactId>
+    <version>1.2.1</version>
+    <name>aitori-admin-server</name>
+    <description>
+        基于aitori项目重构
+    </description>
+    <developers>
+        <developer>
+            <name>chenyu</name>
+            <email>jchenpx@gmail.com</email>
+        </developer>
+    </developers>
+
+    <properties>
+        <java.version>21</java.version>
+        <mybatis-plus.version>3.5.12</mybatis-plus.version>
+        <hutool.version>5.8.39</hutool.version>
+        <sa-token.version>1.44.0</sa-token.version>
+        <knife4j.version>4.5.0</knife4j.version>
+        <commons-lang3.version>3.18.0</commons-lang3.version>
+        <forest.version>1.8.0</forest.version>
+        <springdoc.version>2.8.9</springdoc.version>
+        <logstash-logback-encoder.version>7.4</logstash-logback-encoder.version>
+        <okhttp.version>5.1.0</okhttp.version>
+        <micrometer-registry-prometheus.version>1.15.2</micrometer-registry-prometheus.version>
+        <auth0.version>4.4.0</auth0.version>
+        <shedlock.version>7.3.0</shedlock.version>
+        <easy.captcha.version>1.6.2</easy.captcha.version>
+        <google.cloud.storage.version>2.4.2</google.cloud.storage.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-jsqlparser</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
+            <version>${knife4j.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springdoc</groupId>
+                    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+            <version>${springdoc.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>${commons-lang3.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate.validator</groupId>
+            <artifactId>hibernate-validator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>${hutool.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>net.logstash.logback</groupId>
+            <artifactId>logstash-logback-encoder</artifactId>
+            <version>${logstash-logback-encoder.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.dtflys.forest</groupId>
+            <artifactId>forest-spring-boot3-starter</artifactId>
+            <version>${forest.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+            <version>${micrometer-registry-prometheus.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>${auth0.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>net.javacrumbs.shedlock</groupId>
+            <artifactId>shedlock-spring</artifactId>
+            <version>${shedlock.version}</version>
+        </dependency>
+
+        <!-- JDBC + MySQL -->
+        <dependency>
+            <groupId>net.javacrumbs.shedlock</groupId>
+            <artifactId>shedlock-provider-jdbc-template</artifactId>
+            <version>${shedlock.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.whvcse</groupId>
+            <artifactId>easy-captcha</artifactId>
+            <version>${easy.captcha.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot3-starter</artifactId>
+            <version>${sa-token.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-redis-template</artifactId>
+            <version>${sa-token.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.cloud</groupId>
+            <artifactId>google-cloud-storage</artifactId>
+            <version>${google.cloud.storage.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>org.springframework.boot</groupId>
+                            <artifactId>spring-boot-configuration-processor</artifactId>
+                        </path>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope>
+                    <jvmArguments>
+                        -Djava.library.path=${project.build.directory}/native_libs
+                    </jvmArguments>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <!-- Maven Resources 插件 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-native-libs</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/native_libs</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/resources/native_libs</directory>
+                                    <includes>
+                                        <include>*.so</include>
+                                    </includes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 13 - 0
src/main/java/com/aitori/Application.java

@@ -0,0 +1,13 @@
+package com.aitori;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+
+}

+ 11 - 0
src/main/java/com/aitori/annotation/LogRecord.java

@@ -0,0 +1,11 @@
+package com.aitori.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LogRecord {
+}

+ 15 - 0
src/main/java/com/aitori/annotation/SkipLogRecord.java

@@ -0,0 +1,15 @@
+package com.aitori.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 跳过日志记录注解
+ * 被此注解标记的方法将不会被日志切面拦截
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SkipLogRecord {
+}

+ 15 - 0
src/main/java/com/aitori/annotation/SkipResponseWrapper.java

@@ -0,0 +1,15 @@
+package com.aitori.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 跳过全局响应包装注解
+ * 被此注解标记的方法将不会被全局响应包装器包装
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SkipResponseWrapper {
+}

+ 54 - 0
src/main/java/com/aitori/aop/log/ForestLogHandler.java

@@ -0,0 +1,54 @@
+package com.aitori.aop.log;
+
+import cn.hutool.json.JSONUtil;
+import com.dtflys.forest.http.ForestResponse;
+import com.dtflys.forest.logging.DefaultLogHandler;
+import com.dtflys.forest.logging.RequestLogMessage;
+import com.dtflys.forest.logging.ResponseLogMessage;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ForestLogHandler extends DefaultLogHandler {
+
+    /**
+     * 该方法生成Forest请求的日志内容字符串
+     *
+     * @param requestLogMessage 请求日志字符串
+     * @return 日志内容字符串
+     */
+    @Override
+    protected String requestLoggingContent(RequestLogMessage requestLogMessage) {
+        Map<String, Object> attributes = new HashMap<>();
+        attributes.put("retryContent", retryContent(requestLogMessage));
+        attributes.put("proxyContent", proxyContent(requestLogMessage));
+        attributes.put("requestTypeChangeHistory", requestTypeChangeHistory(requestLogMessage));
+        attributes.put("requestLogMessage", requestLogMessage.getRequestLine());
+        attributes.put("requestLoggingHeaders", requestLoggingHeaders(requestLogMessage));
+        attributes.put("requestLoggingBody", requestLoggingBody(requestLogMessage));
+        return JSONUtil.toJsonStr(attributes);
+    }
+
+    /**
+     * 该方法生成Forest请求响应结果的日志内容字符串
+     *
+     * @param responseLogMessage 请求响应日志字符串
+     * @return 日志内容字符串
+     */
+    @Override
+    protected String responseLoggingContent(ResponseLogMessage responseLogMessage) {
+        ForestResponse<?> response = responseLogMessage.getResponse();
+        if (response != null && response.getException() != null) {
+            return "[网络错误]: " + response.getException().getMessage();
+        }
+        // 获取请求响应状态码
+        int status = responseLogMessage.getStatus();
+        // 获取请求响应时间
+        long time = responseLogMessage.getTime();
+        if (status >= 0) {
+            return "请求响应: 状态码: " + status + ", 耗时: " + time + "ms";
+        } else {
+            return "[网络错误]: 未知的网络错误!";
+        }
+    }
+}

+ 35 - 0
src/main/java/com/aitori/aop/log/LogAspect.java

@@ -0,0 +1,35 @@
+package com.aitori.aop.log;
+
+import lombok.RequiredArgsConstructor;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+@Aspect
+@Component
+@RequiredArgsConstructor
+public class LogAspect {
+
+    private final LogHandler logHandler;
+
+    /**
+     * 拦截controller包下的所有方法,或者被@LogRecord注解标记的方法
+     * 但排除被@SkipLogRecord注解标记的方法
+     */
+    @Around("(execution(* com.aitori.controller..*(..)) || @annotation(com.aitori.annotation.LogRecord)) " +
+            "&& !@annotation(com.aitori.annotation.SkipLogRecord)")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        // 非 Web 环境不记录
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes == null) {
+            return joinPoint.proceed();
+        }
+        
+        // 委托给 LogHandler 处理
+        return logHandler.handle(joinPoint);
+    }
+
+}

+ 234 - 0
src/main/java/com/aitori/aop/log/LogHandler.java

@@ -0,0 +1,234 @@
+package com.aitori.aop.log;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.MDC;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class LogHandler {
+
+    private final ObjectMapper objectMapper;
+
+    private static final String TRACE_ID = "traceId";
+    private static final int MAX_RESPONSE_LENGTH = 400;
+
+    /**
+     * 处理日志记录
+     */
+    public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
+        Instant startTime = Instant.now();
+
+        // 生成并设置 traceId
+        String traceId = generateTraceId();
+        MDC.put(TRACE_ID, traceId);
+
+        LogInfo logInfo = new LogInfo();
+        logInfo.setTraceId(traceId);
+        logInfo.setStartTime(startTime);
+
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes != null) {
+            HttpServletRequest request = attributes.getRequest();
+            logInfo.setMethod(request.getMethod());
+            logInfo.setRequestUrl(request.getRequestURI());
+        }
+
+        // 获取方法名作为 API 名称
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        String apiName = signature.getDeclaringType().getSimpleName() + "." + signature.getName();
+        logInfo.setApiName(apiName);
+
+        // 记录请求参数(过滤不适合序列化的对象)
+        Object[] args = joinPoint.getArgs();
+        if (args != null && args.length > 0) {
+            Object filteredArgs = filterRequestArgs(args);
+            logInfo.setRequest(filteredArgs);
+        }
+
+        Object result = null;
+        try {
+            // 执行目标方法
+            result = joinPoint.proceed();
+            if (result != null) {
+                // 过滤响应结果(避免序列化不适合的对象)
+                Object filteredResult = filterResponseResult(result);
+                logInfo.setResponse(filteredResult);
+            }
+            return result;
+        } catch (Throwable throwable) {
+            logInfo.setErrorMsg(throwable.getMessage());
+            throw throwable;
+        } finally {
+            // 记录结束时间和耗时
+            Instant endTime = Instant.now();
+            logInfo.setEndTime(endTime);
+            logInfo.setCostTime((int) Duration.between(startTime, endTime).toMillis());
+
+            // 输出日志
+            printLog(logInfo);
+            // 注意:不在这里清理MDC,让traceId在整个请求链中保持可用
+            // MDC会在请求结束时由Servlet容器或其他机制自动清理
+            // 如果需要手动清理,应该在更上层的Filter中处理
+        }
+    }
+
+    /**
+     * 过滤请求参数,移除不适合序列化的对象
+     */
+    private Object filterRequestArgs(Object[] args) {
+        if (args == null || args.length == 0) {
+            return null;
+        }
+
+        List<Object> filteredArgs = new ArrayList<>();
+        for (Object arg : args) {
+            Object filteredArg = filterSingleArg(arg);
+            if (filteredArg != null) {
+                filteredArgs.add(filteredArg);
+            }
+        }
+
+        return filteredArgs.size() == 1 ? filteredArgs.get(0) : filteredArgs;
+    }
+
+    /**
+     * 过滤单个参数
+     */
+    private Object filterSingleArg(Object arg) {
+        if (arg == null) {
+            return null;
+        }
+
+        // 过滤不适合序列化的类型
+        if (isUnserializableType(arg)) {
+            return getObjectDescription(arg);
+        }
+
+        return arg;
+    }
+
+    /**
+     * 过滤响应结果
+     */
+    private Object filterResponseResult(Object result) {
+        if (result == null) {
+            return null;
+        }
+
+        // 如果是不适合序列化的类型,返回描述信息
+        if (isUnserializableType(result)) {
+            return getObjectDescription(result);
+        }
+
+        try {
+            objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+            String json = objectMapper.writeValueAsString(result);
+            if (json != null && json.length() > MAX_RESPONSE_LENGTH) {
+                return json.substring(0, MAX_RESPONSE_LENGTH) + "...";
+            }
+        } catch (Exception ignored) {
+        }
+        return result;
+    }
+
+    /**
+     * 判断是否为不适合序列化的类型
+     */
+    private boolean isUnserializableType(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+
+        Class<?> clazz = obj.getClass();
+
+        // Servlet相关对象
+        if (HttpServletRequest.class.isAssignableFrom(clazz) ||
+                HttpServletResponse.class.isAssignableFrom(clazz)) {
+            return true;
+        }
+
+        // 文件相关对象
+        if (MultipartFile.class.isAssignableFrom(clazz) ||
+                InputStream.class.isAssignableFrom(clazz) ||
+                OutputStream.class.isAssignableFrom(clazz)) {
+            return true;
+        }
+
+        // 检查包名,过滤Spring内部对象
+        String packageName = clazz.getPackage() != null ? clazz.getPackage().getName() : "";
+        return packageName.startsWith("org.springframework.web.context") ||
+                packageName.startsWith("org.springframework.web.multipart") ||
+                packageName.startsWith("jakarta.servlet");
+    }
+
+    /**
+     * 获取对象描述信息
+     */
+    private String getObjectDescription(Object obj) {
+        if (obj == null) {
+            return null;
+        }
+
+        Class<?> clazz = obj.getClass();
+
+        if (HttpServletRequest.class.isAssignableFrom(clazz)) {
+            return "[HttpServletRequest]";
+        } else if (HttpServletResponse.class.isAssignableFrom(clazz)) {
+            return "[HttpServletResponse]";
+        } else if (MultipartFile.class.isAssignableFrom(clazz)) {
+            MultipartFile file = (MultipartFile) obj;
+            return String.format("[MultipartFile: name=%s, size=%d]",
+                    file.getOriginalFilename(), file.getSize());
+        } else if (InputStream.class.isAssignableFrom(clazz)) {
+            return "[InputStream]";
+        } else if (OutputStream.class.isAssignableFrom(clazz)) {
+            return "[OutputStream]";
+        } else {
+            return String.format("[%s]", clazz.getSimpleName());
+        }
+    }
+
+    /**
+     * 生成 traceId
+     */
+    private String generateTraceId() {
+        return UUID.randomUUID().toString().replace("-", "");
+    }
+
+    /**
+     * 打印日志
+     */
+    private void printLog(LogInfo logInfo) {
+        try {
+            objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+            String logJson = objectMapper.writeValueAsString(logInfo);
+            if (logInfo.getErrorMsg() != null) {
+                log.error("API调用失败 - {}", logJson);
+            } else {
+                log.info("API调用记录 - {}", logJson);
+            }
+        } catch (Exception e) {
+            log.error("日志输出失败", e);
+        }
+    }
+}

+ 41 - 0
src/main/java/com/aitori/aop/log/LogInfo.java

@@ -0,0 +1,41 @@
+package com.aitori.aop.log;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+@Data
+public class LogInfo implements Serializable {
+
+    // API名称
+    private String apiName;
+
+    // 请求方法
+    private String method;
+
+    // 请求URL
+    private String requestUrl;
+
+    // 开始时间
+    private Instant startTime;
+
+    // 结束时间
+    private Instant endTime;
+
+    // 请求耗时
+    private Integer costTime;
+
+    // 追踪ID
+    private String traceId;
+
+    // 请求参数
+    private Object request;
+
+    // 响应结果
+    private Object response;
+
+    // 错误信息
+    private String errorMsg;
+
+}

+ 19 - 0
src/main/java/com/aitori/client/AuthClient.java

@@ -0,0 +1,19 @@
+//package com.aitori.client;
+//
+//import com.dtflys.forest.annotation.*;
+//import com.aitori.handler.ErrorInterceptor;
+//
+//@BaseRequest(baseURL = "${auth_host}")
+//public interface AuthClient {
+//
+//    @Retry(maxRetryCount = "3", maxRetryInterval = "10")
+//    @Post(url = "/cabin-client-id/findUserGroup", dataType = "json", interceptor = ErrorInterceptor.class)
+//    AuthUserGroupResponse findUserGroup(@JSONBody("client_id") String clientId);
+//
+//
+//    @Retry(maxRetryCount = "3", maxRetryInterval = "10")
+//    @Get(url = "/admin/credentials/getDecryptedCredential", headers = {"client-id: ${clientId}"}, dataType = "json", interceptor = ErrorInterceptor.class)
+//    DecryptedCredentialResponse getCredentials(@Var("clientId") String clientId,
+//                                               @Query("groupId") Integer groupId,
+//                                               @Query("productName") String productName);
+//}

+ 22 - 0
src/main/java/com/aitori/client/HealthModelClient.java

@@ -0,0 +1,22 @@
+//package com.aitori.client;
+//
+//import com.dtflys.forest.annotation.BaseRequest;
+//import com.dtflys.forest.annotation.JSONBody;
+//import com.dtflys.forest.annotation.Post;
+//import com.dtflys.forest.annotation.Var;
+//import com.dtflys.forest.callback.OnError;
+//import com.aitori.handler.HealthInquirySSEHandler;
+//
+//@BaseRequest(baseURL = "${model_health_host}")
+//public interface HealthModelClient {
+//
+//    /**
+//     * 流式聊天接口(SSE)
+//     * 注意:使用此接口时,需要在ModelRequest中设置stream=true
+//     */
+//    @Post(url = "/v1/llm/chat-completions", headers = {"Authorization: ${token}",
+//            "Content-Type: application/json", "Accept: text/event-stream"}
+//    )
+//    HealthInquirySSEHandler chatStream(@Var("token") String token, @JSONBody ModelRequest request, OnError onError);
+//
+//}

+ 88 - 0
src/main/java/com/aitori/config/GlobalResponseWrapper.java

@@ -0,0 +1,88 @@
+package com.aitori.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.aitori.annotation.SkipResponseWrapper;
+import com.aitori.dto.R;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jspecify.annotations.NonNull;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+/**
+ * 全局响应包装器
+ * 自动将 @RestController 接口的返回值包装到 R<T> 中
+ */
+@Slf4j
+@RestControllerAdvice
+@RequiredArgsConstructor
+public class GlobalResponseWrapper implements ResponseBodyAdvice<Object> {
+
+    private final ObjectMapper objectMapper;
+
+    @Override
+    public boolean supports(MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
+        // 检查是否需要包装
+        // 1. 如果方法上有 @SkipResponseWrapper 注解,则跳过
+        if (returnType.hasMethodAnnotation(SkipResponseWrapper.class)) {
+            return false;
+        }
+
+        // 2. 如果类上有 @SkipResponseWrapper 注解,则跳过
+        if (returnType.getDeclaringClass().isAnnotationPresent(SkipResponseWrapper.class)) {
+            return false;
+        }
+
+        // 3. 如果返回类型已经是 R 类型,则跳过(避免重复包装)
+        if (returnType.getParameterType().equals(R.class)) {
+            return false;
+        }
+
+        // 4. 如果是 Swagger 相关的接口,则跳过
+        String className = returnType.getDeclaringClass().getName();
+        if (className.contains("springdoc") || className.contains("swagger") ||  className.contains("actuate")) {
+            return false;
+        }
+
+        // 5. 如果是错误页面相关的接口,则跳过
+        return !className.contains("BasicErrorController");
+    }
+
+    @Override
+    public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType,
+                                  @NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
+                                  @NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response) {
+
+        // 如果返回值已经是 R 类型,直接返回(双重保险)
+        if (body instanceof R) {
+            return body;
+        }
+
+        // 如果返回值是 void 或 null,包装为成功响应,data 为 null
+        if (body == null) {
+            return R.success(null);
+        }
+
+        // 如果返回值是 String 类型,需要特殊处理
+        // 因为 Spring 默认使用 StringHttpMessageConverter 处理 String 类型
+        // 直接返回 R 对象会导致类型转换异常
+        if (returnType.getParameterType().equals(String.class)) {
+            try {
+                // 将 R 对象转换为 JSON 字符串
+                R<Object> result = R.success(body);
+                return objectMapper.writeValueAsString(result);
+            } catch (Exception e) {
+                log.error("包装 String 类型响应时发生错误", e);
+                return body;
+            }
+        }
+
+        // 包装其他类型的返回值
+        return R.success(body);
+    }
+}

+ 50 - 0
src/main/java/com/aitori/config/JacksonConfig.java

@@ -0,0 +1,50 @@
+package com.aitori.config;
+
+import cn.hutool.core.date.DatePattern;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.TimeZone;
+
+@Configuration
+public class JacksonConfig {
+
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
+        return builder -> {
+            JavaTimeModule javaTimeModule = this.timeModule();
+            builder.timeZone(TimeZone.getDefault());
+            builder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
+            builder.modules(javaTimeModule);
+        };
+    }
+
+    private JavaTimeModule timeModule() {
+        // 针对大数值的序列化处理
+        JavaTimeModule javaTimeModule = new JavaTimeModule();
+        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN);
+        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
+        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
+        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN);
+        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
+        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
+        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN);
+        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter));
+        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter));
+        return javaTimeModule;
+    }
+
+}

+ 152 - 0
src/main/java/com/aitori/config/Knife4jOpenApiCustomizer.java

@@ -0,0 +1,152 @@
+package com.aitori.config;
+
+import com.github.xiaoymin.knife4j.annotations.ApiSupport;
+import com.github.xiaoymin.knife4j.core.conf.ExtensionsConstants;
+import com.github.xiaoymin.knife4j.core.conf.GlobalConstants;
+import com.github.xiaoymin.knife4j.spring.configuration.Knife4jProperties;
+import com.github.xiaoymin.knife4j.spring.configuration.Knife4jSetting;
+import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.models.OpenAPI;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ArrayUtils;
+import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
+import org.springdoc.core.properties.SpringDocConfigProperties;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * <p>只是为了解决spring3.4版本的兼容问题</p>
+ *
+ * <p>增强扩展属性支持</p>
+ *
+ * @author <a href="xiaoymin@foxmail.com">xiaoymin@foxmail.com</a>
+ * 2022/12/11 22:40
+ * @since 4.1.0
+ */
+@Primary
+@Configuration
+@Slf4j
+public class Knife4jOpenApiCustomizer extends com.github.xiaoymin.knife4j.spring.extension.Knife4jOpenApiCustomizer implements GlobalOpenApiCustomizer {
+    final Knife4jProperties knife4jProperties;
+    final SpringDocConfigProperties properties;
+
+    public Knife4jOpenApiCustomizer(Knife4jProperties knife4jProperties, SpringDocConfigProperties properties) {
+        super(knife4jProperties, properties);
+        this.knife4jProperties = knife4jProperties;
+        this.properties = properties;
+    }
+
+    @Override
+    public void customise(OpenAPI openApi) {
+        log.debug("Knife4j OpenApiCustomizer");
+        if (knife4jProperties.isEnable()) {
+            Knife4jSetting setting = knife4jProperties.getSetting();
+            OpenApiExtensionResolver openApiExtensionResolver = new OpenApiExtensionResolver(setting, knife4jProperties.getDocuments());
+            // 解析初始化
+            openApiExtensionResolver.start();
+            Map<String, Object> objectMap = new HashMap<>();
+            objectMap.put(GlobalConstants.EXTENSION_OPEN_SETTING_NAME, setting);
+            objectMap.put(GlobalConstants.EXTENSION_OPEN_MARKDOWN_NAME, openApiExtensionResolver.getMarkdownFiles());
+            openApi.addExtension(GlobalConstants.EXTENSION_OPEN_API_NAME, objectMap);
+            addOrderExtension(openApi);
+        }
+    }
+
+    /**
+     * 往OpenAPI内tags字段添加x-order属性
+     *
+     * @param openApi openApi
+     */
+
+    private void addOrderExtension(OpenAPI openApi) {
+        if (CollectionUtils.isEmpty(properties.getGroupConfigs())) {
+            return;
+        }
+        // 获取包扫描路径
+        Set<String> packagesToScan =
+                properties.getGroupConfigs().stream()
+                        .map(SpringDocConfigProperties.GroupConfig::getPackagesToScan)
+                        .filter(toScan -> !CollectionUtils.isEmpty(toScan))
+                        .flatMap(List::stream)
+                        .collect(Collectors.toSet());
+        if (CollectionUtils.isEmpty(packagesToScan)) {
+            return;
+        }
+        // 扫描包下被ApiSupport注解的RestController Class
+        Set<Class<?>> classes =
+                packagesToScan.stream()
+                        .map(this::scanPackageByAnnotation)
+                        .flatMap(Set::stream)
+                        .filter(clazz -> clazz.isAnnotationPresent(ApiSupport.class))
+                        .collect(Collectors.toSet());
+        if (!CollectionUtils.isEmpty(classes)) {
+            // ApiSupport oder值存入tagSortMap<Tag.name,ApiSupport.order>
+            Map<String, Integer> tagOrderMap = new HashMap<>();
+            classes.forEach(
+                    clazz -> {
+                        Tag tag = getTag(clazz);
+                        if (Objects.nonNull(tag)) {
+                            ApiSupport apiSupport = clazz.getAnnotation(ApiSupport.class);
+                            tagOrderMap.putIfAbsent(tag.name(), apiSupport.order());
+                        }
+                    });
+            // 往openApi tags字段添加x-order增强属性
+            if (openApi.getTags() != null) {
+                openApi
+                        .getTags()
+                        .forEach(
+                                tag -> {
+                                    if (tagOrderMap.containsKey(tag.getName())) {
+                                        tag.addExtension(
+                                                ExtensionsConstants.EXTENSION_ORDER, tagOrderMap.get(tag.getName()));
+                                    }
+                                });
+            }
+        }
+    }
+
+    private Tag getTag(Class<?> clazz) {
+        // 从类上获取
+        Tag tag = clazz.getAnnotation(Tag.class);
+        if (Objects.isNull(tag)) {
+            // 从接口上获取
+            Class<?>[] interfaces = clazz.getInterfaces();
+            if (ArrayUtils.isNotEmpty(interfaces)) {
+                for (Class<?> interfaceClazz : interfaces) {
+                    Tag anno = interfaceClazz.getAnnotation(Tag.class);
+                    if (Objects.nonNull(anno)) {
+                        tag = anno;
+                        break;
+                    }
+                }
+            }
+        }
+        return tag;
+    }
+
+    private Set<Class<?>> scanPackageByAnnotation(
+            String packageName) {
+        ClassPathScanningCandidateComponentProvider scanner =
+                new ClassPathScanningCandidateComponentProvider(false);
+        scanner.addIncludeFilter(new AnnotationTypeFilter(RestController.class));
+        Set<Class<?>> classes = new HashSet<>();
+        for (BeanDefinition beanDefinition : scanner.findCandidateComponents(packageName)) {
+            try {
+                Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());
+                classes.add(clazz);
+            } catch (ClassNotFoundException ignore) {
+
+            }
+        }
+        return classes;
+    }
+}

+ 20 - 0
src/main/java/com/aitori/config/MybatisPlusConfig.java

@@ -0,0 +1,20 @@
+package com.aitori.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@MapperScan("com.aitori.mapper")
+public class MybatisPlusConfig {
+
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        return interceptor;
+    }
+}

+ 78 - 0
src/main/java/com/aitori/config/RedisConfig.java

@@ -0,0 +1,78 @@
+package com.aitori.config;
+
+import com.aitori.constants.CacheConstants;
+import com.aitori.listener.WorkflowTaskMessageListener;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.listener.ChannelTopic;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * RedisTemplate 通用配置类(兼容所有 Jackson 版本)
+ */
+@Configuration
+public class RedisConfig {
+
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+
+        // Key/HashKey 序列化器(解决乱码)
+        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
+        redisTemplate.setKeySerializer(stringRedisSerializer);
+        redisTemplate.setHashKeySerializer(stringRedisSerializer);
+
+        // Value/HashValue 序列化器(JSON 序列化,兼容所有 Jackson 版本)
+        GenericJackson2JsonRedisSerializer jacksonSerializer = getGenericJackson2JsonRedisSerializer();
+        redisTemplate.setValueSerializer(jacksonSerializer);
+        redisTemplate.setHashValueSerializer(jacksonSerializer);
+
+        redisTemplate.afterPropertiesSet();
+        return redisTemplate;
+    }
+
+    @Bean
+    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
+        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
+        return stringRedisTemplate;
+    }
+
+    @Bean
+    public RedisMessageListenerContainer redisMessageListenerContainer(
+            RedisConnectionFactory connectionFactory,
+            WorkflowTaskMessageListener workflowTaskMessageListener) {
+        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
+        container.setConnectionFactory(connectionFactory);
+        container.addMessageListener(workflowTaskMessageListener,
+                new ChannelTopic(CacheConstants.WORKFLOW_TASK_TOPIC));
+        return container;
+    }
+
+    /**
+     * 自定义 Jackson 序列化器(兼容低版本 Jackson,移除过时/找不到的常量)
+     */
+    private GenericJackson2JsonRedisSerializer getGenericJackson2JsonRedisSerializer() {
+        ObjectMapper objectMapper = new ObjectMapper();
+        // 1. 开启所有字段的序列化(包括私有字段)
+        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+
+        // 2. 配置类型信息(兼容所有 Jackson 版本的写法)
+        objectMapper.activateDefaultTyping(
+                LaissezFaireSubTypeValidator.instance,
+                ObjectMapper.DefaultTyping.NON_FINAL
+        );
+
+        return new GenericJackson2JsonRedisSerializer(objectMapper);
+    }
+}

+ 27 - 0
src/main/java/com/aitori/config/SchedulerConfig.java

@@ -0,0 +1,27 @@
+package com.aitori.config;
+
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
+import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+import javax.sql.DataSource;
+
+@Configuration
+@EnableScheduling
+@EnableSchedulerLock(defaultLockAtMostFor = "PT30M")
+public class SchedulerConfig {
+
+    @Bean
+    public LockProvider lockProvider(DataSource dataSource) {
+        return new JdbcTemplateLockProvider(
+                JdbcTemplateLockProvider.Configuration.builder()
+                        .withJdbcTemplate(new JdbcTemplate(dataSource))
+                        .usingDbTime()
+                        .build()
+        );
+    }
+}

+ 58 - 0
src/main/java/com/aitori/config/VirtualThreadConfig.java

@@ -0,0 +1,58 @@
+package com.aitori.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.core.task.support.TaskExecutorAdapter;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * 适配定时任务+异步任务全场景
+ */
+@Configuration
+@EnableAsync
+@EnableScheduling // 开启定时任务
+public class VirtualThreadConfig implements SchedulingConfigurer {
+
+    /**
+     * 1. 全局异步任务执行器(虚拟线程)
+     * 异步任务执行器(@Async 注解会使用这个虚拟线程执行器)
+     */
+    @Bean
+    public AsyncTaskExecutor asyncTaskExecutor() {
+        // 虚拟线程池:每个任务创建一个虚拟线程
+        return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
+    }
+
+    /**
+     * 2. 定时任务调度器(绑定虚拟线程工厂)
+     */
+    @Bean
+    public ThreadPoolTaskScheduler taskScheduler() {
+        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+        // 虚拟线程无实际池大小限制,此值仅为适配Spring接口(可设1)
+        scheduler.setPoolSize(1);
+        scheduler.setThreadNamePrefix("scheduled-virtual-thread-");
+        // 关键修正:直接创建虚拟线程工厂(无需依赖ExecutorService的方法)
+        ThreadFactory virtualThreadFactory = Thread.ofVirtual().name("scheduled-", 1).factory();
+        scheduler.setThreadFactory(virtualThreadFactory);
+
+        scheduler.initialize();
+        return scheduler;
+    }
+
+    /**
+     * 3. 绑定定时任务到虚拟线程调度器
+     */
+    @Override
+    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+        taskRegistrar.setTaskScheduler(taskScheduler());
+    }
+}

+ 25 - 0
src/main/java/com/aitori/config/WebMvcConfig.java

@@ -0,0 +1,25 @@
+package com.aitori.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * WebMvc配置
+ */
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+
+    /**
+     * 跨域配置
+     */
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedOriginPatterns("*")
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+                .allowedHeaders("*")
+                .allowCredentials(true)
+                .maxAge(3600);
+    }
+}

+ 32 - 0
src/main/java/com/aitori/constants/CacheConstants.java

@@ -0,0 +1,32 @@
+package com.aitori.constants;
+
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class CacheConstants {
+
+    /**
+     * 验证码键前缀
+     */
+    public static final String CAPTCHA_KEY_PREFIX = "CAPTCHA";
+
+    /**
+     * Workflow 任务完成通知 Redis Topic
+     */
+    public static final String WORKFLOW_TASK_TOPIC = "WORKFLOW_TASK";
+
+
+    /**
+     * 格式化键,将各子键用 : 拼接起来
+     *
+     * @param subKeys 子键列表
+     * @return 键
+     */
+    public static String formatKey(String... subKeys) {
+        return String.join(":", subKeys);
+    }
+
+
+}

+ 55 - 0
src/main/java/com/aitori/controller/DashboardController.java

@@ -0,0 +1,55 @@
+package com.aitori.controller;
+
+import com.aitori.entity.vo.*;
+import com.aitori.service.DashboardService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/v2/dashboard")
+@Tag(name = "仪表盘接口")
+@RequiredArgsConstructor
+public class DashboardController {
+
+    private final DashboardService dashboardService;
+
+    @GetMapping("/today-overview")
+    @Operation(summary = "今日数据总览", description = "获取今日新增用户、充值积分、消耗积分、新增VIP,并与昨日对比")
+    public TodayOverviewVO todayOverview() {
+        return dashboardService.getTodayOverview();
+    }
+
+    @GetMapping("/task-overview")
+    @Operation(summary = "任务状态概览", description = "获取当前所有订单任务的不同状态数量")
+    public List<TaskStatusCountVO> taskOverview() {
+        return dashboardService.getTaskOverview();
+    }
+
+    @GetMapping("/trend")
+    @Operation(summary = "趋势数据", description = "按周或月维度获取新增用户和新增VIP趋势数据,用于折线图/柱状图展示")
+    public List<TrendDataVO> trend(
+            @Parameter(description = "时间维度: week(近7天) / month(近30天)", example = "week")
+            @RequestParam(defaultValue = "week") String type) {
+        return dashboardService.getTrendData(type);
+    }
+
+    @GetMapping("/system-overview")
+    @Operation(summary = "系统数据概览", description = "获取系统总用户数、总模版数、已完成/失败任务数、会员用户数")
+    public SystemOverviewVO systemOverview() {
+        return dashboardService.getSystemOverview();
+    }
+
+    @GetMapping("/running-tasks")
+    @Operation(summary = "进行中任务数", description = "获取当前正在执行中的任务数量")
+    public Long runningTasks() {
+        return dashboardService.getRunningTaskCount();
+    }
+}

+ 50 - 0
src/main/java/com/aitori/controller/LoginController.java

@@ -0,0 +1,50 @@
+package com.aitori.controller;
+
+import cn.hutool.core.util.IdUtil;
+import com.aitori.constants.CacheConstants;
+import com.aitori.entity.request.LoginRequest;
+import com.aitori.entity.response.CaptchaResponse;
+import com.aitori.entity.response.LoginResponse;
+import com.aitori.service.SysUserService;
+import com.wf.captcha.SpecCaptcha;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.Duration;
+
+@Slf4j
+@RestController
+@Tag(name = "系统管理员登录相关接口")
+@RequiredArgsConstructor
+public class LoginController {
+
+    /* 验证码的过期时间 */
+    private static final Long EXPIRATION_IN_MINUTES = 1L;
+
+    private final RedisTemplate<String, Object> redisTemplate;
+    private final SysUserService sysUserService;
+
+    @GetMapping("/v2/admin/login")
+    @Operation(summary = "获取图片验证码", description = "获取图片验证码(Base64编码,带图片格式:data:image/gif;base64)")
+    public CaptchaResponse getCaptcha() {
+        SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
+        String captcha = specCaptcha.text().toLowerCase();
+        String uuid = IdUtil.fastUUID();
+        String captchaKey = CacheConstants.formatKey(CacheConstants.CAPTCHA_KEY_PREFIX, uuid);
+        redisTemplate.opsForValue().set(captchaKey, captcha, Duration.ofMinutes(EXPIRATION_IN_MINUTES));
+        return CaptchaResponse.builder().uuid(uuid).img(specCaptcha.toBase64()).build();
+    }
+
+    @PostMapping("/v2/admin/login")
+    @Operation(summary = "登录", description = "登录")
+    public LoginResponse login(@RequestBody LoginRequest request) {
+        return sysUserService.login(request);
+    }
+}

+ 32 - 0
src/main/java/com/aitori/controller/StorageController.java

@@ -0,0 +1,32 @@
+package com.aitori.controller;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.IdUtil;
+import com.aitori.utils.GoogleStorageUtil;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+
+@Slf4j
+@RestController
+@Tag(name = "系统管理员登录相关接口")
+@RequiredArgsConstructor
+public class StorageController {
+
+    @PostMapping("/google/upload/img")
+    @Operation(summary = "上传文件接口")
+    public String uploadGoogle(@RequestParam("file") MultipartFile file,
+                               @RequestParam String prefix) throws IOException {
+        String uuid = IdUtil.fastSimpleUUID();
+        String path = prefix + uuid + "." + FileUtil.getSuffix(file.getOriginalFilename());
+        return GoogleStorageUtil.upload(file.getBytes(), path);
+    }
+
+}

+ 32 - 0
src/main/java/com/aitori/controller/TAiAlbumController.java

@@ -0,0 +1,32 @@
+package com.aitori.controller;
+
+import com.aitori.entity.request.AiAlbumQueryRequest;
+import com.aitori.entity.vo.AiAlbumVO;
+import com.aitori.service.TAiAlbumService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/v2/ai/album")
+@Tag(name = "相册管理接口")
+@RequiredArgsConstructor
+public class TAiAlbumController {
+
+    private final TAiAlbumService albumService;
+
+    @GetMapping("/list")
+    @Operation(summary = "相册列表", description = "分页查询相册列表")
+    public IPage<AiAlbumVO> list(AiAlbumQueryRequest request) {
+        return albumService.getAlbumVOPage(new Page<>(request.getPage(), request.getSize()), request);
+    }
+
+    @GetMapping("/get/{id}")
+    @Operation(summary = "相册详情", description = "根据ID查询相册详情")
+    public AiAlbumVO get(@PathVariable Long id) {
+        return albumService.getAlbumVOById(id);
+    }
+}

+ 51 - 0
src/main/java/com/aitori/controller/TAiModuleController.java

@@ -0,0 +1,51 @@
+package com.aitori.controller;
+
+import com.aitori.entity.request.ModuleSaveRequest;
+import com.aitori.entity.request.ModuleSortRequest;
+import com.aitori.entity.vo.ModuleDetailVO;
+import com.aitori.service.TAiModuleService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/v2/module")
+@Tag(name = "首页模块管理接口")
+@RequiredArgsConstructor
+public class TAiModuleController {
+
+    private final TAiModuleService moduleService;
+
+    @PostMapping("/add")
+    @Operation(summary = "新增/更新模块", description = "新增或更新模块,并可同时配置关联项")
+    public Boolean add(@RequestBody ModuleSaveRequest request) {
+        return moduleService.saveModuleWithConfigs(request);
+    }
+
+    @PostMapping("/sort")
+    @Operation(summary = "更新排序", description = "批量更新模块排序")
+    public Boolean updateSort(@RequestBody List<ModuleSortRequest> requests) {
+        return moduleService.updateSort(requests);
+    }
+
+    @DeleteMapping("/delete/{id}")
+    @Operation(summary = "删除模块", description = "根据ID删除模块及其关联配置")
+    public Boolean delete(@PathVariable Integer id) {
+        return moduleService.removeModuleWithConfigs(id);
+    }
+
+    @GetMapping("/get/{id}")
+    @Operation(summary = "获取单个模块", description = "根据ID获取模块详情(包含配置项)")
+    public ModuleDetailVO get(@PathVariable Integer id) {
+        return moduleService.getDetailById(id);
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获取所有模块", description = "获取所有模块列表(包含配置项),已按sort排序")
+    public List<ModuleDetailVO> list() {
+        return moduleService.listAllDetails();
+    }
+}

+ 34 - 0
src/main/java/com/aitori/controller/TAiOrderV2Controller.java

@@ -0,0 +1,34 @@
+package com.aitori.controller;
+
+import com.aitori.entity.TAiOrderV2;
+import com.aitori.entity.request.AiOrderV2QueryRequest;
+import com.aitori.entity.vo.AiOrderV2VO;
+import com.aitori.service.TAiOrderV2Service;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/v2/ai/order/v2")
+@Tag(name = "订单V2管理接口")
+@RequiredArgsConstructor
+public class TAiOrderV2Controller {
+
+    private final TAiOrderV2Service aiOrderV2Service;
+
+    @GetMapping("/list")
+    @Operation(summary = "订单V2列表", description = "分页查询订单V2列表")
+    public IPage<AiOrderV2VO> list(AiOrderV2QueryRequest request) {
+        Page<AiOrderV2VO> page = new Page<>(request.getPage(), request.getSize());
+        return aiOrderV2Service.getOrderVOPage(page, request);
+    }
+
+    @GetMapping("/get/{id}")
+    @Operation(summary = "订单V2详情", description = "根据ID查询订单V2详情")
+    public TAiOrderV2 get(@PathVariable String id) {
+        return aiOrderV2Service.getById(id);
+    }
+}

+ 61 - 0
src/main/java/com/aitori/controller/TAiStyleController.java

@@ -0,0 +1,61 @@
+package com.aitori.controller;
+
+import com.aitori.entity.TAiStyle;
+import com.aitori.service.TAiStyleService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/v2/style")
+@Tag(name = "风格管理接口")
+@RequiredArgsConstructor
+public class TAiStyleController {
+
+    private final TAiStyleService styleService;
+
+    @PostMapping("/add")
+    @Operation(summary = "新增风格", description = "新增风格")
+    public Boolean add(@RequestBody TAiStyle style) {
+        return styleService.save(style);
+    }
+
+    @DeleteMapping("/delete/{id}")
+    @Operation(summary = "删除风格", description = "根据ID删除风格")
+    public Boolean delete(@PathVariable Integer id) {
+        return styleService.removeById(id);
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "修改风格", description = "修改风格")
+    public Boolean update(@RequestBody TAiStyle style) {
+        return styleService.updateById(style);
+    }
+
+    @GetMapping("/get/{id}")
+    @Operation(summary = "获取单个风格", description = "根据ID获取风格详情")
+    public TAiStyle get(@PathVariable Integer id) {
+        return styleService.getById(id);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "分页查询风格", description = "分页查询风格,支持风格名称查询")
+    public Page<TAiStyle> page(
+            @Parameter(description = "页码", required = true) @RequestParam(defaultValue = "1") Integer pageNum,
+            @Parameter(description = "每页数量", required = true) @RequestParam(defaultValue = "10") Integer pageSize,
+            @Parameter(description = "风格名称") @RequestParam(required = false) String name,
+            @Parameter(description = "ID") @RequestParam(required = false) Integer id) {
+
+        LambdaQueryWrapper<TAiStyle> wrapper = new LambdaQueryWrapper<>();
+        wrapper.like(StringUtils.isNotBlank(name), TAiStyle::getName, name);
+        wrapper.eq(id != null, TAiStyle::getId, id);
+        wrapper.orderByDesc(TAiStyle::getCreateTime);
+
+        return styleService.page(new Page<>(pageNum, pageSize), wrapper);
+    }
+}

+ 45 - 0
src/main/java/com/aitori/controller/TAiSubscribeController.java

@@ -0,0 +1,45 @@
+package com.aitori.controller;
+
+import com.aitori.entity.TAiSubscribe;
+import com.aitori.entity.vo.AiSubscribeVO;
+import com.aitori.service.TAiSubscribeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RestController
+@RequestMapping("/v2/ai/subscribe")
+@Tag(name = "AI会员订阅配置接口")
+@RequiredArgsConstructor
+public class TAiSubscribeController {
+
+    private final TAiSubscribeService subscribeService;
+
+    @GetMapping("/list")
+    @Operation(summary = "订阅配置列表", description = "查询订阅配置列表")
+    public List<AiSubscribeVO> list() {
+        return subscribeService.listSubscribeVOs();
+    }
+
+    @PostMapping("/add")
+    @Operation(summary = "新增订阅配置")
+    public boolean add(@RequestBody TAiSubscribe subscribe) {
+        return subscribeService.save(subscribe);
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新订阅配置")
+    public boolean update(@RequestBody TAiSubscribe subscribe) {
+        return subscribeService.updateById(subscribe);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除订阅配置")
+    public boolean delete(@RequestBody Integer[] ids) {
+        return subscribeService.removeByIds(Arrays.asList(ids));
+    }
+}

+ 55 - 0
src/main/java/com/aitori/controller/TAiTagController.java

@@ -0,0 +1,55 @@
+package com.aitori.controller;
+
+import com.aitori.entity.TAiTag;
+import com.aitori.entity.request.TagSaveRequest;
+import com.aitori.entity.vo.TagDetailVO;
+import com.aitori.service.TAiTagService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/v2/tag")
+@Tag(name = "标签管理接口")
+@RequiredArgsConstructor
+public class TAiTagController {
+
+    private final TAiTagService tagService;
+
+    @PostMapping("/add")
+    @Operation(summary = "新增/更新标签", description = "新增或更新标签,并可同时绑定风格")
+    public Boolean add(@RequestBody TagSaveRequest request) {
+        return tagService.saveTagWithStyles(request);
+    }
+
+    @DeleteMapping("/delete/{id}")
+    @Operation(summary = "删除标签", description = "根据ID删除标签及其关联配置")
+    public Boolean delete(@PathVariable Integer id) {
+        return tagService.removeTagWithConfig(id);
+    }
+
+    @GetMapping("/get/{id}")
+    @Operation(summary = "获取单个标签", description = "根据ID获取标签详情(包含绑定的风格)")
+    public TagDetailVO get(@PathVariable Integer id) {
+        return tagService.getDetailById(id);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "分页查询标签", description = "分页查询标签,支持标签名称查询")
+    public Page<TAiTag> page(
+            @Parameter(description = "页码", required = true) @RequestParam(defaultValue = "1") Integer pageNum,
+            @Parameter(description = "每页数量", required = true) @RequestParam(defaultValue = "10") Integer pageSize,
+            @Parameter(description = "标签名称") @RequestParam(required = false) String name) {
+        
+        LambdaQueryWrapper<TAiTag> wrapper = new LambdaQueryWrapper<>();
+        wrapper.like(StringUtils.isNotBlank(name), TAiTag::getName, name);
+        wrapper.orderByDesc(TAiTag::getId);
+        
+        return tagService.page(new Page<>(pageNum, pageSize), wrapper);
+    }
+}

+ 45 - 0
src/main/java/com/aitori/controller/TPointsConfigController.java

@@ -0,0 +1,45 @@
+package com.aitori.controller;
+
+import com.aitori.entity.TPointsConfig;
+import com.aitori.entity.vo.PointsConfigVO;
+import com.aitori.service.TPointsConfigService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RestController
+@RequestMapping("/v2/points/config")
+@Tag(name = "积分来源配置接口")
+@RequiredArgsConstructor
+public class TPointsConfigController {
+
+    private final TPointsConfigService configService;
+
+    @GetMapping("/list")
+    @Operation(summary = "配置列表", description = "查询积分来源配置列表")
+    public List<PointsConfigVO> list() {
+        return configService.listConfigVOs();
+    }
+
+    @PostMapping("/add")
+    @Operation(summary = "新增配置")
+    public boolean add(@RequestBody TPointsConfig config) {
+        return configService.save(config);
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新配置")
+    public boolean update(@RequestBody TPointsConfig config) {
+        return configService.updateById(config);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除配置")
+    public boolean delete(@RequestBody Integer[] ids) {
+        return configService.removeByIds(Arrays.asList(ids));
+    }
+}

+ 45 - 0
src/main/java/com/aitori/controller/TPointsProductController.java

@@ -0,0 +1,45 @@
+package com.aitori.controller;
+
+import com.aitori.entity.TPointsProduct;
+import com.aitori.entity.vo.PointsProductVO;
+import com.aitori.service.TPointsProductService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RestController
+@RequestMapping("/v2/points/product")
+@Tag(name = "积分产品管理接口")
+@RequiredArgsConstructor
+public class TPointsProductController {
+
+    private final TPointsProductService productService;
+
+    @GetMapping("/list")
+    @Operation(summary = "产品列表", description = "查询积分产品列表")
+    public List<PointsProductVO> list() {
+        return productService.listProductVOs();
+    }
+
+    @PostMapping("/add")
+    @Operation(summary = "新增产品")
+    public boolean add(@RequestBody TPointsProduct product) {
+        return productService.save(product);
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新产品")
+    public boolean update(@RequestBody TPointsProduct product) {
+        return productService.updateById(product);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除产品")
+    public boolean delete(@RequestBody Integer[] ids) {
+        return productService.removeByIds(Arrays.asList(ids));
+    }
+}

+ 55 - 0
src/main/java/com/aitori/controller/TRedemptionCodeController.java

@@ -0,0 +1,55 @@
+package com.aitori.controller;
+
+import com.aitori.entity.TRedemptionCode;
+import com.aitori.entity.request.RedemptionCodeQueryRequest;
+import com.aitori.entity.vo.RedemptionCodeVO;
+import com.aitori.service.TRedemptionCodeService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RestController
+@RequestMapping("/v2/redemption/code")
+@Tag(name = "兑换码管理接口")
+@RequiredArgsConstructor
+public class TRedemptionCodeController {
+
+    private final TRedemptionCodeService redemptionCodeService;
+
+    @GetMapping("/list")
+    @Operation(summary = "兑换码列表", description = "分页查询兑换码,包含已使用次数")
+    public IPage<RedemptionCodeVO> list(RedemptionCodeQueryRequest request) {
+        Page<RedemptionCodeVO> page = new Page<>(request.getPage(), request.getSize());
+        return redemptionCodeService.getRedemptionCodePage(page, request);
+    }
+
+    @PostMapping("/add")
+    @Operation(summary = "新增兑换码")
+    public boolean add(@RequestBody TRedemptionCode redemptionCode) {
+        return redemptionCodeService.save(redemptionCode);
+    }
+
+    @PostMapping("/addBatch")
+    @Operation(summary = "批量新增兑换码")
+    public boolean addBatch(@RequestBody List<TRedemptionCode> redemptionCodes) {
+        return redemptionCodeService.saveBatch(redemptionCodes);
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新兑换码")
+    public boolean update(@RequestBody TRedemptionCode redemptionCode) {
+        return redemptionCodeService.updateById(redemptionCode);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除兑换码")
+    public boolean delete(@RequestBody Long[] ids) {
+        return redemptionCodeService.removeByIds(Arrays.asList(ids));
+    }
+}

+ 49 - 0
src/main/java/com/aitori/controller/TSubscribeActionController.java

@@ -0,0 +1,49 @@
+package com.aitori.controller;
+
+import com.aitori.entity.TSubscribeAction;
+import com.aitori.entity.request.SubscribeActionSaveRequest;
+import com.aitori.service.TSubscribeActionService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/v2/subscribe/action")
+@Tag(name = "订阅广告配置接口")
+@RequiredArgsConstructor
+public class TSubscribeActionController {
+
+    private final TSubscribeActionService subscribeActionService;
+
+    @GetMapping("/list")
+    @Operation(summary = "订阅广告列表", description = "查询所有订阅广告配置")
+    public List<TSubscribeAction> list() {
+        return subscribeActionService.list();
+    }
+
+    @PostMapping("/add")
+    @Operation(summary = "添加订阅广告", description = "添加订阅广告配置")
+    public boolean add(@RequestBody SubscribeActionSaveRequest request) {
+        TSubscribeAction action = new TSubscribeAction();
+        BeanUtils.copyProperties(request, action);
+        return subscribeActionService.save(action);
+    }
+
+    @PostMapping("/update")
+    @Operation(summary = "更新订阅广告", description = "更新订阅广告配置")
+    public boolean update(@RequestBody SubscribeActionSaveRequest request) {
+        TSubscribeAction action = new TSubscribeAction();
+        BeanUtils.copyProperties(request, action);
+        return subscribeActionService.updateById(action);
+    }
+
+    @PostMapping("/delete")
+    @Operation(summary = "删除订阅广告", description = "根据ID删除订阅广告配置")
+    public boolean delete(@RequestParam Integer id) {
+        return subscribeActionService.removeById(id);
+    }
+}

+ 43 - 0
src/main/java/com/aitori/controller/TSurveyController.java

@@ -0,0 +1,43 @@
+package com.aitori.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.aitori.entity.TSurvey;
+import com.aitori.entity.request.CommonQueryRequest;
+import com.aitori.service.TSurveyService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/v2/survey")
+@Tag(name = "问卷管理接口")
+@RequiredArgsConstructor
+public class TSurveyController {
+
+    private final TSurveyService surveyService;
+
+    @GetMapping("/list")
+    @Operation(summary = "问卷列表", description = "分页查询问卷列表")
+    public Page<TSurvey> list(CommonQueryRequest request) {
+        Page<TSurvey> page = new Page<>(request.getPage(), request.getSize());
+        LambdaQueryWrapper<TSurvey> wrapper = new LambdaQueryWrapper<>();
+
+        if (StrUtil.isNotBlank(request.getKeyword())) {
+            wrapper.like(TSurvey::getTitle, request.getKeyword());
+        }
+        if (request.getStartTime() != null) {
+            wrapper.ge(TSurvey::getCreateTime, request.getStartTime());
+        }
+        if (request.getEndTime() != null) {
+            wrapper.le(TSurvey::getCreateTime, request.getEndTime());
+        }
+
+        wrapper.orderByDesc(TSurvey::getCreateTime);
+        return surveyService.page(page, wrapper);
+    }
+}

+ 32 - 0
src/main/java/com/aitori/controller/TUserController.java

@@ -0,0 +1,32 @@
+package com.aitori.controller;
+
+import com.aitori.entity.TUser;
+import com.aitori.entity.request.UserQueryRequest;
+import com.aitori.entity.request.UserStatusRequest;
+import com.aitori.service.TUserService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/v2/user")
+@Tag(name = "用户管理接口")
+@RequiredArgsConstructor
+public class TUserController {
+
+    private final TUserService userService;
+
+    @GetMapping("/list")
+    @Operation(summary = "用户列表查询", description = "分页查询用户列表")
+    public Page<TUser> list(UserQueryRequest request) {
+        return userService.pageQuery(request);
+    }
+
+    @PostMapping("/status")
+    @Operation(summary = "修改用户状态", description = "修改用户状态(0:禁用 1:可用)")
+    public Boolean updateStatus(@RequestBody UserStatusRequest request) {
+        return userService.updateStatus(request);
+    }
+}

+ 29 - 0
src/main/java/com/aitori/controller/TUserFeedbackController.java

@@ -0,0 +1,29 @@
+package com.aitori.controller;
+
+import com.aitori.entity.request.UserFeedbackQueryRequest;
+import com.aitori.entity.vo.UserFeedbackVO;
+import com.aitori.service.TUserFeedbackService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/v2/user/feedback")
+@Tag(name = "用户反馈管理接口")
+@RequiredArgsConstructor
+public class TUserFeedbackController {
+
+    private final TUserFeedbackService userFeedbackService;
+
+    @GetMapping("/list")
+    @Operation(summary = "用户反馈列表", description = "分页查询用户反馈列表,包含用户信息")
+    public IPage<UserFeedbackVO> list(UserFeedbackQueryRequest request) {
+        Page<UserFeedbackVO> page = new Page<>(request.getPage(), request.getSize());
+        return userFeedbackService.getFeedbackVOPage(page, request);
+    }
+}

+ 29 - 0
src/main/java/com/aitori/controller/TUserInviteRecordController.java

@@ -0,0 +1,29 @@
+package com.aitori.controller;
+
+import com.aitori.entity.request.UserInviteRecordQueryRequest;
+import com.aitori.entity.vo.UserInviteRecordVO;
+import com.aitori.service.TUserInviteRecordService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/v2/user/invite")
+@Tag(name = "用户邀请记录接口")
+@RequiredArgsConstructor
+public class TUserInviteRecordController {
+
+    private final TUserInviteRecordService inviteRecordService;
+
+    @GetMapping("/list")
+    @Operation(summary = "邀请记录列表", description = "分页查询邀请记录,包含邀请人和被邀请人详细信息")
+    public IPage<UserInviteRecordVO> list(UserInviteRecordQueryRequest request) {
+        Page<UserInviteRecordVO> page = new Page<>(request.getPage(), request.getSize());
+        return inviteRecordService.getInviteRecordVOPage(page, request);
+    }
+}

+ 29 - 0
src/main/java/com/aitori/controller/TUserMessageController.java

@@ -0,0 +1,29 @@
+package com.aitori.controller;
+
+import com.aitori.entity.request.UserMessageQueryRequest;
+import com.aitori.entity.vo.UserMessageVO;
+import com.aitori.service.TUserMessageService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/v2/user/message")
+@Tag(name = "用户消息管理接口")
+@RequiredArgsConstructor
+public class TUserMessageController {
+
+    private final TUserMessageService messageService;
+
+    @GetMapping("/list")
+    @Operation(summary = "用户消息列表", description = "分页查询用户消息列表,包含用户详细信息")
+    public IPage<UserMessageVO> list(UserMessageQueryRequest request) {
+        Page<UserMessageVO> page = new Page<>(request.getPage(), request.getSize());
+        return messageService.getMessageVOPage(page, request);
+    }
+}

+ 29 - 0
src/main/java/com/aitori/controller/TUserPointsOrderController.java

@@ -0,0 +1,29 @@
+package com.aitori.controller;
+
+import com.aitori.entity.request.UserPointsOrderQueryRequest;
+import com.aitori.entity.vo.UserPointsOrderVO;
+import com.aitori.service.TUserPointsOrderService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/v2/user/points/order")
+@Tag(name = "用户积分订单接口")
+@RequiredArgsConstructor
+public class TUserPointsOrderController {
+
+    private final TUserPointsOrderService pointsOrderService;
+
+    @GetMapping("/list")
+    @Operation(summary = "积分订单列表", description = "分页查询积分订单")
+    public IPage<UserPointsOrderVO> list(UserPointsOrderQueryRequest request) {
+        Page<UserPointsOrderVO> page = new Page<>(request.getPage(), request.getSize());
+        return pointsOrderService.getPointsOrderVOPage(page, request);
+    }
+}

+ 29 - 0
src/main/java/com/aitori/controller/TUserPointsRecordController.java

@@ -0,0 +1,29 @@
+package com.aitori.controller;
+
+import com.aitori.entity.request.UserPointsStatementQueryRequest;
+import com.aitori.entity.vo.UserPointsStatementVO;
+import com.aitori.service.TUserPointsRecordService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/v2/user/points/record")
+@Tag(name = "用户积分记录接口")
+@RequiredArgsConstructor
+public class TUserPointsRecordController {
+
+    private final TUserPointsRecordService pointsRecordService;
+
+    @GetMapping("/statement")
+    @Operation(summary = "用户积分流水(收入+支出)", description = "分页查询用户积分流水,包含收入和支出")
+    public IPage<UserPointsStatementVO> statement(UserPointsStatementQueryRequest request) {
+        Page<UserPointsStatementVO> page = new Page<>(request.getPage(), request.getSize());
+        return pointsRecordService.getPointsStatementPage(page, request);
+    }
+}

+ 15 - 0
src/main/java/com/aitori/controller/TUserPointsUsageController.java

@@ -0,0 +1,15 @@
+package com.aitori.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/v2/user/points/usage")
+@Tag(name = "用户积分使用记录接口")
+@RequiredArgsConstructor
+public class TUserPointsUsageController {
+
+
+}

+ 29 - 0
src/main/java/com/aitori/controller/TUserRedemptionRecordController.java

@@ -0,0 +1,29 @@
+package com.aitori.controller;
+
+import com.aitori.entity.request.UserRedemptionRecordQueryRequest;
+import com.aitori.entity.vo.UserRedemptionRecordVO;
+import com.aitori.service.TUserRedemptionRecordService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/v2/user/redemption/record")
+@Tag(name = "用户兑换记录接口")
+@RequiredArgsConstructor
+public class TUserRedemptionRecordController {
+
+    private final TUserRedemptionRecordService redemptionRecordService;
+
+    @GetMapping("/list")
+    @Operation(summary = "兑换记录列表", description = "分页查询兑换记录")
+    public IPage<UserRedemptionRecordVO> list(UserRedemptionRecordQueryRequest request) {
+        Page<UserRedemptionRecordVO> page = new Page<>(request.getPage(), request.getSize());
+        return redemptionRecordService.getRecordVOPage(page, request);
+    }
+}

+ 29 - 0
src/main/java/com/aitori/controller/TUserSubscribeOrderController.java

@@ -0,0 +1,29 @@
+package com.aitori.controller;
+
+import com.aitori.entity.request.UserSubscribeOrderQueryRequest;
+import com.aitori.entity.vo.UserSubscribeOrderVO;
+import com.aitori.service.TUserSubscribeOrderService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/v2/user/subscribe/order")
+@Tag(name = "用户订阅订单接口")
+@RequiredArgsConstructor
+public class TUserSubscribeOrderController {
+
+    private final TUserSubscribeOrderService subscribeOrderService;
+
+    @GetMapping("/list")
+    @Operation(summary = "订阅订单列表", description = "分页查询订阅订单")
+    public IPage<UserSubscribeOrderVO> list(UserSubscribeOrderQueryRequest request) {
+        Page<UserSubscribeOrderVO> page = new Page<>(request.getPage(), request.getSize());
+        return subscribeOrderService.getOrderVOPage(page, request);
+    }
+}

+ 46 - 0
src/main/java/com/aitori/controller/TUserSurveyController.java

@@ -0,0 +1,46 @@
+package com.aitori.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.aitori.entity.TUserSurvey;
+import com.aitori.entity.request.CommonQueryRequest;
+import com.aitori.service.TUserSurveyService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/v2/user/survey")
+@Tag(name = "用户问卷调查接口")
+@RequiredArgsConstructor
+public class TUserSurveyController {
+
+    private final TUserSurveyService userSurveyService;
+
+    @GetMapping("/list")
+    @Operation(summary = "用户问卷列表", description = "分页查询用户问卷提交记录")
+    public Page<TUserSurvey> list(CommonQueryRequest request) {
+        Page<TUserSurvey> page = new Page<>(request.getPage(), request.getSize());
+        LambdaQueryWrapper<TUserSurvey> wrapper = new LambdaQueryWrapper<>();
+
+        if (request.getUserId() != null) {
+            wrapper.eq(TUserSurvey::getUserId, request.getUserId());
+        }
+        if (StrUtil.isNotBlank(request.getKeyword())) {
+            wrapper.like(TUserSurvey::getTitle, request.getKeyword());
+        }
+        if (request.getStartTime() != null) {
+            wrapper.ge(TUserSurvey::getCreateTime, request.getStartTime());
+        }
+        if (request.getEndTime() != null) {
+            wrapper.le(TUserSurvey::getCreateTime, request.getEndTime());
+        }
+
+        wrapper.orderByDesc(TUserSurvey::getCreateTime);
+        return userSurveyService.page(page, wrapper);
+    }
+}

+ 57 - 0
src/main/java/com/aitori/dto/R.java

@@ -0,0 +1,57 @@
+package com.aitori.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+
+@Getter
+@Schema(description = "响应体")
+public class R<T> {
+
+    @Schema(description = "响应码")
+    private int code;
+
+    @Schema(description = "响应消息")
+    private String message;
+
+    @Schema(description = "响应数据")
+    private T data;
+
+    @Schema(description = "响应状态")
+    private boolean success;
+
+    @Schema(description = "响应时间戳")
+    private final Long timestamp;
+
+    private R() {
+        this.timestamp = System.currentTimeMillis();
+    }
+
+    private R(int code, String message, T data, boolean success) {
+        this.code = code;
+        this.message = message;
+        this.data = data;
+        this.success = success;
+        this.timestamp = System.currentTimeMillis();
+    }
+
+    public static <T> R<T> success(T data) {
+        return new R<>(200, "success", data, true);
+    }
+
+    public static <T> R<T> fail(int code, String message) {
+        return new R<>(code, message, null, false);
+    }
+
+    public static <T> R<T> fail(String message) {
+        return new R<>(500, message, null, false);
+    }
+
+    public static <T> R<T> fail(T data) {
+        return new R<>(500, "fail", data, false);
+    }
+
+    public static <T> R<T> fail() {
+        return new R<>(500, "fail", null, false);
+    }
+
+}

+ 41 - 0
src/main/java/com/aitori/entity/SysUser.java

@@ -0,0 +1,41 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("sys_user")
+@Schema(description = "系统用户")
+public class SysUser implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description = "ID")
+    private Long id;
+
+    @Schema(description = "用户名")
+    private String username;
+
+    @Schema(description = "密码")
+    private String password;
+
+    @Schema(description = "昵称")
+    private String nickname;
+
+    @Schema(description = "状态 1启用 0禁用")
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新时间")
+    private LocalDateTime updateTime;
+}

+ 85 - 0
src/main/java/com/aitori/entity/TAiAlbum.java

@@ -0,0 +1,85 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_ai_album")
+@Schema(description = "相册表")
+public class TAiAlbum implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description = "相册id")
+    private Long id;
+
+    @Schema(description = "标题")
+    private String title;
+
+    @Schema(description = "用户id")
+    private Long userId;
+
+    @Schema(description = "风格id")
+    private Integer styleId;
+
+    @Schema(description = "分身id")
+    private Long digitalId;
+
+    @Schema(description = "任务id")
+    private String taskId;
+
+    @Schema(description = "写真状态 1:处理中(待生成分身) 2:生成中 3:已完成")
+    private Integer status;
+
+    @Schema(description = "写真照片")
+    private String pictures;
+
+    @Schema(description = "视频")
+    private String videos;
+
+    @Schema(description = "是否加速 0:否 1:是")
+    private Integer accelerate;
+
+    @TableField("`timestamp`")
+    @Schema(description = "预计花费时间(单位:秒)")
+    private Long timestamp;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @Schema(description = "最后更新时间")
+    private LocalDateTime updateTime;
+
+    @Schema(description = "订单id")
+    private String orderId;
+
+    @Schema(description = "类型 1:分身相册 2:普通相册")
+    private Integer type;
+
+    @Schema(description = "预计完成时间")
+    private LocalDateTime expectTime;
+
+    @TableLogic
+    @Schema(description = "逻辑删除 0:未删除 1:已删除")
+    private Integer isDeleted;
+
+    @Schema(description = "视频的原始图片")
+    private String videoImage;
+
+    @Schema(description = "当前节点index")
+    private Integer currentNodeIndex;
+}

+ 40 - 0
src/main/java/com/aitori/entity/TAiModule.java

@@ -0,0 +1,40 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName(value = "t_ai_module", autoResultMap = true)
+@Schema(description = "首页模块表")
+public class TAiModule implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @Schema(description = "模块名称")
+    private String name;
+
+    @Schema(description = "子标题")
+    private String subTitle;
+
+    @Schema(description = "模块类型 1:幻灯片 2:列表 3:卡片 4:条幅 5:标签 6:vip类型 7:排版1 8:排版2 9:排版3")
+    private String moduleType;
+
+    @Schema(description = "排序")
+    private Integer sort;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "语言包")
+    private String lang;
+}

+ 79 - 0
src/main/java/com/aitori/entity/TAiModuleConfig.java

@@ -0,0 +1,79 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@TableName("t_ai_module_config")
+@Schema(description = "模块配置表")
+public class TAiModuleConfig implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @Schema(description = "模块id")
+    private Integer moduleId;
+
+    @Schema(description = "类型 1:标签 2:风格 3:条幅 4:vip框")
+    private Integer type;
+
+    @Schema(description = "标签id")
+    private Integer tagId;
+
+    @Schema(description = "风格id")
+    private Integer styleId;
+
+    @Schema(description = "滤镜id")
+    private Integer effectId;
+
+    @Schema(description = "封面图")
+    private String coverImg;
+
+    @Schema(description = "卡片模块下封面图")
+    private String cardCoverImg;
+
+    @Schema(description = "排序")
+    private Integer sort;
+
+    @Schema(description = "链接类型 1:打开标签详情 2:打开风格详情 3: 打开网页")
+    private Integer linkType;
+
+    @Schema(description = "跳转URL")
+    private String url;
+
+    @Schema(description = "标签名称")
+    private String tagName;
+
+    @Schema(description = "标签描述")
+    private String tagDescribe;
+
+    @Schema(description = "风格名称")
+    private String styleName;
+
+    @Schema(description = "风格描述")
+    private String styleDescribe;
+
+    @Schema(description = "滤镜名称")
+    private String effectName;
+
+    @Schema(description = "滤镜描述")
+    private String effectDescribe;
+
+    @Schema(description = "url标题")
+    private String urlTitle;
+
+    @Schema(description = "内容")
+    private String content;
+
+    @Schema(description = "内容描述")
+    private String description;
+}

+ 80 - 0
src/main/java/com/aitori/entity/TAiOrderV2.java

@@ -0,0 +1,80 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_ai_order_v2")
+@Schema(description = "订单表V2")
+public class TAiOrderV2 implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId("id")
+    @Schema(description = "订单id")
+    private String id;
+
+    @Schema(description = "用户id")
+    private Integer userId;
+
+    @Schema(description = "商品")
+    private String title;
+
+    @Schema(description = "状态 1:待支付 2:已支付 0:已退款")
+    private Integer status;
+
+    @Schema(description = "支付积分")
+    private Long payPoints;
+
+    @Schema(description = "支付时间")
+    private LocalDateTime payTime;
+
+    @Schema(description = "相册id")
+    private Integer albumId;
+
+    @Schema(description = "模版id")
+    private Integer styleId;
+
+    @Schema(description = "任务id")
+    private String taskId;
+
+    @Schema(description = "任务状态 0异常失败,1任务执行中,2执行完成,3执行失败")
+    private Integer taskStatus;
+
+    @Schema(description = "订单语言")
+    private String orderLang;
+
+    @Schema(description = "错误信息")
+    private String errorMessage;
+
+    @Schema(description = "结果")
+    private String result;
+
+    @Schema(description = "地区")
+    private String region;
+
+    @Schema(description = "语言")
+    private String language;
+
+    @Schema(description = "app版本")
+    private String appVersion;
+
+    @Schema(description = "手机型号")
+    private String phoneModel;
+
+    @Schema(description = "手机系统版本")
+    private String phoneOsVersion;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 166 - 0
src/main/java/com/aitori/entity/TAiStyle.java

@@ -0,0 +1,166 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@TableName(value = "t_ai_style", autoResultMap = true)
+@Schema(description = "风格表")
+public class TAiStyle implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description = "id主键")
+    private Integer id;
+
+    @Schema(description = "风格名字")
+    private String name;
+
+    @TableField("`describe`")
+    @Schema(description = "风格描述")
+    private String describe;
+
+    @Schema(description = "作者")
+    private String author;
+
+    @Schema(description = "风格封面,可以是一个视频封面")
+    private String coverImg;
+
+    @Schema(description = "详情图(逗号分割)")
+    private String detailImg;
+
+    @Schema(description = "标准定价(原价)")
+    private BigDecimal originalStandardPlanPrice;
+
+    @Schema(description = "标准定价(原价)(积分)")
+    private Long originalStandardPlanPoints;
+
+    @Schema(description = "标准定价")
+    private BigDecimal standardPlanPrice;
+
+    @Schema(description = "标准定价(积分)")
+    private Long standardPlanPoints;
+
+    @Schema(description = "快速套餐价格(原价)")
+    private BigDecimal originalFastPlanPrice;
+
+    @Schema(description = "快速套餐价格(原价)(积分)")
+    private Long originalFastPlanPoints;
+
+    @Schema(description = "快速套餐价格")
+    private BigDecimal fastPlanPrice;
+
+    @Schema(description = "快速套餐价格(积分)")
+    private Long fastPlanPoints;
+
+    @Schema(description = "排序")
+    private Integer sort;
+
+    @Schema(description = "风格的tag(限免、上新、等)")
+    private String tagId;
+
+    @TableField("`use`")
+    @Schema(description = "风格的使用量")
+    private Integer use;
+
+    @Schema(description = "风格所需最低图片数量")
+    private Integer minPic;
+
+    @Schema(description = "风格所需最高图片数量")
+    private Integer maxPic;
+
+    @Schema(description = "快速套餐生成图片数量")
+    private Integer fastPic;
+
+    @Schema(description = "标准套餐生成图片数量")
+    private Integer standardPic;
+
+    @Schema(description = "模版zip文件")
+    private String templateZip;
+
+    @Schema(description = "展示类型 0:默认")
+    private Integer showType = 0;
+
+    @Schema(description = "样图下面区域文本标题")
+    private String textTitleBelowSampleImg;
+
+    @Schema(description = "样图下面区域文本内容")
+    private String textContentBelowSampleImg;
+
+    @Schema(description = "样图")
+    private String sampleImg;
+
+    @Schema(description = "详情图蒙版")
+    private String detailImgMask;
+
+    @Schema(description = "卡片模块下封面图")
+    private String cardCoverImg;
+
+    @Schema(description = "平台(iOS,安卓等)")
+    private String platform;
+
+    @Schema(description = "最小版本号")
+    private String minVersionNum;
+
+    @Schema(description = "最大版本号")
+    private String maxVersionNum;
+
+    @Schema(description = "样图标题")
+    private String sampleImgTitle;
+
+    @Schema(description = "样图描述")
+    private String sampleImgDesc;
+
+    @Schema(description = "stylemode")
+    private Integer stylemode;
+
+    @Schema(description = "inneed")
+    private Integer inneed;
+
+    @Schema(description = "角标")
+    private String badge;
+
+    @Schema(description = "角标颜色值")
+    private String color;
+
+    @Schema(description = "语言包")
+    private String lang;
+
+    @Schema(description = "ai平台(该字段当前默认使用2这个值,后续有其他的情况可以扩充)")
+    private Integer apiType = 2;
+
+    @Schema(description = "api调用默认参数")
+    private String aiContent;
+
+    @Schema(description = "滤镜能力,目前使用的情况中为workflow")
+    private String aiType;
+
+    @Schema(description = "requirements,用于后续在ai调用过程中的依赖使用")
+    private String requirements;
+
+    @Schema(description = "模版类型,0:可试用,1:vip")
+    private Integer styleType;
+
+    @Schema(description = "视频封面预览图")
+    private String previewImg;
+
+    @TableLogic
+    @Schema(description = "逻辑删除字段")
+    private Integer isDeleted;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @Schema(description = "更新时间")
+    private LocalDateTime updateTime;
+}

+ 36 - 0
src/main/java/com/aitori/entity/TAiSubscribe.java

@@ -0,0 +1,36 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+@TableName("t_ai_subscribe")
+@Schema(description = "会员订阅表")
+public class TAiSubscribe implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description = "主键")
+    private Integer id;
+
+    @Schema(description = "Apple产品id")
+    private String productId;
+
+    @Schema(description = "订阅等级(0:连续包月,1:连续包年)")
+    private Integer subscribeType;
+
+    @Schema(description = "发放积分数量")
+    private Integer tokens;
+
+    @Schema(description = "积分过期时间(天)")
+    private Integer tokenExpirationTime;
+
+    @Schema(description = "订阅action类型(0:默认,1:推荐)")
+    private Integer actionType;
+}

+ 41 - 0
src/main/java/com/aitori/entity/TAiTag.java

@@ -0,0 +1,41 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName(value = "t_ai_tag", autoResultMap = true)
+@Schema(description = "标签表")
+public class TAiTag implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @Schema(description = "标签名")
+    private String name;
+
+    @TableField("`describe`")
+    @Schema(description = "描述")
+    private String describe;
+
+    @Schema(description = "标签封面图")
+    private String coverImg;
+
+    @Schema(description = "卡片封面图")
+    private String cardCoverImg;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "语言包")
+    private String lang;
+}

+ 43 - 0
src/main/java/com/aitori/entity/TAiTagConfig.java

@@ -0,0 +1,43 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@TableName("t_ai_tag_config")
+@Schema(description = "标签配置表")
+public class TAiTagConfig implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @Schema(description = "标签id")
+    private Integer tagId;
+
+    @Schema(description = "风格id")
+    private Integer styleId;
+
+    @Schema(description = "风格名")
+    private String styleName;
+
+    @Schema(description = "风格封面图")
+    private String coverImg;
+
+    @Schema(description = "滤镜id")
+    private Integer effectId;
+
+    @Schema(description = "滤镜名称")
+    private String effectName;
+
+    @Schema(description = "排序")
+    private Integer sort;
+}

+ 35 - 0
src/main/java/com/aitori/entity/TPointsConfig.java

@@ -0,0 +1,35 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+@TableName("t_points_config")
+@Schema(description = "积分来源配置表")
+public class TPointsConfig implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description = "主键")
+    private Integer id;
+
+    @Schema(description = "1:苹果支付购买 2:兑换码 3:签到 4:分享 5:邀请 6:被邀请 7:邀请人使用模板 8:注册")
+    private Integer code;
+
+    @Schema(description = "积分")
+    private Integer points;
+
+    @Schema(description = "有效期天数")
+    private Integer expireDays;
+
+    @TableField("`desc`")
+    @Schema(description = "描述")
+    private String desc;
+}

+ 43 - 0
src/main/java/com/aitori/entity/TPointsProduct.java

@@ -0,0 +1,43 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@TableName("t_points_product")
+@Schema(description = "积分产品表")
+public class TPointsProduct implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description = "主键")
+    private Integer id;
+
+    @Schema(description = "点数")
+    private Long points;
+
+    @Schema(description = "价格")
+    private BigDecimal price;
+
+    @Schema(description = "原始价格")
+    private BigDecimal originalPrice;
+
+    @Schema(description = "是否打折")
+    private Integer isDiscounted;
+
+    @Schema(description = "苹果支付产品id")
+    private String applePayProductId;
+
+    @Schema(description = "排序")
+    private Integer sort;
+
+    @Schema(description = "角标颜色值")
+    private String color;
+}

+ 39 - 0
src/main/java/com/aitori/entity/TRedemptionCode.java

@@ -0,0 +1,39 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_redemption_code")
+@Schema(description = "兑换码表")
+public class TRedemptionCode implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "兑换码")
+    private String code;
+
+    @Schema(description = "积分")
+    private Long points;
+
+    @Schema(description = "最大使用次数")
+    private Long maxUsage;
+
+    @Schema(description = "失效日期")
+    private LocalDateTime expireTime;
+
+    @Schema(description = "状态 0:禁用 1:启用")
+    private Integer status;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 45 - 0
src/main/java/com/aitori/entity/TSubscribeAction.java

@@ -0,0 +1,45 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_subscribe_action")
+@Schema(description = "订阅广告配置表")
+public class TSubscribeAction implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description = "主键")
+    private Integer id;
+
+    @Schema(description = "标题")
+    private String title;
+
+    @Schema(description = "描述")
+    private String description;
+
+    @Schema(description = "图片")
+    private String image;
+
+    @Schema(description = "类型(0:首页广告,1:订阅页面)")
+    private Integer type;
+
+    @Schema(description = "多语言")
+    private String lang;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 36 - 0
src/main/java/com/aitori/entity/TSurvey.java

@@ -0,0 +1,36 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_survey")
+@Schema(description = "满意度调查表")
+public class TSurvey implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @Schema(description = "标题")
+    private String title;
+
+    @Schema(description = "排序")
+    private Integer sort;
+
+    @Schema(description = "类型 1:5星评价")
+    private Integer type;
+
+    @Schema(description = "语言地区")
+    private String language;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 90 - 0
src/main/java/com/aitori/entity/TUser.java

@@ -0,0 +1,90 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_user")
+@Schema(description = "用户基础表")
+public class TUser implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description = "用户id")
+    private Long id;
+
+    @Schema(description = "用户昵称")
+    private String nickname;
+
+    @Schema(description = "用户密码")
+    private String password;
+
+    @Schema(description = "用户账号/邮箱")
+    private String email;
+
+    @Schema(description = "苹果id")
+    private String appleUserId;
+
+    @Schema(description = "最近一次登录时间")
+    private LocalDateTime lastLoginTime;
+
+    @Schema(description = "最近一次登录IP地址")
+    private String lastLoginIp;
+
+    @Schema(description = "用户身份信息")
+    private String role;
+
+    @Schema(description = "状态 0:禁用 1:可用 2:注销")
+    private Integer status;
+
+    @Schema(description = "盐")
+    private String salt;
+
+    @Schema(description = "头像")
+    private String avatar;
+
+    @Schema(description = "性别 0:女 1:男")
+    private Integer gender;
+
+    @Schema(description = "消息推送开关 0:关 1:开")
+    private Integer msgSwitch;
+
+    @Schema(description = "额外参数")
+    private String extJson;
+
+    @Schema(description = "评论权限 0:禁用 1:正常")
+    private Integer commentPermission;
+
+    @Schema(description = "推荐码")
+    private String referralCode;
+
+    @Schema(description = "登录次数")
+    private Long loginCount;
+
+    @Schema(description = "地区(默认日语)")
+    private String language;
+
+    @Schema(description = "是否会员(0:否,1:是)")
+    private Integer isVip;
+
+    @Schema(description = "交易id")
+    private String originalTransactionId;
+
+    @Schema(description = "平台")
+    private String platform;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @Schema(description = "更新时间")
+    private LocalDateTime updateTime;
+}

+ 33 - 0
src/main/java/com/aitori/entity/TUserFeedback.java

@@ -0,0 +1,33 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_user_feedback")
+@Schema(description = "用户反馈表")
+public class TUserFeedback implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @Schema(description = "用户id")
+    private Integer userId;
+
+    @Schema(description = "标题")
+    private String title;
+
+    @Schema(description = "内容")
+    private String content;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 30 - 0
src/main/java/com/aitori/entity/TUserInviteRecord.java

@@ -0,0 +1,30 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_user_invite_record")
+@Schema(description = "用户邀请记录表")
+public class TUserInviteRecord implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "用户ID")
+    private Long userId;
+
+    @Schema(description = "被邀请人ID")
+    private Long inviteeId;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 55 - 0
src/main/java/com/aitori/entity/TUserMessage.java

@@ -0,0 +1,55 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_user_message")
+@Schema(description = "消息推送表")
+public class TUserMessage implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @Schema(description = "用户id")
+    private Integer userId;
+
+    @Schema(description = "标题")
+    private String title;
+
+    @Schema(description = "内容")
+    private String content;
+
+    @Schema(description = "缩略图")
+    private String thumbUrl;
+
+    @Schema(description = "状态 0:已删除 1:可用")
+    private Integer status;
+
+    @Schema(description = "已读状态 0:未读 1:已读")
+    private Integer readStatus;
+
+    @Schema(description = "消息类型 0:订单已支付 1:训练分身 2:训练分身完成 3:训练写真 4:训练写真完成")
+    private Integer type;
+
+    @Schema(description = "订单id")
+    private String orderId;
+
+    @Schema(description = "相册id")
+    private Integer albumId;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @Schema(description = "最后更新时间")
+    private LocalDateTime updateTime;
+}

+ 40 - 0
src/main/java/com/aitori/entity/TUserPointsOrder.java

@@ -0,0 +1,40 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_user_points_order")
+@Schema(description = "用户积分订单表")
+public class TUserPointsOrder implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "用户ID")
+    private Long userId;
+
+    @Schema(description = "产品ID")
+    private Integer productId;
+
+    @Schema(description = "积分")
+    private Integer points;
+
+    @Schema(description = "交易ID")
+    private String transactionId;
+
+    @Schema(description = "支付价格")
+    private BigDecimal payPrice;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 39 - 0
src/main/java/com/aitori/entity/TUserPointsRecord.java

@@ -0,0 +1,39 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_user_points_record")
+@Schema(description = "积分变更记录表")
+public class TUserPointsRecord implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "用户id")
+    private Long userId;
+
+    @Schema(description = "点数")
+    private Long points;
+
+    @Schema(description = "来源 1:购买 2:兑换码 3:签到 4:分享 5:邀请 6:被邀请 7:邀请人使用模板 8:注册 9:投稿 10:评论 11:会员")
+    private Integer source;
+
+    @Schema(description = "过期时间")
+    private LocalDateTime expireTime;
+
+    @Schema(description = "额外参数")
+    private String extParam;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 42 - 0
src/main/java/com/aitori/entity/TUserPointsUsage.java

@@ -0,0 +1,42 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_user_points_usage")
+@Schema(description = "积分使用记录表")
+public class TUserPointsUsage implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "用户id")
+    private Long userId;
+
+    @Schema(description = "点数")
+    private Long points;
+
+    @Schema(description = "变更记录id")
+    private Long recordId;
+
+    @Schema(description = "订单id")
+    private String orderId;
+
+    @Schema(description = "标题")
+    private String title;
+
+    @Schema(description = "缩略图")
+    private String thumbUrl;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 39 - 0
src/main/java/com/aitori/entity/TUserRedemptionRecord.java

@@ -0,0 +1,39 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_user_redemption_record")
+@Schema(description = "用户兑换记录表")
+public class TUserRedemptionRecord implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "用户id")
+    private Long userId;
+
+    @Schema(description = "兑换码id")
+    private Long redemptionId;
+
+    @Schema(description = "兑换码")
+    private String redemptionCode;
+
+    @Schema(description = "积分")
+    private Long points;
+
+    @Schema(description = "失效日期")
+    private LocalDateTime expireTime;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 42 - 0
src/main/java/com/aitori/entity/TUserSubscribeOrder.java

@@ -0,0 +1,42 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_user_subscribe_order")
+@Schema(description = "用户会员订单表")
+public class TUserSubscribeOrder implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "用户ID")
+    private Long userId;
+
+    @Schema(description = "产品ID")
+    private String productId;
+
+    @Schema(description = "订阅ID")
+    private Integer subscribeId;
+
+    @Schema(description = "原始交易ID")
+    private String originalTransactionId;
+
+    @Schema(description = "交易ID")
+    private String transactionId;
+
+    @Schema(description = "过期时间")
+    private LocalDateTime expireTime;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 51 - 0
src/main/java/com/aitori/entity/TUserSurvey.java

@@ -0,0 +1,51 @@
+package com.aitori.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_user_survey")
+@Schema(description = "用户满意度调查表")
+public class TUserSurvey implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "用户id")
+    private Long userId;
+
+    @Schema(description = "相册id")
+    private Long albumId;
+
+    @Schema(description = "分身id")
+    private Long digitalId;
+
+    @Schema(description = "问卷id")
+    private Integer surveyId;
+
+    @Schema(description = "类型 1:5星评价")
+    private Integer type;
+
+    @Schema(description = "标题")
+    private String title;
+
+    @Schema(description = "分数")
+    private Integer score;
+
+    @Schema(description = "排序")
+    private Integer sort;
+
+    @Schema(description = "语言地区")
+    private String language;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+}

+ 42 - 0
src/main/java/com/aitori/entity/message/WorkflowTaskMessage.java

@@ -0,0 +1,42 @@
+package com.aitori.entity.message;
+
+import com.fasterxml.jackson.annotation.JsonAlias;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class WorkflowTaskMessage {
+
+    @JsonAlias("task_id")
+    private String taskId;
+
+    private String status;
+
+    private String message;
+
+    private String type;
+
+    private TaskData data;
+
+    private TaskRequest request;
+
+    @JsonAlias("create_time")
+    private Long createTime;
+
+    @JsonAlias("update_time")
+    private Long updateTime;
+
+    @Data
+    public static class TaskData {
+        private List<String> images;
+        private List<String> video;
+    }
+
+    @Data
+    public static class TaskRequest {
+        private String template;
+        private Map<String, Object> inputs;
+    }
+}

+ 45 - 0
src/main/java/com/aitori/entity/request/AiAlbumQueryRequest.java

@@ -0,0 +1,45 @@
+package com.aitori.entity.request;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(description = "相册查询请求")
+public class AiAlbumQueryRequest implements Serializable {
+
+    @Schema(description = "页码", defaultValue = "1")
+    private Integer page = 1;
+
+    @Schema(description = "每页大小", defaultValue = "10")
+    private Integer size = 10;
+
+    @Schema(description = "相册id")
+    private Long id;
+
+    @Schema(description = "用户id")
+    private Long userId;
+
+    @Schema(description = "标题")
+    private String title;
+
+    @Schema(description = "任务id")
+    private String taskId;
+
+    @Schema(description = "订单id")
+    private String orderId;
+
+    @Schema(description = "写真状态 1:处理中(待生成分身) 2:生成中 3:已完成")
+    private Integer status;
+
+    @Schema(description = "开始时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime startTime;
+
+    @Schema(description = "结束时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime endTime;
+}

+ 45 - 0
src/main/java/com/aitori/entity/request/AiOrderV2QueryRequest.java

@@ -0,0 +1,45 @@
+package com.aitori.entity.request;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(description = "订单V2查询请求")
+public class AiOrderV2QueryRequest implements Serializable {
+
+    @Schema(description = "页码", defaultValue = "1")
+    private Integer page = 1;
+
+    @Schema(description = "每页大小", defaultValue = "10")
+    private Integer size = 10;
+
+    @Schema(description = "订单ID")
+    private String id;
+
+    @Schema(description = "用户ID")
+    private Integer userId;
+
+    @Schema(description = "订单状态 1:待支付 2:已支付 0:已退款")
+    private Integer status;
+
+    @Schema(description = "任务状态 0异常失败,1任务执行中,2执行完成,3执行失败")
+    private Integer taskStatus;
+
+    @Schema(description = "任务ID")
+    private String taskId;
+
+    @Schema(description = "订单语言")
+    private String orderLang;
+
+    @Schema(description = "开始时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime startTime;
+
+    @Schema(description = "结束时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime endTime;
+}

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff