Compare commits

...

6 Commits

Author SHA1 Message Date
d3abe32e1a feat(chat): 新增AI陪聊角色voiceId字段及使用逻辑
- AiCompanionVO、KeyboardAiCompanion实体新增voiceId字段与Swagger注解
- ChatServiceImpl同步/异步消息接口优先使用角色配置voiceId进行TTS
- 补充角色状态校验,下线角色禁止对话
2026-02-11 20:38:59 +08:00
e7437a39b2 feat(speech): 为语音转文字接口增加VIP每日调用限制 2026-02-04 20:01:25 +08:00
ca2cd87d89 feat(chat): 新增删除聊天记录接口并支持逻辑删除 2026-02-04 19:03:21 +08:00
190fb95bb6 feat(comment): 新增回复目标用户ID与昵称字段 2026-02-04 18:27:20 +08:00
884e5f5da4 refactor(auth): 精简放行白名单并移除举报类型校验
- 仅保留必要匿名接口,清理历史 demo 与业务无关路径
- 合并 CORS 配置,保持跨域设置不变
- AI 举报接口改为仅校验角色存在,不再校验类型范围,简化逻辑
- 商品实体与 VO 新增 level 字段
2026-02-04 16:38:22 +08:00
7f6edde956 fix(service): 调整AI聊天消息排序为倒序 2026-01-30 15:44:21 +08:00
16 changed files with 215 additions and 91 deletions

View File

@@ -57,67 +57,17 @@ public class SaTokenConfigure implements WebMvcConfigurer {
"/swagger-ui/**",
"/favicon.ico",
// 你的其他放行路径,例如登录接口
"/demo/test",
"/error",
"/demo/talk",
"/user/appleLogin",
"/demo/embed",
"/demo/testSaveEmbed",
"/demo/testSearch",
"/demo/testSearchText",
"/file/upload",
"/user/logout",
"/tag/list",
"/character/detail",
"/user/login",
"/character/listByUser",
"/user/detail",
"/user/register",
"/user/updateInfo",
"/character/updateUserCharacterSort",
"/character/delUserCharacter",
"/user/sendVerifyMail",
"/user/verifyMailCode",
"/character/listWithNotLogin",
"/character/listByTagWithNotLogin",
"/character/listByTag",
"/character/detailWithNotLogin",
"/character/addUserCharacter",
"/character/list",
"/user/resetPassWord",
"/chat/talk",
"/chat/save_embed",
"/themes/listByStyle",
"/wallet/balance",
"/themes/purchase",
"/themes/purchased",
"/themes/purchase/list",
"/themes/detail",
"/themes/recommended",
"/themes/search",
"/user-themes/batch-delete",
"/products/listByType",
"/products/detail",
"/products/inApp/list",
"/products/subscription/list",
"/purchase/handle",
"/apple/notification",
"/apple/receipt",
"/apple/validate-receipt",
"/user/inviteCode",
"/user/bindInviteCode",
"/themes/listAllStyles",
"/wallet/transactions",
"/themes/restore",
"/chat/message",
"/chat/voice",
"/chat/audio/*",
"/ai-companion/page",
"/chat/history",
"/ai-companion/comment/add",
"/speech/transcribe",
"/ai-companion/comment/page",
"/ai-companion/liked"
"/ai-companion/report",
"/apple/notification"
};
}
@Bean

View File

@@ -9,6 +9,7 @@ import com.yolo.keyborad.common.ErrorCode;
import com.yolo.keyborad.common.ResultUtils;
import com.yolo.keyborad.exception.BusinessException;
import com.yolo.keyborad.mapper.QdrantPayloadMapper;
import com.yolo.keyborad.model.dto.chat.ChatHistoryDeleteReq;
import com.yolo.keyborad.model.dto.chat.ChatHistoryPageReq;
import com.yolo.keyborad.model.dto.chat.ChatMessageReq;
import com.yolo.keyborad.model.dto.chat.ChatReq;
@@ -19,7 +20,6 @@ import com.yolo.keyborad.model.vo.AudioTaskVO;
import com.yolo.keyborad.model.vo.ChatMessageHistoryVO;
import com.yolo.keyborad.model.vo.ChatMessageVO;
import com.yolo.keyborad.model.vo.ChatSessionVO;
import com.yolo.keyborad.model.vo.ChatVoiceVO;
import com.yolo.keyborad.service.ChatService;
import com.yolo.keyborad.service.KeyboardAiChatMessageService;
import com.yolo.keyborad.service.KeyboardAiChatSessionService;
@@ -138,6 +138,18 @@ public class ChatController {
return ResultUtils.success(result);
}
@PostMapping("/history/delete")
@Operation(summary = "删除聊天记录", description = "根据聊天记录ID逻辑删除聊天消息")
public BaseResponse<Boolean> deleteHistory(@RequestBody ChatHistoryDeleteReq req) {
if (req == null || req.getId() == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "聊天记录ID不能为空");
}
Long userId = StpUtil.getLoginIdAsLong();
aiChatMessageService.deleteMessageById(userId, req.getId());
return ResultUtils.success(true);
}
@PostMapping("/session/reset")
@Operation(summary = "重置会话", description = "重置与AI角色的聊天会话将当前会话设为不活跃并创建新会话后续聊天记录将绑定到新会话")
public BaseResponse<ChatSessionVO> resetSession(@RequestBody SessionResetReq req) {

View File

@@ -1,16 +1,31 @@
package com.yolo.keyborad.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.yolo.keyborad.common.BaseResponse;
import com.yolo.keyborad.common.ErrorCode;
import com.yolo.keyborad.common.ResultUtils;
import com.yolo.keyborad.config.AppConfig;
import com.yolo.keyborad.config.NacosAppConfigCenter;
import com.yolo.keyborad.exception.BusinessException;
import com.yolo.keyborad.model.entity.KeyboardUser;
import com.yolo.keyborad.model.vo.SpeechToTextVO;
import com.yolo.keyborad.service.DeepgramService;
import com.yolo.keyborad.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
/**
* 语音服务控制器
*
@@ -22,13 +37,71 @@ import org.springframework.web.multipart.MultipartFile;
@Tag(name = "语音服务", description = "语音相关功能接口")
public class SpeechController {
private static final String SPEECH_DAILY_LIMIT_PREFIX = "speech:daily:limit:";
@Resource
private DeepgramService deepgramService;
@Resource
private UserService userService;
@Resource
private StringRedisTemplate stringRedisTemplate;
private final NacosAppConfigCenter.DynamicAppConfig cfgHolder;
public SpeechController(NacosAppConfigCenter.DynamicAppConfig cfgHolder) {
this.cfgHolder = cfgHolder;
}
@PostMapping("/transcribe")
@Operation(summary = "语音转文字", description = "上传音频文件并转换为文本")
public BaseResponse<SpeechToTextVO> transcribe(@RequestPart("file") MultipartFile file) {
// 获取当前登录用户ID
Long userId = StpUtil.getLoginIdAsLong();
// 查询用户信息
KeyboardUser user = userService.getById(userId);
// 获取VIP等级null视为0
int vipLevel = user != null && user.getVipLevel() != null ? user.getVipLevel() : 0;
// vipLevel >= 2 不做限制,直接放行
if (vipLevel < 2) {
AppConfig appConfig = cfgHolder.getRef().get();
String redisKey = SPEECH_DAILY_LIMIT_PREFIX + userId;
Integer dailyLimit = appConfig.getUserRegisterProperties().getVipFreeTrialTalk();
// 获取当前使用次数
String countStr = stringRedisTemplate.opsForValue().get(redisKey);
int currentCount = countStr != null ? Integer.parseInt(countStr) : 0;
// 检查是否超出限制
if (currentCount >= dailyLimit) {
log.warn("用户 {} VIP等级 {} 已达到每日语音转文字次数限制 {}", userId, vipLevel, dailyLimit);
throw new BusinessException(ErrorCode.VIP_TRIAL_LIMIT_REACHED);
}
}
// 调用语音转文字服务
SpeechToTextVO result = deepgramService.transcribe(file);
// 成功后扣减次数(仅 vipLevel < 2 的用户)
if (vipLevel < 2) {
String redisKey = SPEECH_DAILY_LIMIT_PREFIX + userId;
Long newCount = stringRedisTemplate.opsForValue().increment(redisKey);
// 设置到午夜过期(只有第一次设置时需要设置过期时间)
if (newCount != null && newCount == 1) {
LocalDateTime now = LocalDateTime.now(ZoneId.of("America/New_York"));
LocalDateTime midnight = LocalDateTime.of(LocalDate.now(ZoneId.of("America/New_York")).plusDays(1), LocalTime.MIDNIGHT);
long secondsUntilMidnight = ChronoUnit.SECONDS.between(now, midnight);
stringRedisTemplate.expire(redisKey, secondsUntilMidnight, TimeUnit.SECONDS);
}
log.info("用户 {} VIP等级 {} 今日已使用语音转文字 {}/{} 次", userId, vipLevel, newCount,
cfgHolder.getRef().get().getUserRegisterProperties().getVipFreeTrialTalk());
}
return ResultUtils.success(result);
}
}

View File

@@ -0,0 +1,17 @@
package com.yolo.keyborad.model.dto.chat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/*
* @author: ziin
* @date: 2026/2/4
*/
@Data
@Schema(description = "删除聊天记录请求")
public class ChatHistoryDeleteReq {
@Schema(description = "聊天记录ID", requiredMode = Schema.RequiredMode.REQUIRED)
private Long id;
}

View File

@@ -79,4 +79,8 @@ public class KeyboardAiChatMessage {
@TableField(value = "session_id")
@Schema(description = "会话Id")
private Long sessionId;
@TableField(value = "deleted")
@Schema(description = "是否删除")
private Boolean deleted;
}

View File

@@ -146,4 +146,8 @@ public class KeyboardAiCompanion {
@TableField(value = "prologue_audio")
@Schema(description="开场白音频")
private String prologueAudio;
@TableField(value = "voice_id")
@Schema(description="角色音频Id")
private String voiceId;
}

View File

@@ -108,4 +108,8 @@ public class KeyboardProductItems {
@TableField(value = "duration_days")
@Schema(description="订阅时长的具体天数")
private Integer durationDays;
@TableField(value = "level")
@Schema(description = "级别")
private Integer level;
}

View File

@@ -1,5 +1,6 @@
package com.yolo.keyborad.model.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -66,4 +67,9 @@ public class AiCompanionVO {
@Schema(description = "创建时间")
private Date createdAt;
@TableField(value = "voice_id")
@Schema(description="角色音频Id")
private String voiceId;
}

View File

@@ -32,6 +32,12 @@ public class CommentVO {
@Schema(description = "父评论ID")
private Long parentId;
@Schema(description = "回复给的用户ID仅回复评论有值")
private Long replyToUserId;
@Schema(description = "回复给的用户昵称(仅回复评论有值)")
private String replyToUserName;
@Schema(description = "根评论ID")
private Long rootId;

View File

@@ -44,5 +44,7 @@ public class KeyboardProductItemRespVO {
@Schema(description = "描述")
private String description;
@Schema(description = "级别")
private Integer level;
}

View File

@@ -41,4 +41,12 @@ public interface KeyboardAiChatMessageService extends IService<KeyboardAiChatMes
* @return 聊过天的AI角色ID列表按最近聊天时间倒序
*/
List<Long> getChattedCompanionIds(Long userId);
/**
* 根据聊天记录ID逻辑删除消息
*
* @param userId 当前用户ID
* @param messageId 聊天记录ID
*/
void deleteMessageById(Long userId, Long messageId);
}

View File

@@ -422,6 +422,7 @@ public class ApplePurchaseServiceImpl implements ApplePurchaseService {
// 检查VIP是否已过期
if (user.getVipExpiry() != null && user.getVipExpiry().toInstant().isBefore(Instant.now())) {
user.setIsVip(false);
user.setVipLevel(null);
userService.updateById(user);
log.info("User VIP expired: userId={}", userId);
}
@@ -464,6 +465,7 @@ public class ApplePurchaseServiceImpl implements ApplePurchaseService {
} else {
// 倒扣后已过期取消VIP
user.setIsVip(false);
user.setVipLevel(null);
user.setVipExpiry(Date.from(newExpiry)); // 保留倒扣后的时间而不是设为null
log.info("Subscription refunded, VIP expired after deduction: userId={}, newExpiry={}",
userId, newExpiry);
@@ -607,6 +609,7 @@ public class ApplePurchaseServiceImpl implements ApplePurchaseService {
// 4. 更新用户VIP状态
user.setIsVip(true);
user.setVipLevel(product.getLevel());
user.setVipExpiry(Date.from(newExpiry));
// 5. 保存用户信息到数据库
@@ -764,6 +767,7 @@ public class ApplePurchaseServiceImpl implements ApplePurchaseService {
// 6. 更新用户VIP状态
user.setIsVip(true); // 设置为VIP用户
user.setVipLevel(product.getLevel());
user.setVipExpiry(Date.from(newExpiry)); // 更新VIP过期时间
// 7. 保存用户信息到数据库

View File

@@ -10,6 +10,7 @@ import com.yolo.keyborad.config.NacosAppConfigCenter;
import com.yolo.keyborad.exception.BusinessException;
import com.yolo.keyborad.model.dto.chat.ChatReq;
import com.yolo.keyborad.model.dto.chat.ChatStreamMessage;
import com.yolo.keyborad.model.entity.KeyboardAiCompanion;
import com.yolo.keyborad.model.entity.KeyboardAiChatMessage;
import com.yolo.keyborad.model.entity.KeyboardCharacter;
import com.yolo.keyborad.model.entity.KeyboardUser;
@@ -406,8 +407,16 @@ public class ChatServiceImpl implements ChatService {
long startTime = System.currentTimeMillis();
// 获取AI人设的系统提示词
String systemPrompt = aiCompanionService.getSystemPromptById(companionId);
// 获取AI角色信息(用于系统提示词 + voiceId
KeyboardAiCompanion companion = aiCompanionService.getById(companionId);
if (companion == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "AI陪聊角色不存在");
}
if (companion.getStatus() == null || companion.getStatus() != 1) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "AI陪聊角色已下线");
}
String systemPrompt = companion.getSystemPrompt();
String voiceId = companion.getVoiceId();
// 获取最近20条聊天记录作为上下文
List<KeyboardAiChatMessage> historyMessages = aiChatMessageService.getRecentMessages(
@@ -444,8 +453,8 @@ public class ChatServiceImpl implements ChatService {
// 初始化音频任务状态为 processing
setAudioTaskStatus(audioId, AudioTaskVO.STATUS_PROCESSING, null, null);
// 异步执行 TTS + R2 上传
CompletableFuture.runAsync(() -> processAudioAsync(audioId, response, userId));
// 异步执行 TTS + R2 上传(优先使用角色配置的 voiceId
CompletableFuture.runAsync(() -> processAudioAsync(audioId, response, userId, voiceId));
return ChatMessageVO.builder()
.aiResponse(response)
@@ -515,14 +524,14 @@ public class ChatServiceImpl implements ChatService {
/**
* 异步处理音频TTS 转换 + 上传 R2
*/
private void processAudioAsync(String audioId, String text, String userId) {
private void processAudioAsync(String audioId, String text, String userId, String voiceId) {
try {
log.info("开始异步音频处理, audioId: {}", audioId);
long startTime = System.currentTimeMillis();
// 1. TTS 转换
long ttsStart = System.currentTimeMillis();
TextToSpeechVO ttsResult = elevenLabsService.textToSpeechWithTimestamps(text);
TextToSpeechVO ttsResult = elevenLabsService.textToSpeechWithTimestamps(text, voiceId);
long ttsDuration = System.currentTimeMillis() - ttsStart;
log.info("TTS 完成, audioId: {}, 耗时: {}ms", audioId, ttsDuration);

View File

@@ -6,6 +6,8 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yolo.keyborad.mapper.KeyboardAiChatMessageMapper;
import com.yolo.keyborad.common.ErrorCode;
import com.yolo.keyborad.exception.BusinessException;
import com.yolo.keyborad.model.entity.KeyboardAiChatMessage;
import com.yolo.keyborad.model.entity.KeyboardAiChatSession;
import com.yolo.keyborad.model.vo.ChatMessageHistoryVO;
@@ -46,8 +48,9 @@ public class KeyboardAiChatMessageServiceImpl extends ServiceImpl<KeyboardAiChat
queryWrapper.eq(KeyboardAiChatMessage::getUserId, userId)
.eq(KeyboardAiChatMessage::getCompanionId, companionId)
.eq(KeyboardAiChatMessage::getSessionId, activeSession.getId())
.orderByAsc(KeyboardAiChatMessage::getCreatedAt)
.orderByAsc(KeyboardAiChatMessage::getId);
.eq(KeyboardAiChatMessage::getDeleted,false)
.orderByDesc(KeyboardAiChatMessage::getCreatedAt)
.orderByDesc(KeyboardAiChatMessage::getId);
IPage<KeyboardAiChatMessage> entityPage = this.page(page, queryWrapper);
return entityPage.convert(entity -> BeanUtil.copyProperties(entity, ChatMessageHistoryVO.class));
}
@@ -110,4 +113,23 @@ public class KeyboardAiChatMessageServiceImpl extends ServiceImpl<KeyboardAiChat
.distinct()
.collect(java.util.stream.Collectors.toList());
}
@Override
public void deleteMessageById(Long userId, Long messageId) {
KeyboardAiChatMessage message = this.getById(messageId);
if (message == null || Boolean.TRUE.equals(message.getDeleted())) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "聊天记录不存在");
}
if (!userId.equals(message.getUserId())) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
KeyboardAiChatMessage update = new KeyboardAiChatMessage();
update.setId(messageId);
update.setDeleted(true);
boolean success = this.updateById(update);
if (!success) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "删除失败");
}
}
}

View File

@@ -14,6 +14,7 @@ import com.yolo.keyborad.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -74,24 +75,10 @@ public class KeyboardAiCompanionCommentServiceImpl extends ServiceImpl<KeyboardA
// 转换为VO
Map<Long, KeyboardUser> finalUserMap = userMap;
Map<Long, KeyboardAiCompanionComment> commentById = entityPage.getRecords().stream()
.collect(Collectors.toMap(KeyboardAiCompanionComment::getId, c -> c));
return entityPage.convert(entity -> {
CommentVO vo = new CommentVO();
vo.setId(entity.getId());
vo.setCompanionId(entity.getCompanionId());
vo.setUserId(entity.getUserId());
vo.setParentId(entity.getParentId());
vo.setRootId(entity.getRootId());
vo.setContent(entity.getContent());
vo.setLikeCount(entity.getLikeCount());
vo.setCreatedAt(entity.getCreatedAt());
// 填充用户信息
KeyboardUser user = finalUserMap.get(entity.getUserId());
if (user != null) {
vo.setUserName(user.getNickName());
vo.setUserAvatar(user.getAvatarUrl());
}
return vo;
return convertToVO(entity, finalUserMap, null, commentById);
});
}
@@ -113,13 +100,14 @@ public class KeyboardAiCompanionCommentServiceImpl extends ServiceImpl<KeyboardA
// 批量查询回复每条一级评论取前3条回复
Map<Long, List<KeyboardAiCompanionComment>> repliesMap = Map.of();
Map<Long, Long> replyCountMap = Map.of();
List<KeyboardAiCompanionComment> allReplies = List.of();
if (!topCommentIds.isEmpty()) {
// 查询所有回复
LambdaQueryWrapper<KeyboardAiCompanionComment> replyWrapper = new LambdaQueryWrapper<>();
replyWrapper.in(KeyboardAiCompanionComment::getRootId, topCommentIds)
.eq(KeyboardAiCompanionComment::getStatus, 1)
.orderByAsc(KeyboardAiCompanionComment::getCreatedAt);
List<KeyboardAiCompanionComment> allReplies = this.list(replyWrapper);
allReplies = this.list(replyWrapper);
// 按rootId分组每组取前3条
repliesMap = allReplies.stream()
@@ -137,6 +125,11 @@ public class KeyboardAiCompanionCommentServiceImpl extends ServiceImpl<KeyboardA
));
}
// 用于“回复给谁”的映射commentId -> comment
Map<Long, KeyboardAiCompanionComment> commentById = new HashMap<>();
entityPage.getRecords().forEach(c -> commentById.put(c.getId(), c));
allReplies.forEach(c -> commentById.put(c.getId(), c));
// 收集所有需要查询的用户ID一级评论 + 回复)
List<Long> userIds = new ArrayList<>(entityPage.getRecords().stream()
.map(KeyboardAiCompanionComment::getUserId)
@@ -171,12 +164,12 @@ public class KeyboardAiCompanionCommentServiceImpl extends ServiceImpl<KeyboardA
Map<Long, Long> finalReplyCountMap = replyCountMap;
return entityPage.convert(entity -> {
CommentVO vo = convertToVO(entity, finalUserMap, likedCommentIds);
CommentVO vo = convertToVO(entity, finalUserMap, likedCommentIds, commentById);
// 填充回复列表
List<KeyboardAiCompanionComment> replies = finalRepliesMap.getOrDefault(entity.getId(), List.of());
List<CommentVO> replyVOs = replies.stream()
.map(reply -> convertToVO(reply, finalUserMap, likedCommentIds))
.map(reply -> convertToVO(reply, finalUserMap, likedCommentIds, commentById))
.collect(Collectors.toList());
vo.setReplies(replyVOs);
vo.setReplyCount(finalReplyCountMap.getOrDefault(entity.getId(), 0L).intValue());
@@ -188,7 +181,12 @@ public class KeyboardAiCompanionCommentServiceImpl extends ServiceImpl<KeyboardA
/**
* 将评论实体转换为VO
*/
private CommentVO convertToVO(KeyboardAiCompanionComment entity, Map<Long, KeyboardUser> userMap, Set<Long> likedCommentIds) {
private CommentVO convertToVO(
KeyboardAiCompanionComment entity,
Map<Long, KeyboardUser> userMap,
Set<Long> likedCommentIds,
Map<Long, KeyboardAiCompanionComment> commentById
) {
CommentVO vo = new CommentVO();
vo.setId(entity.getId());
vo.setCompanionId(entity.getCompanionId());
@@ -198,7 +196,7 @@ public class KeyboardAiCompanionCommentServiceImpl extends ServiceImpl<KeyboardA
vo.setContent(entity.getContent());
vo.setLikeCount(entity.getLikeCount());
vo.setCreatedAt(entity.getCreatedAt());
vo.setLiked(likedCommentIds.contains(entity.getId()));
vo.setLiked(likedCommentIds != null && likedCommentIds.contains(entity.getId()));
// 填充用户信息
KeyboardUser user = userMap.get(entity.getUserId());
@@ -206,6 +204,19 @@ public class KeyboardAiCompanionCommentServiceImpl extends ServiceImpl<KeyboardA
vo.setUserName(user.getNickName());
vo.setUserAvatar(user.getAvatarUrl());
}
// 填充“回复给谁”(仅回复评论有值)
if (entity.getParentId() != null && commentById != null) {
KeyboardAiCompanionComment parent = commentById.get(entity.getParentId());
if (parent != null) {
vo.setReplyToUserId(parent.getUserId());
KeyboardUser replyToUser = userMap.get(parent.getUserId());
if (replyToUser != null) {
vo.setReplyToUserName(replyToUser.getNickName());
}
}
}
return vo;
}
}

View File

@@ -38,14 +38,6 @@ public class KeyboardAiCompanionReportServiceImpl extends ServiceImpl<KeyboardAi
throw new BusinessException(ErrorCode.REPORT_TYPE_EMPTY);
}
// 校验每个 reportType 在有效范围内1,2,3,4,5,99
List<Short> validTypes = List.of((short) 1, (short) 2, (short) 3, (short) 4, (short) 5, (short) 99);
for (Short type : req.getReportTypes()) {
if (!validTypes.contains(type)) {
throw new BusinessException(ErrorCode.REPORT_TYPE_INVALID);
}
}
// 校验 AI 角色是否存在
KeyboardAiCompanion companion = aiCompanionService.getById(req.getCompanionId());
if (companion == null) {