Administrator
2025-08-05 f7a1b1996d92d96004a7a1ab1bf289516732c7e4
feat(ai): 添加 AI 用户陪练功能

- 新增 AiMemberTalkItemService 和 AiMemberTalkService接口及其实现类
- 添加 AiProductRoleLinkService 接口及其简单实现
- 新增 AiService 接口及其实现类,用于调用 AI 服务
- 添加相关请求和响应对象,以及控制器类
- 在 application-test.yml 中添加 AI 服务配置
8 files modified
7 files added
515 ■■■■■ changed files
src/main/java/cc/mrbird/febs/ai/controller/memberTalk/ApiMemberTalkController.java 54 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/req/memberTalk/ApiMemberTalkAnswerDto.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/req/memberTalk/ApiMemberTalkDto.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/res/ai/AiResponse.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/res/memberTalk/ApiMemberTalkVo.java 35 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/AiMemberTalkItemService.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/AiMemberTalkService.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/AiProductRoleLinkService.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/AiService.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberTalkItemServiceImpl.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberTalkServiceImpl.java 137 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/impl/AiProductRoleLinkServiceImpl.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/impl/AiServiceImpl.java 147 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/HSController.java 2 ●●● patch | view | raw | blame | history
src/main/resources/application-test.yml 8 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/controller/memberTalk/ApiMemberTalkController.java
New file
@@ -0,0 +1,54 @@
package cc.mrbird.febs.ai.controller.memberTalk;
import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkAnswerDto;
import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkDto;
import cc.mrbird.febs.ai.res.memberTalk.ApiMemberTalkVo;
import cc.mrbird.febs.ai.service.AiMemberTalkService;
import cc.mrbird.febs.common.entity.FebsResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author Administrator
 */
@Slf4j
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/api/ai/memberTalk")
@Api(value = "ApiMemberTalkController", tags = "AI-用户陪练")
public class ApiMemberTalkController {
    private final AiMemberTalkService aiMemberTalkService;
    @ApiOperation(value = "开始陪练", notes = "开始陪练")
    @ApiResponses({
            @ApiResponse(code = 200, message = "success", response = ApiMemberTalkVo.class)
    })
    @PostMapping(value = "/start")
    public FebsResponse start(@RequestBody @Validated ApiMemberTalkDto dto) {
        return aiMemberTalkService.start(dto);
    }
    @ApiOperation(value = "回答", notes = "回答")
    @ApiResponses({
            @ApiResponse(code = 200, message = "success", response = ApiMemberTalkVo.class)
    })
    @PostMapping(value = "/answer")
    public FebsResponse answer(@RequestBody @Validated ApiMemberTalkAnswerDto dto) {
        return aiMemberTalkService.answer(dto);
    }
}
src/main/java/cc/mrbird/febs/ai/req/memberTalk/ApiMemberTalkAnswerDto.java
New file
@@ -0,0 +1,26 @@
package cc.mrbird.febs.ai.req.memberTalk;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
 * @author Administrator
 */
@Data
@ApiModel(value = "ApiMemberTalkAnswerDto", description = "参数")
public class ApiMemberTalkAnswerDto {
    @NotBlank(message = "会话ID不能为空")
    @ApiModelProperty(value = "会话ID", example = "10")
    private String id;
    @NotBlank(message = "回答不能为空")
    @ApiModelProperty(value = "回答", example = "10")
    private String reqContext;
}
src/main/java/cc/mrbird/febs/ai/req/memberTalk/ApiMemberTalkDto.java
New file
@@ -0,0 +1,21 @@
package cc.mrbird.febs.ai.req.memberTalk;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
 * @author Administrator
 */
@Data
@ApiModel(value = "ApiMemberTalkDto", description = "参数")
public class ApiMemberTalkDto {
    @NotBlank(message = "ID不能为空")
    @ApiModelProperty(value = "产品ID", example = "10")
    private String id;
}
src/main/java/cc/mrbird/febs/ai/res/ai/AiResponse.java
New file
@@ -0,0 +1,16 @@
package cc.mrbird.febs.ai.res.ai;
import io.swagger.annotations.ApiModel;
import lombok.Data;
/**
 * @author Administrator
 */
@Data
@ApiModel(value = "AiResponse", description = "参数")
public class AiResponse {
    private String code;
    private String description;
    private String resContext;
}
src/main/java/cc/mrbird/febs/ai/res/memberTalk/ApiMemberTalkVo.java
New file
@@ -0,0 +1,35 @@
package cc.mrbird.febs.ai.res.memberTalk;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * @author Administrator
 */
@Data
@ApiModel(value = "ApiMemberTalkVo", description = "参数")
public class ApiMemberTalkVo {
    /**
     * 用户对话ID (UUID)
     */
    @ApiModelProperty(value = "会话ID")
    private String memberTalkId;
    /**
     * 类型 1-AI提问 2-用户回答 3-AI分析结果
     */
    @ApiModelProperty(value = "类型 1-AI提问 2-用户回答 3-AI分析结果")
    private Integer type;
    /**
     * 内容
     */
    @ApiModelProperty(value = "内容")
    private String context;
}
src/main/java/cc/mrbird/febs/ai/service/AiMemberTalkItemService.java
@@ -1,7 +1,10 @@
package cc.mrbird.febs.ai.service;
import cc.mrbird.febs.ai.entity.AiMemberTalkItem;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Date;
import java.util.List;
/**
@@ -13,4 +16,7 @@
public interface AiMemberTalkItemService extends IService<AiMemberTalkItem> {
    void add(String memberUuid, String id, int type, String resContext, Date createdTime);
    AiMemberTalkItem getByQuery(LambdaQueryWrapper<AiMemberTalkItem> memberTalkItemQuery);
}
src/main/java/cc/mrbird/febs/ai/service/AiMemberTalkService.java
@@ -1,8 +1,13 @@
package cc.mrbird.febs.ai.service;
import cc.mrbird.febs.ai.entity.AiMemberTalk;
import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkAnswerDto;
import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkDto;
import cc.mrbird.febs.common.entity.FebsResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import java.util.Date;
/**
 * AI用户对话训练记录 Service接口
@@ -13,4 +18,15 @@
public interface AiMemberTalkService extends IService<AiMemberTalk> {
    AiMemberTalk getById(String id);
    FebsResponse start(ApiMemberTalkDto dto);
    AiMemberTalk getByQuery(LambdaQueryWrapper<AiMemberTalk> query);
    void updateTimeUpdate(Date nowTime, String id);
    FebsResponse answer(ApiMemberTalkAnswerDto dto);
    AiMemberTalk add(String memberUuid, String productId, Date nowTime);
}
src/main/java/cc/mrbird/febs/ai/service/AiProductRoleLinkService.java
@@ -1,10 +1,15 @@
package cc.mrbird.febs.ai.service;
import cc.mrbird.febs.ai.entity.AiMemberTalk;
import cc.mrbird.febs.ai.entity.AiProductRoleLink;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * @author Administrator
 */
public interface AiProductRoleLinkService extends IService<AiProductRoleLink> {
    AiProductRoleLink getByQuery(LambdaQueryWrapper<AiProductRoleLink> query);
}
src/main/java/cc/mrbird/febs/ai/service/AiService.java
New file
@@ -0,0 +1,14 @@
package cc.mrbird.febs.ai.service;
import cc.mrbird.febs.ai.res.ai.AiResponse;
/**
 * @author Administrator
 */
public interface AiService {
    AiResponse start(String productRoleId, String content);
    AiResponse question(String promptTemplate,String linkId,String content);
}
src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberTalkItemServiceImpl.java
@@ -3,6 +3,7 @@
import cc.mrbird.febs.ai.entity.AiMemberTalkItem;
import cc.mrbird.febs.ai.mapper.AiMemberTalkItemMapper;
import cc.mrbird.febs.ai.service.AiMemberTalkItemService;
import cc.mrbird.febs.ai.utils.UUID;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
@@ -10,6 +11,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
/**
@@ -26,4 +28,20 @@
    private final AiMemberTalkItemMapper aiMemberTalkItemMapper;
    @Override
    public void add(String memberUuid, String id, int type, String resContext,Date createdTime) {
        AiMemberTalkItem aiMemberTalkItem = new AiMemberTalkItem();
        aiMemberTalkItem.setId(UUID.getSimpleUUIDString());
        aiMemberTalkItem.setCreatedTime(createdTime);
        aiMemberTalkItem.setMemberId(memberUuid);
        aiMemberTalkItem.setMemberTalkId(id);
        aiMemberTalkItem.setType(type);
        aiMemberTalkItem.setContext(resContext);
        aiMemberTalkItemMapper.insert(aiMemberTalkItem);
    }
    @Override
    public AiMemberTalkItem getByQuery(LambdaQueryWrapper<AiMemberTalkItem> memberTalkItemQuery) {
        return aiMemberTalkItemMapper.selectOne(memberTalkItemQuery);
    }
}
src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberTalkServiceImpl.java
@@ -1,16 +1,30 @@
package cc.mrbird.febs.ai.service.impl;
import cc.mrbird.febs.ai.entity.AiMemberTalk;
import cc.mrbird.febs.ai.entity.AiMemberTalkItem;
import cc.mrbird.febs.ai.entity.AiProductRoleLink;
import cc.mrbird.febs.ai.mapper.AiMemberTalkMapper;
import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkAnswerDto;
import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkDto;
import cc.mrbird.febs.ai.res.ai.AiResponse;
import cc.mrbird.febs.ai.res.memberTalk.ApiMemberTalkVo;
import cc.mrbird.febs.ai.service.AiMemberTalkItemService;
import cc.mrbird.febs.ai.service.AiMemberTalkService;
import cc.mrbird.febs.ai.service.AiProductRoleLinkService;
import cc.mrbird.febs.ai.service.AiService;
import cc.mrbird.febs.ai.utils.UUID;
import cc.mrbird.febs.common.entity.FebsResponse;
import cc.mrbird.febs.common.utils.LoginUserUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Date;
/**
 * AI用户对话训练记录 Service实现类
@@ -24,6 +38,125 @@
public class AiMemberTalkServiceImpl extends ServiceImpl<AiMemberTalkMapper, AiMemberTalk> implements AiMemberTalkService {
    private final AiMemberTalkMapper aiMemberTalkMapper;
    private final AiProductRoleLinkService aiProductRoleLinkService;
    private final AiMemberTalkItemService aiMemberTalkItemService;
    private final AiService aiService;
    @Override
    public AiMemberTalk getById(String id) {
        return aiMemberTalkMapper.selectById( id);
    }
    @Override
    public FebsResponse start(ApiMemberTalkDto dto) {
        String memberUuid = LoginUserUtil.getLoginUser().getMemberUuid();
        String productId = dto.getId();
        LambdaQueryWrapper<AiProductRoleLink> productLinkQuery = Wrappers.lambdaQuery(AiProductRoleLink.class);
        productLinkQuery.eq(AiProductRoleLink::getProductId,productId);
        productLinkQuery.last("limit 1");
        AiProductRoleLink aiProductRoleLink = aiProductRoleLinkService.getByQuery(productLinkQuery);
        if(ObjectUtil.isNull(aiProductRoleLink)){
            throw new RuntimeException("产品AI陪练不存在");
        }
        Date nowTime = new Date();
        LambdaQueryWrapper<AiMemberTalk> query = Wrappers.lambdaQuery(AiMemberTalk.class);
        query.eq(AiMemberTalk::getMemberId,memberUuid);
        query.eq(AiMemberTalk::getProductId,productId);
        query.last("limit 1");
        AiMemberTalk aiMemberTalk = this.getByQuery(query);
        if (ObjectUtil.isNull(aiMemberTalk)){
            aiMemberTalk = this.add(memberUuid,productId,nowTime);
        }
        AiResponse aiResponse = aiService.start(aiProductRoleLink.getProductRoleId(),"开始出题");
        if(aiResponse.getCode().equals("200")){
            aiMemberTalkItemService.add(memberUuid,aiMemberTalk.getId(),1,aiResponse.getResContext(),nowTime);
            this.updateTimeUpdate(nowTime,aiMemberTalk.getId());
        }else{
            throw new RuntimeException(aiResponse.getDescription());
        }
        ApiMemberTalkVo apiMemberTalkVo = new ApiMemberTalkVo();
        apiMemberTalkVo.setMemberTalkId(aiMemberTalk.getId());
        apiMemberTalkVo.setType(1);
        apiMemberTalkVo.setContext(aiResponse.getResContext());
        return new FebsResponse().success().data(apiMemberTalkVo);
    }
    @Override
    public AiMemberTalk getByQuery(LambdaQueryWrapper<AiMemberTalk> query) {
        return aiMemberTalkMapper.selectOne( query);
    }
    @Override
    public void updateTimeUpdate(Date nowTime, String id) {
        aiMemberTalkMapper.update(
                null,
                Wrappers.lambdaUpdate(AiMemberTalk.class)
                .set(AiMemberTalk::getUpdatedTime,nowTime)
                .eq(AiMemberTalk::getId,id));
    }
    public static final String ANSWER_FORMAT = "{}/n回答:{}/n请根据回答给出以下四个方面的总结,这个四个方面分别是亮点、建议、参考答案和核心知识点回顾。重点:四个方面的总结都是必须要有内容。";
    @Override
    public FebsResponse answer(ApiMemberTalkAnswerDto dto) {
        String memberUuid = LoginUserUtil.getLoginUser().getMemberUuid();
        String memberTalkId = dto.getId();
        AiMemberTalk aiMemberTalk = this.getById(memberTalkId);
        if (ObjectUtil.isNull(aiMemberTalk)){
            throw new RuntimeException("产品AI陪练对话不存在");
        }
        LambdaQueryWrapper<AiProductRoleLink> productLinkQuery = Wrappers.lambdaQuery(AiProductRoleLink.class);
        productLinkQuery.eq(AiProductRoleLink::getProductId,aiMemberTalk.getProductId());
        productLinkQuery.last("limit 1");
        AiProductRoleLink aiProductRoleLink = aiProductRoleLinkService.getByQuery(productLinkQuery);
        if(ObjectUtil.isNull(aiProductRoleLink)){
            throw new RuntimeException("产品AI陪练不存在");
        }
        String reqContext = dto.getReqContext();
        LambdaQueryWrapper<AiMemberTalkItem> memberTalkItemQuery = Wrappers.lambdaQuery(AiMemberTalkItem.class);
        memberTalkItemQuery.eq(AiMemberTalkItem::getMemberId,memberUuid);
        memberTalkItemQuery.eq(AiMemberTalkItem::getMemberTalkId,memberTalkId);
        memberTalkItemQuery.eq(AiMemberTalkItem::getType,1);
        memberTalkItemQuery.orderByDesc(AiMemberTalkItem::getCreatedTime);
        memberTalkItemQuery.last("limit 1");
        AiMemberTalkItem aiMemberTalkItem = aiMemberTalkItemService.getByQuery(memberTalkItemQuery);
        aiMemberTalkItemService.add(memberUuid,aiMemberTalk.getId(),2,reqContext,new Date());
        String format = StrUtil.format(ANSWER_FORMAT, aiMemberTalkItem.getContext(), reqContext);
        log.info("format:{}",format);
        AiResponse aiResponse = aiService.start(aiProductRoleLink.getProductRoleId(), format);
        if(aiResponse.getCode().equals("200")){
            Date nowTime = new Date();
            aiMemberTalkItemService.add(memberUuid,aiMemberTalk.getId(),3,aiResponse.getResContext(),nowTime);
            this.updateTimeUpdate(nowTime,aiMemberTalk.getId());
        }else{
            throw new RuntimeException(aiResponse.getDescription());
        }
        ApiMemberTalkVo apiMemberTalkVo = new ApiMemberTalkVo();
        apiMemberTalkVo.setMemberTalkId(aiMemberTalk.getId());
        apiMemberTalkVo.setType(1);
        apiMemberTalkVo.setContext(aiResponse.getResContext());
        return new FebsResponse().success().data(apiMemberTalkVo);
    }
    @Override
    public AiMemberTalk add(String memberUuid, String productId, Date nowTime) {
        AiMemberTalk aiMemberTalk = new AiMemberTalk();
        aiMemberTalk.setId(UUID.getSimpleUUIDString());
        aiMemberTalk.setCreatedTime(nowTime);
        aiMemberTalk.setMemberId(memberUuid);
        aiMemberTalk.setProductId(productId);
        aiMemberTalkMapper.insert(aiMemberTalk);
        return aiMemberTalk;
    }
}
src/main/java/cc/mrbird/febs/ai/service/impl/AiProductRoleLinkServiceImpl.java
@@ -1,8 +1,10 @@
package cc.mrbird.febs.ai.service.impl;
import cc.mrbird.febs.ai.entity.AiMemberTalk;
import cc.mrbird.febs.ai.entity.AiProductRoleLink;
import cc.mrbird.febs.ai.mapper.AiProductRoleLinkMapper;
import cc.mrbird.febs.ai.service.AiProductRoleLinkService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -15,4 +17,10 @@
@Service
@RequiredArgsConstructor
public class AiProductRoleLinkServiceImpl extends ServiceImpl<AiProductRoleLinkMapper, AiProductRoleLink> implements AiProductRoleLinkService {
    private final AiProductRoleLinkMapper aiProductRoleLinkMapper;
    @Override
    public AiProductRoleLink getByQuery(LambdaQueryWrapper<AiProductRoleLink> query) {
        return aiProductRoleLinkMapper.selectOne( query);
    }
}
src/main/java/cc/mrbird/febs/ai/service/impl/AiServiceImpl.java
New file
@@ -0,0 +1,147 @@
package cc.mrbird.febs.ai.service.impl;
import cc.mrbird.febs.ai.entity.AiProductRole;
import cc.mrbird.febs.ai.res.ai.AiResponse;
import cc.mrbird.febs.ai.service.AiProductRoleService;
import cc.mrbird.febs.ai.service.AiService;
import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionChoice;
import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionRequest;
import com.volcengine.ark.runtime.model.completion.chat.ChatMessage;
import com.volcengine.ark.runtime.model.completion.chat.ChatMessageRole;
import com.volcengine.ark.runtime.service.ArkService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.Dispatcher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
 * @author Administrator
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AiServiceImpl implements AiService {
    private static final String CODE_SUCCESS = "200";
    private static final String CODE_NOT_FOUND = "201";
    private static final String CODE_ERROR = "500";
    private final AiProductRoleService aiProductRoleService;
    @Value("${ai.service.ak}")
    private String ak;
    @Value("${ai.service.sk}")
    private String sk;
    @Value("${ai.service.base-url}")
    private String baseUrl;
    private ArkService service;
    @PostConstruct
    public void init() {
        ConnectionPool connectionPool = new ConnectionPool(10, 30, TimeUnit.SECONDS);
        Dispatcher dispatcher = new Dispatcher();
        this.service = ArkService.builder()
                .dispatcher(dispatcher)
                .connectionPool(connectionPool)
                .baseUrl(baseUrl)
                .ak(ak)
                .sk(sk)
                .build();
    }
    @PreDestroy
    public void destroy() {
        if (service != null) {
            service.shutdownExecutor();
        }
    }
    @Override
    public AiResponse start(String productRoleId, String content) {
        if (!StringUtils.hasText(productRoleId)) {
            log.warn("productRoleId 不能为空");
            return buildErrorResponse(CODE_NOT_FOUND, "AI陪练不存在");
        }
        AiProductRole aiProductRole = aiProductRoleService.getById(productRoleId);
        if (aiProductRole == null) {
            log.warn("未找到对应的角色配置,productRoleId: {}", productRoleId);
            return buildErrorResponse(CODE_NOT_FOUND, "AI陪练不存在");
        }
        String promptTemplate = aiProductRole.getPromptTemplate();
        String linkId = aiProductRole.getLinkId();
        if (!StringUtils.hasText(promptTemplate) || !StringUtils.hasText(linkId)) {
            log.warn("角色配置不完整,promptTemplate 或 linkId 为空,productRoleId: {}", productRoleId);
            return buildErrorResponse(CODE_ERROR, "角色配置不完整");
        }
        return question(promptTemplate, linkId, content);
    }
    @Override
    public AiResponse question(String promptTemplate, String linkId, String content) {
        if (!StringUtils.hasText(promptTemplate) || !StringUtils.hasText(linkId) || !StringUtils.hasText(content)) {
            log.warn("请求参数不完整,promptTemplate: {}, linkId: {}, content: {}", promptTemplate, linkId, content);
            return buildErrorResponse(CODE_ERROR, "请求参数不完整");
        }
        final List<ChatMessage> messages = new ArrayList<>();
        final ChatMessage systemMessage = ChatMessage.builder().role(ChatMessageRole.SYSTEM).content(promptTemplate).build();
        final ChatMessage userMessage = ChatMessage.builder().role(ChatMessageRole.USER).content(content).build();
        messages.add(systemMessage);
        messages.add(userMessage);
        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
                .model(linkId)
                .messages(messages)
                .temperature(1.0)
                .topP(0.7)
                .maxTokens(4096)
                .frequencyPenalty(0.0)
                .build();
        try {
            List<ChatCompletionChoice> choices = service.createChatCompletion(chatCompletionRequest).getChoices();
            String result = choices.stream()
                    .map(choice -> choice.getMessage().getContent())
                    .filter(contentObj -> contentObj != null)
                    .map(Object::toString)
                    .collect(Collectors.joining());
            return buildSuccessResponse(result);
        } catch (Exception e) {
            log.error("调用AI服务失败,modelId: {}, content: {}", linkId, content, e);
            return buildErrorResponse(CODE_ERROR, "AI服务调用失败");
        }
    }
    private AiResponse buildErrorResponse(String code, String description) {
        AiResponse response = new AiResponse();
        response.setCode(code);
        response.setDescription(description);
        return response;
    }
    private AiResponse buildSuccessResponse(String result) {
        AiResponse response = new AiResponse();
        response.setCode(CODE_SUCCESS);
        response.setDescription("成功");
        response.setResContext(result);
        return response;
    }
}
src/main/java/cc/mrbird/febs/mall/controller/HSController.java
@@ -18,7 +18,7 @@
    // 从环境变量中获取您的Key鉴权。此为默认方式,您可根据需要进行修改
    private static String ak = "AKLTZTQxZjMyZTUxMWJmNDEyNDkzNWExOGQ3ODllNzhhNmQ";
    private static String sk = "TmpFeE1qZ3haREExTW1JeE5HRTBZVGc1WlRRNVlqWXpORGd5TWpsak5HWQ==";
    private static String ep_id = "ep-20250728114932-429wg";
    private static String ep_id = "ep-20250805124033-lhxbf";
    // 此为默认路径,您可根据业务所在地域进行配置
    static String baseUrl = "https://ark.cn-beijing.volces.com/api/v3";
    static ConnectionPool connectionPool = new ConnectionPool(5, 1, TimeUnit.SECONDS);
src/main/resources/application-test.yml
@@ -89,4 +89,10 @@
    path: /mnt/sdc/webresource/file/
system:
  job: false
  job: false
ai:
  service:
    ak: AKLTZTQxZjMyZTUxMWJmNDEyNDkzNWExOGQ3ODllNzhhNmQ
    sk: TmpFeE1qZ3haREExTW1JeE5HRTBZVGc1WlRRNVlqWXpORGd5TWpsak5HWQ==
    base-url: https://ark.cn-beijing.volces.com/api/v3