# AI 服务积分扣减公共服务 - 实现计划 > 版本: v1.0 > 日期: 2026-02-22 > 基于设计文档: points-service-integration.md --- ## 一、实现概览 ### 当前状态分析 | 组件 | 文件路径 | 当前状态 | 需要修改 | |------|---------|---------|---------| | PointRecordDO | `muye/pointrecord/dal/PointRecordDO.java` | 完整 | 新增 status 字段 | | MemberUserProfileMapper | `muye/memberuserprofile/mapper/MemberUserProfileMapper.java` | 仅查询 | 新增原子扣减方法 | | AiModelConfigMapper | `muye/aimodelconfig/mapper/AiModelConfigMapper.java` | 仅分页 | 新增按平台查询方法 | | PointsService | 不存在 | **需新建** | 新建接口+实现 | | DifyService | 不存在 | **需新建** | 新建服务 | --- ## 二、实现步骤 ### 步骤 1: 数据库层修改 #### 1.1 PointRecordDO 新增 status 字段 **文件**: `muye/pointrecord/dal/PointRecordDO.java` ```java // 新增字段 private String status; // 状态:pending(预扣) / confirmed(已确认) / canceled(已取消) ``` **数据库迁移**: ```sql ALTER TABLE muey_point_record ADD COLUMN status VARCHAR(20) DEFAULT 'confirmed' COMMENT '状态:pending-预扣 confirmed-已确认 canceled-已取消'; ``` #### 1.2 MemberUserProfileMapper 新增方法 **文件**: `muye/memberuserprofile/mapper/MemberUserProfileMapper.java` ```java /** * 根据用户ID查询档案 */ default MemberUserProfileDO selectByUserId(Long userId) { return selectOne(new LambdaQueryWrapperX() .eq(MemberUserProfileDO::getUserId, userId)); } /** * 原子扣减积分(乐观锁) * @return 影响行数,0表示余额不足 */ @Update("UPDATE muey_member_user_profile " + "SET remaining_points = remaining_points - #{points}, " + " used_points = used_points + #{points}, " + " update_time = NOW() " + "WHERE user_id = #{userId} AND remaining_points >= #{points}") int updatePointsDeduct(@Param("userId") Long userId, @Param("points") Integer points); ``` #### 1.3 AiModelConfigMapper 新增方法 **文件**: `muye/aimodelconfig/mapper/AiModelConfigMapper.java` ```java /** * 根据平台和模型类型查询配置 */ default AiModelConfigDO selectByPlatformAndModelType(String platform, String modelType) { return selectOne(new LambdaQueryWrapperX() .eq(AiModelConfigDO::getPlatform, platform) .eq(AiModelConfigDO::getModelType, modelType) .eq(AiModelConfigDO::getStatus, 1)); } ``` #### 1.4 PointRecordMapper 新增方法 **文件**: `muye/pointrecord/mapper/PointRecordMapper.java` ```java /** * 取消过期的预扣记录(30分钟前) */ @Update("UPDATE muey_point_record SET status = 'canceled', update_time = NOW() " + "WHERE status = 'pending' AND create_time < DATE_SUB(NOW(), INTERVAL 30 MINUTE)") int cancelExpiredPendingRecords(); ``` --- ### 步骤 2: 公共积分服务 #### 2.1 新建 PointsService 接口 **文件**: `muye/points/service/PointsService.java`(新建) ```java public interface PointsService { /** * 获取积分配置 */ AiModelConfigDO getConfig(String platform, String modelType); /** * 预检积分(余额不足抛异常) */ void checkPoints(Long userId, Integer points); /** * 即时扣减(同步场景) */ Long deductPoints(Long userId, Integer points, String bizType, String bizId); /** * 创建预扣(流式/异步场景) * @return 预扣记录ID */ Long createPendingDeduct(Long userId, Integer points, String bizType, String bizId); /** * 确认预扣(实际扣减) */ void confirmPendingDeduct(Long recordId); /** * 取消预扣(不扣费) */ void cancelPendingDeduct(Long recordId); } ``` #### 2.2 新建 PointsServiceImpl 实现 **文件**: `muye/points/service/PointsServiceImpl.java`(新建) 核心逻辑: - `deductPoints`: 调用 Mapper 原子扣减,失败抛 `POINTS_DEDUCT_FAILED` - `createPendingDeduct`: 创建 status=pending 的记录 - `confirmPendingDeduct`: 执行实际扣减 + 更新 status=confirmed - `cancelPendingDeduct`: 更新 status=canceled #### 2.3 新建错误码常量 **文件**: `ErrorCodeConstants.java`(修改) ```java // 积分相关错误码 1001001-1001003 ErrorCode POINTS_INSUFFICIENT = new ErrorCode(1001001, "积分不足"); ErrorCode POINTS_CONFIG_NOT_FOUND = new ErrorCode(1001002, "积分配置不存在"); ErrorCode POINTS_DEDUCT_FAILED = new ErrorCode(1001003, "积分扣减失败"); ``` --- ### 步骤 3: Dify 工作流集成 #### 3.1 新建 Dify 配置类 **文件**: `dify/config/DifyProperties.java`(新建) ```java @ConfigurationProperties(prefix = "yudao.dify") public class DifyProperties { private String apiUrl; private Integer timeout = 60; } ``` #### 3.2 新建 DifyClient **文件**: `dify/client/DifyClient.java`(新建) - 调用 Dify 工作流 API - 支持流式响应 - 传入 sysPrompt 参数 #### 3.3 新建 DifyService **文件**: `dify/service/DifyService.java`(新建) ```java public interface DifyService { /** * 流式聊天(带积分扣减) */ Flux chatStream(DifyChatReqVO reqVO, Long userId); } ``` #### 3.4 新建 DifyController **文件**: `dify/controller/AppDifyController.java`(新建) ```java @PostMapping("/api/tik/dify/chat/stream") public Flux> chatStream(@RequestBody DifyChatReqVO reqVO); ``` --- ### 步骤 4: 集成到现有服务 #### 4.1 AiChatMessageService 集成 **文件**: `service/chat/AiChatMessageServiceImpl.java`(修改) 在 `sendChatMessageStream` 方法中: 1. 调用前:`pointsService.checkPoints()` 2. 创建预扣:`pointsService.createPendingDeduct()` 3. 流结束:`pointsService.confirmPendingDeduct()` 4. 出错/取消:`pointsService.cancelPendingDeduct()` --- ### 步骤 5: 定时任务 #### 5.1 预扣过期清理任务 **文件**: `job/PointsPendingCleanJob.java`(新建) - 每 5 分钟执行 - 调用 `pointRecordMapper.cancelExpiredPendingRecords()` --- ## 三、文件清单 ### 新建文件 | 文件 | 路径 | |------|------| | PointsService | `muye/points/service/PointsService.java` | | PointsServiceImpl | `muye/points/service/PointsServiceImpl.java` | | DifyProperties | `dify/config/DifyProperties.java` | | DifyClient | `dify/client/DifyClient.java` | | DifyService | `dify/service/DifyService.java` | | DifyServiceImpl | `dify/service/DifyServiceImpl.java` | | DifyReqVO | `dify/vo/DifyChatReqVO.java` | | DifyRespVO | `dify/vo/DifyChatRespVO.java` | | AppDifyController | `dify/controller/AppDifyController.java` | | PointsPendingCleanJob | `job/PointsPendingCleanJob.java` | ### 修改文件 | 文件 | 修改内容 | |------|---------| | PointRecordDO | 新增 status 字段 | | MemberUserProfileMapper | 新增 selectByUserId、updatePointsDeduct 方法 | | AiModelConfigMapper | 新增 selectByPlatformAndModelType 方法 | | PointRecordMapper | 新增 cancelExpiredPendingRecords 方法 | | ErrorCodeConstants | 新增积分相关错误码 | | AiChatMessageServiceImpl | 集成积分扣减逻辑 | --- ## 四、数据库变更 ```sql -- 1. 积分记录表新增状态字段 ALTER TABLE muey_point_record ADD COLUMN status VARCHAR(20) DEFAULT 'confirmed' COMMENT '状态:pending-预扣 confirmed-已确认 canceled-已取消'; -- 2. 添加索引(可选优化) CREATE INDEX idx_point_record_status_time ON muey_point_record(status, create_time); ``` --- ## 五、依赖关系 ``` 业务层 ├── DifyService ──────┐ ├── AiChatMessageService ──┼──→ PointsService(公共服务) ├── TikHubService ─────────┤ │ ├── VoiceService ──────────┤ ├── AiModelConfigMapper(配置查询) └── DigitalHumanService ───┘ ├── MemberUserProfileMapper(积分扣减) └── PointRecordMapper(流水记录) ``` --- ## 六、验收标准 - [ ] PointsService 单元测试通过 - [ ] Dify 流式接口正常返回 - [ ] 积分不足时抛出正确异常 - [ ] 流式中断时预扣正确取消 - [ ] 预扣过期定时任务正常运行 - [ ] 积分扣减原子性(并发不超扣)