Administrator
2025-08-25 33a883b0d7edbf633dcf142d2e3c5bf2e334b121
feat(ai): 添加 AI 灵通相关功能

- 新增 AiTalk 和 AiTalkItem 模型类
- 添加 AiTalkService 和 AiTalkItemService 接口及其实现类
- 新增 ApiAiTalkController 控制器类
- 添加相关请求和响应对象类
- 更新 AiTalkMapper 接口和 XML 文件,添加分页查询方法
5 files modified
9 files added
342 ■■■■■ changed files
src/main/java/cc/mrbird/febs/ai/controller/talk/ApiAiTalkController.java 68 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/entity/AiTalk.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/entity/AiTalkItem.java 2 ●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/enumerates/AiTypeEnum.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/mapper/AiTalkMapper.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/req/talk/ApiTalkDto.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/req/talk/ApiTalkPageDto.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/res/talk/ApiTalkPageVo.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/res/talk/ApiTalkVo.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/AiTalkItemService.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/AiTalkService.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/impl/AiTalkItemServiceImpl.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/impl/AiTalkServiceImpl.java 83 ●●●●● patch | view | raw | blame | history
src/main/resources/mapper/modules/AiTalkMapper.xml 12 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/controller/talk/ApiAiTalkController.java
New file
@@ -0,0 +1,68 @@
package cc.mrbird.febs.ai.controller.talk;
import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkAnswerDto;
import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkDto;
import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkItemPageDto;
import cc.mrbird.febs.ai.req.talk.ApiTalkDto;
import cc.mrbird.febs.ai.req.talk.ApiTalkPageDto;
import cc.mrbird.febs.ai.res.memberTalk.ApiMemberTalkItemVo;
import cc.mrbird.febs.ai.res.memberTalk.ApiMemberTalkStreamVo;
import cc.mrbird.febs.ai.res.memberTalk.ApiMemberTalkVo;
import cc.mrbird.febs.ai.res.talk.ApiTalkPageVo;
import cc.mrbird.febs.ai.res.talk.ApiTalkVo;
import cc.mrbird.febs.ai.service.AiMemberTalkService;
import cc.mrbird.febs.ai.service.AiTalkService;
import cc.mrbird.febs.common.entity.FebsResponse;
import io.swagger.annotations.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Flux;
import java.io.IOException;
/**
 * @author Administrator
 */
@Slf4j
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/api/ai/talk")
@Api(value = "ApiAiTalkController", tags = "AI-灵通")
public class ApiAiTalkController {
    private final AiTalkService aiTalkService;
    @ApiOperation(value = "用户AI问答", notes = "用户AI问答")
    @ApiResponses({
            @ApiResponse(code = 200, message = "success", response = ApiTalkVo.class)
    })
    @PostMapping(value = "/talkOpen")
    public FebsResponse talkOpen(@RequestBody @Validated ApiTalkDto dto) {
        return aiTalkService.talkOpen(dto);
    }
    @ApiOperation(value = "用户AI问答记录分页查询", notes = "用户AI问答记录分页查询")
    @ApiResponses({
            @ApiResponse(code = 200, message = "success", response = ApiTalkPageVo.class)
    })
    @PostMapping(value = "/talkList")
    public FebsResponse talkList(@RequestBody @Validated ApiTalkPageDto dto) {
        return aiTalkService.talkList(dto);
    }
    @ApiOperation("提问AI(流式)")
    @ApiResponses({
            @ApiResponse(code = 200, message = "流式响应", response = ApiMemberTalkStreamVo.class),
    })
    @GetMapping("/answer-stream")
    public Flux<FebsResponse> answerStream(@RequestParam String question) {
        return aiTalkService.answerStream(question);
    }
}
src/main/java/cc/mrbird/febs/ai/entity/AiTalk.java
@@ -19,6 +19,11 @@
    private String companyId;
    /**
     * 主要问题
     */
    private String question;
    /**
     * 用户ID (UUID)
     */
    private String memberId;
src/main/java/cc/mrbird/febs/ai/entity/AiTalkItem.java
@@ -25,7 +25,7 @@
    /**
     * 用户对话ID (UUID)
     */
    private String memberTalkId;
    private String talkId;
    /**
     * 类型 1-用户提问 2-AI回答
src/main/java/cc/mrbird/febs/ai/enumerates/AiTypeEnum.java
@@ -10,6 +10,13 @@
public enum AiTypeEnum {
    /**
     * 1:用户提问
     * 2:AI回答
     */
    MEMBER_QUESTION(1,"用户提问"),
    AI_ANSWER(2,"AI回答"),
    /**
     * 1:AI提问
     * 2:用户回答
     * 3:生成答案解析
src/main/java/cc/mrbird/febs/ai/mapper/AiTalkMapper.java
@@ -1,7 +1,14 @@
package cc.mrbird.febs.ai.mapper;
import cc.mrbird.febs.ai.entity.AiTalk;
import cc.mrbird.febs.ai.req.talk.ApiTalkPageDto;
import cc.mrbird.febs.ai.res.talk.ApiTalkPageVo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
public interface AiTalkMapper extends BaseMapper<AiTalk> {
    Page<ApiTalkPageVo> getPageTalkListByQuery(Page<ApiTalkPageVo> page, @Param("record")ApiTalkPageDto dto);
}
src/main/java/cc/mrbird/febs/ai/req/talk/ApiTalkDto.java
New file
@@ -0,0 +1,27 @@
package cc.mrbird.febs.ai.req.talk;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
 * @author Administrator
 */
@Data
@ApiModel(value = "ApiTalkDto", description = "参数")
public class ApiTalkDto {
    @NotBlank(message = "内容不能为空")
    @ApiModelProperty(value = "内容", example = "10")
    private String context;
    @NotNull(message = "类型不能为空")
    @ApiModelProperty(value = "类型 1-用户提问 2-AI回答", example = "10")
    private Integer type;
    @ApiModelProperty(value = "会话ID", example = "10")
    private String talkId;
}
src/main/java/cc/mrbird/febs/ai/req/talk/ApiTalkPageDto.java
New file
@@ -0,0 +1,26 @@
package cc.mrbird.febs.ai.req.talk;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
 * @author Administrator
 */
@Data
@ApiModel(value = "ApiTalkPageDto", description = "参数")
public class ApiTalkPageDto {
    @NotNull(message = "页码不能为空")
    @ApiModelProperty(value = "页码", example = "1")
    private Integer pageNow;
    @NotNull(message = "每页数量不能为空")
    @ApiModelProperty(value = "每页数量", example = "10")
    private Integer pageSize;
    @ApiModelProperty(hidden = true)
    private String memberUuid;
}
src/main/java/cc/mrbird/febs/ai/res/talk/ApiTalkPageVo.java
New file
@@ -0,0 +1,26 @@
package cc.mrbird.febs.ai.res.talk;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
 * @author Administrator
 */
@Data
@ApiModel(value = "ApiTalkPageVo", description = "参数")
public class ApiTalkPageVo {
    @ApiModelProperty(value = "会话ID")
    private String talkId;
    @ApiModelProperty(value = "题目")
    private String question;
    @ApiModelProperty(value = "时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createdTime;
}
src/main/java/cc/mrbird/febs/ai/res/talk/ApiTalkVo.java
New file
@@ -0,0 +1,19 @@
package cc.mrbird.febs.ai.res.talk;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * @author Administrator
 */
@Data
@ApiModel(value = "ApiTalkVo", description = "参数")
public class ApiTalkVo {
    /**
     * 视频号 id,以"sph"开头的id,可在视频号助手获取
     */
    @ApiModelProperty(value = "会话ID")
    private String talkId;
}
src/main/java/cc/mrbird/febs/ai/service/AiTalkItemService.java
New file
@@ -0,0 +1,11 @@
package cc.mrbird.febs.ai.service;
import cc.mrbird.febs.ai.entity.AiTalkItem;
import cn.hutool.core.date.DateTime;
import com.baomidou.mybatisplus.extension.service.IService;
public interface AiTalkItemService extends IService<AiTalkItem> {
    void add(String id, int code, String context, String memberUuid, DateTime date);
}
src/main/java/cc/mrbird/febs/ai/service/AiTalkService.java
New file
@@ -0,0 +1,21 @@
package cc.mrbird.febs.ai.service;
import cc.mrbird.febs.ai.entity.AiTalk;
import cc.mrbird.febs.ai.req.talk.ApiTalkDto;
import cc.mrbird.febs.ai.req.talk.ApiTalkPageDto;
import cc.mrbird.febs.common.entity.FebsResponse;
import com.baomidou.mybatisplus.extension.service.IService;
import reactor.core.publisher.Flux;
import java.util.Date;
public interface AiTalkService extends IService<AiTalk> {
    FebsResponse talkOpen(ApiTalkDto dto);
    AiTalk add(String memberUuid, String question, Date date);
    Flux<FebsResponse> answerStream(String question);
    FebsResponse talkList(ApiTalkPageDto dto);
}
src/main/java/cc/mrbird/febs/ai/service/impl/AiTalkItemServiceImpl.java
New file
@@ -0,0 +1,28 @@
package cc.mrbird.febs.ai.service.impl;
import cc.mrbird.febs.ai.entity.AiTalkItem;
import cc.mrbird.febs.ai.mapper.AiTalkItemMapper;
import cc.mrbird.febs.ai.service.AiTalkItemService;
import cc.mrbird.febs.ai.utils.UUID;
import cn.hutool.core.date.DateTime;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class AiTalkItemServiceImpl extends ServiceImpl<AiTalkItemMapper, AiTalkItem> implements AiTalkItemService {
    @Override
    public void add(String id, int code, String context, String memberUuid, DateTime date) {
        AiTalkItem aiTalkItem = new AiTalkItem();
        aiTalkItem.setId(UUID.getSimpleUUIDString());
        aiTalkItem.setMemberId(memberUuid);
        aiTalkItem.setType(code);
        aiTalkItem.setTalkId(id);
        aiTalkItem.setCreatedTime(date);
        aiTalkItem.setContext(context);
        this.baseMapper.insert(aiTalkItem);
    }
}
src/main/java/cc/mrbird/febs/ai/service/impl/AiTalkServiceImpl.java
New file
@@ -0,0 +1,83 @@
package cc.mrbird.febs.ai.service.impl;
import cc.mrbird.febs.ai.entity.AiTalk;
import cc.mrbird.febs.ai.mapper.AiTalkMapper;
import cc.mrbird.febs.ai.req.talk.ApiTalkDto;
import cc.mrbird.febs.ai.req.talk.ApiTalkPageDto;
import cc.mrbird.febs.ai.res.memberAnswer.ApiMemberProductWorkVo;
import cc.mrbird.febs.ai.res.talk.ApiTalkPageVo;
import cc.mrbird.febs.ai.res.talk.ApiTalkVo;
import cc.mrbird.febs.ai.service.AiService;
import cc.mrbird.febs.ai.service.AiTalkItemService;
import cc.mrbird.febs.ai.service.AiTalkService;
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.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import java.util.Date;
@Slf4j
@Service
@RequiredArgsConstructor
public class AiTalkServiceImpl extends ServiceImpl<AiTalkMapper, AiTalk> implements AiTalkService {
    private final AiTalkItemService aiTalkItemService;
    private final AiService aiService;
    @Override
    public FebsResponse talkOpen(ApiTalkDto dto) {
        String talkId = dto.getTalkId();
        String context = dto.getContext();
        Integer type = dto.getType();
        String memberUuid = LoginUserUtil.getLoginUser().getMemberUuid();
        AiTalk aiTalk = this.getById(talkId);
        DateTime date = DateUtil.date();
        if (StrUtil.isEmpty(talkId)){
            aiTalk = this.add(memberUuid,context, date);
        }
        aiTalkItemService.add(aiTalk.getId(), type, context, memberUuid, date);
        ApiTalkVo apiTalkVo = new ApiTalkVo();
        apiTalkVo.setTalkId(aiTalk.getId());
        return new FebsResponse().success().data(apiTalkVo);
    }
    @Override
    public AiTalk add(String memberUuid, String question, Date date) {
        AiTalk aiTalk = new AiTalk();
        aiTalk.setId(UUID.getSimpleUUIDString());
        aiTalk.setQuestion(question);
        aiTalk.setCreatedTime(date);
        aiTalk.setMemberId(memberUuid);
        this.baseMapper.insert(aiTalk);
        return aiTalk;
    }
    @Override
    public Flux<FebsResponse> answerStream(String question) {
        return aiService.answerStream(question);
    }
    @Override
    public FebsResponse talkList(ApiTalkPageDto dto) {
        String memberUuid = LoginUserUtil.getLoginUser().getMemberUuid();
        dto.setMemberUuid(memberUuid);
        // 创建分页对象,传入当前页和每页大小
        Page<ApiTalkPageVo> page = new Page<>(dto.getPageNow(), dto.getPageSize());
        Page<ApiTalkPageVo> pageListByQuery = this.getBaseMapper().getPageTalkListByQuery(page, dto);
        return new FebsResponse().success().data(pageListByQuery);
    }
}
src/main/resources/mapper/modules/AiTalkMapper.xml
@@ -1,4 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cc.mrbird.febs.ai.mapper.AiTalkMapper">
    <select id="getPageTalkListByQuery" resultType="cc.mrbird.febs.ai.res.talk.ApiTalkPageVo">
        select
        a.id as talkId,
        a.question as question,
        a.CREATED_TIME as createdTime
        from ai_talk a
        where a.member_id = #{record.memberUuid}
        order by a.CREATED_TIME desc
    </select>
</mapper>