day2

#实习

学习git的基本操作:

  1. git clone url
  2. cd 文件夹
  3. git fetch origin dev 拉取分支 git pull
  4. git switch dev 切换分支
  5. 提交并推送

新增接口:案件查询删除、报告查询删除、目录查询

CaseController.Java

package com.fj.report.controller;


import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fj.report.entity.SysCaseFile;
import com.fj.report.entity.SysCaseInfo;
import com.fj.report.service.SysCaseFileService;
import com.fj.report.service.SysCaseInfoService;
import com.fj.report.vo.MaterialGroupVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/case-select")
@Tag(name = "案件管理", description = "案件相关操作API")
public class CaseController {

    @Autowired
    private SysCaseInfoService sysCaseInfoService;
    @Autowired
    private SysCaseFileService sysCaseFileService;

    /**
     * 分页查询案件列表
     */
    @GetMapping("/list")
    @Operation(summary = "分页查询案件列表")
    public ResponseEntity<IPage<SysCaseInfo>> listCases(
            @Parameter(description = "当前页") @RequestParam(defaultValue = "1") Long pageNum,
            @Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long pageSize,
            @Parameter(description = "案件名称") @RequestParam(required = false) String caseName) {

        Page<SysCaseInfo> page = new Page<>(pageNum, pageSize);
        IPage<SysCaseInfo> result = sysCaseInfoService.lambdaQuery()
                .like(caseName != null, SysCaseInfo::getCaseName, caseName)
                .eq(SysCaseInfo::getStatus, 1) // 查询有效案件
                .orderByDesc(SysCaseInfo::getCreateTime)
                .page(page);

        return ResponseEntity.ok(result);
    }

    /**
     * 新增案件
     */
    @PostMapping
    @Operation(summary = "新增案件")
    public ResponseEntity<String> addCase(
            @RequestBody SysCaseInfo caseInfo) {

        // 设置初始值
        caseInfo.setStatus(1); // 有效
        caseInfo.setProcessStatus(0); // 待处理
        caseInfo.setCreateTime(java.time.LocalDateTime.now());

        boolean success = sysCaseInfoService.save(caseInfo);
        return success ? ResponseEntity.ok("案件添加成功")
                : ResponseEntity.status(500).body("案件添加失败");
    }

    /**
     * 删除案件(逻辑删除)
     */
    @DeleteMapping("/{caseId}")
    @Operation(summary = "删除案件")
    public ResponseEntity<String> deleteCase(
            @Parameter(description = "案件ID") @PathVariable Long caseId) {

        boolean success = sysCaseInfoService.removeById(caseId);
        return success ? ResponseEntity.ok("案件删除成功")
                : ResponseEntity.status(500).body("案件删除失败");
    }

    /**
     * 获取案件下的材料分组列表
     * 对应页面:原始目录、材料目录等分类
     */
    @GetMapping("/{caseId}/materials")
    @Operation(summary = "获取案件材料分组列表")
    public ResponseEntity<List<MaterialGroupVO>> getMaterialGroups(
            @Parameter(description = "案件ID") @PathVariable Long caseId) {

        List<MaterialGroupVO> groups = sysCaseFileService.getMaterialGroupsByCaseId(caseId);
        return ResponseEntity.ok(groups);
    }

    /**
     * 搜索案件内的文件
     */
    @GetMapping("/{caseId}/files/search")
    @Operation(summary = "搜索案件内的文件")
    public ResponseEntity<?> searchFiles(
            @Parameter(description = "案件ID") @PathVariable Long caseId,
            @Parameter(description = "搜索关键词") @RequestParam String keyword) {

        var files = sysCaseFileService.searchFiles(caseId, keyword);
        return ResponseEntity.ok(files);
    }

    /**
     * 获取特定材料类型下的所有文件
     */
    @GetMapping("/{caseId}/materials/{materialTypeId}/files")
    @Operation(summary = "获取指定材料类型的文件列表")
    public ResponseEntity<?> getFilesByMaterial(
            @Parameter(description = "案件ID") @PathVariable Long caseId,
            @Parameter(description = "材料类型ID") @PathVariable Long materialTypeId) {

        var files = sysCaseFileService.getFilesByMaterialType(caseId, materialTypeId);
        return ResponseEntity.ok(files);
    }
}

SysCaseFileService.Java

package com.fj.report.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.fj.report.entity.SysCaseFile;
import com.fj.report.vo.MaterialGroupVO;
import java.util.List;

/**
 * 案件文件服务接口
 */
public interface SysCaseFileService extends IService<SysCaseFile> {

    /**
     * 根据案件ID查询分组后的材料及文件
     * @param caseId 案件ID
     * @return 按材料类型分组的文件列表
     */
    List<MaterialGroupVO> getMaterialGroupsByCaseId(Long caseId);

    /**
     * 根据案件ID和材料类型ID查询文件列表
     * @param caseId 案件ID
     * @param materialTypeId 材料类型ID
     * @return 文件列表
     */
    List<SysCaseFile> getFilesByMaterialType(Long caseId, Long materialTypeId);

    /**
     * 搜索文件(按文件名)
     * @param caseId 案件ID
     * @param keyword 关键词
     * @return 匹配的文件列表
     */
    List<SysCaseFile> searchFiles(Long caseId, String keyword);
}

SysCaseFileServiceImpl.Java

package com.fj.report.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fj.report.entity.SysCaseCatalog;
import com.fj.report.entity.SysCaseFile;
import com.fj.report.entity.SysMaterials;
import com.fj.report.mapper.SysCaseFileMapper;
import com.fj.report.mapper.SysMaterialsMapper;
import com.fj.report.service.SysCaseFileService;
import com.fj.report.vo.CaseFileVO;
import com.fj.report.vo.MaterialGroupVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 案件文件服务实现类
 *
 * @author fj
 */
@Service
public class SysCaseFileServiceImpl extends ServiceImpl<SysCaseFileMapper, SysCaseFile> implements SysCaseFileService {
    @Autowired
    private SysMaterialsMapper sysMaterialsMapper;

    /**
     * 根据案件ID查询分组后的材料及文件
     *
     * @param caseId 案件ID
     * @return 按材料类型分组的文件列表
     */
    @Override
    public List<MaterialGroupVO> getMaterialGroupsByCaseId(Long caseId) {
        // 1. 查询该案件下的所有文件
        List<SysCaseFile> caseFiles = this.lambdaQuery()
                .eq(SysCaseFile::getCaseId, caseId)
                .orderByAsc(SysCaseFile::getMaterialTypeId)
                .list();

        if(caseFiles.isEmpty()){
            return new ArrayList<>();
        }
        // 2. 根据文件的材料类型进行分组
        Map<Long, List<SysCaseFile>> groupedFiles = caseFiles.stream()
                .collect(Collectors.groupingBy(SysCaseFile::getMaterialTypeId));
        // 3. 构建 MaterialGroupVO 列表返回
        List<MaterialGroupVO> result = new ArrayList<>();

        for(Map.Entry<Long, List<SysCaseFile>> entry : groupedFiles.entrySet()){
            Long materialTypeId = entry.getKey();
            List<SysCaseFile> files = entry.getValue();

            // 获取材料类型信息
            SysMaterials material = sysMaterialsMapper.selectById(materialTypeId);

            MaterialGroupVO group = new MaterialGroupVO();
            group.setMaterialTypeId(materialTypeId);
            group.setMaterialTypeName(material != null ? material.getMaterialTypeName() : "未知类型");
            group.setFileCount(files.size());

            // 转换为VO
            List<CaseFileVO> fileVOs = files.stream()
                    .map(file -> {
                        CaseFileVO vo = new CaseFileVO();
                        vo.setCaseFileId(file.getCaseFileId());
                        vo.setFileName(file.getFileName());
                        vo.setFileId(file.getFileId());
                        vo.setCreateTime(file.getCreateTime());
                        vo.setCreateBy(file.getCreateBy());
                        return vo;
                    })
                    .collect(Collectors.toList());

            group.setFiles(fileVOs);
            result.add(group);
        }
        return result;
    }

    @Override
    public List<SysCaseFile> getFilesByMaterialType(Long caseId, Long materialTypeId) {
        return this.lambdaQuery()
                .eq(SysCaseFile::getCaseId, caseId)
                .eq(SysCaseFile::getMaterialTypeId, materialTypeId)
                .list();
    }

    @Override
    public List<SysCaseFile> searchFiles(Long caseId, String keyword) {
        return this.lambdaQuery()
                .eq(SysCaseFile::getCaseId, caseId)
                .like(SysCaseFile::getFileName, keyword)
                .list();
    }
}

CaseFileVO.Java

package com.fj.report.vo;

import lombok.Data;
import java.time.LocalDateTime;

/**
 * 案件文件VO
 */
@Data
public class CaseFileVO {
    /**
     * 案件文件ID
     */
    private Long caseFileId;

    /**
     * 文件名
     */
    private String fileName;

    /**
     * 文件ID
     */
    private Long fileId;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 创建人
     */
    private Long createBy;
}

材料视图类:

package com.fj.report.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;

/**
 * 材料类型及其文件分组VO
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MaterialGroupVO {
    /**
     * 材料类型ID
     */
    private Long materialTypeId;

    /**
     * 材料类型名称
     */
    private String materialTypeName;

    /**
     * 该类型下的文件列表
     */
    private List<CaseFileVO> files;

    /**
     * 文件数量
     */
    private Integer fileCount;
}

报告:

ReportController.Java

package com.fj.report.controller;


import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fj.report.entity.SysReportInfo;
import com.fj.report.service.SysReportInfoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/doc-manage")
@Tag(name = "报告管理", description = "报告相关操作API")
public class ReportController {
    @Autowired
    private SysReportInfoService sysReportInfoService;

    /**
     * 分页查询报告列表
     */
    @GetMapping("/list")
    @Operation(summary = "分页查询报告列表")
    public ResponseEntity<IPage<SysReportInfo>> listReports(
            @Parameter(description = "当前页") @RequestParam(defaultValue = "1") Long pageNum,
            @Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long pageSize,
            @Parameter(description = "报告名称") @RequestParam(required = false) String reportName
    ){
        Page<SysReportInfo> page = new Page<>(pageNum, pageSize);
        IPage<SysReportInfo> result = sysReportInfoService.lambdaQuery()
                .like(reportName != null, SysReportInfo::getReportName, reportName)
                .eq(SysReportInfo::getStatus, 1) // 查询有效报告
                .orderByDesc(SysReportInfo::getCreateTime)
                .page(page);

        return ResponseEntity.ok(result);
    }

    /**
     * 删除报告
     */
    @DeleteMapping("/{reportId}")
    @Operation(summary = "删除报告")
    public ResponseEntity<String> deleteReport(
            @Parameter(description = "报告ID") @PathVariable Long reportId
    ){
        boolean success = sysReportInfoService.removeById(reportId);
        return success ? ResponseEntity.ok("报告删除成功")
                : ResponseEntity.status(500).body("报告删除失败");
    }
}

新增调用SpringAI实现应答:

添加配置:

spring:
  # 数据源配置
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://39.103.56.106:15432/fj_report_test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: Bzfurniture@2025
    hikari:
      minimum-idle: 5
      maximum-pool-size: 30
      connection-timeout: 5000
      idle-timeout: 600000
      max-lifetime: 1200000
      leak-detection-threshold: 10000

  # Redis配置
  data:
    redis:
      host: 39.103.56.106
      port: 16379
      password: Ht5#bLk8$nMq4xZ
      database: 0
      timeout: 3000ms
      lettuce:
        pool:
          max-active: 8
          max-idle: 8
          min-idle: 0
          max-wait: -1ms

  # Kafka配置
  kafka:
    bootstrap-servers: 39.103.56.106:19092
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      acks: all
      retries: 3
    consumer:
      group-id: fj-report-server-dev
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      auto-offset-reset: earliest
      enable-auto-commit: true

  # Spring AI Alibaba配置
  ai:
    # 阿里云DashScope配置
    alibaba:
      dashscope:
        api-key: "sk-your-aliyun-api-key"  # 阿里云API Key
        chat:
          options:
            model: "qwen-max"              # 这里不能用DeepSeek-V3,要用阿里云的模型
            temperature: 0.1

    # OpenAI兼容配置(用于第三方兼容接口)
    openai:
      api-key: "sk-HmCbA0klDAA9po6o210b9a033cF0420eAf2585B9B87d6740"      # 第三方API Key
      base-url: "https://one-api.maas.com.cn"  # 注意:去掉 /chat/completions
      chat:
        options:
          model: "deepseek-chat"           # 模型名称
          temperature: 0.1
          max-tokens: 2000

# SpringDoc OpenAPI (Swagger) 配置 - 开发环境
springdoc:
  swagger-ui:
    enabled: true
  api-docs:
    enabled: true

aliyun:
  # 阿里云OSS配置
  cloud:
    access-key: LTAI5t61NaouEwRGAYFaSnJH
    secret-key: sG2D7OXI0sJrnE5HqguNwzT2hBWCpZ
    oss:
      endpoint: oss-cn-beijing.aliyuncs.com
      bucket-name: server-zs
      # 可选配置
      region: cn-beijing
      # 多租户模式下可为每个租户配置目录前缀
      tenant-path-prefix: tenant/
      # 访问图片URL时对图片进行固定宽度处理为200px
      url-style: ${OSS_URL_STYLE:?x-oss-process=image/resize,w_200}
      # 角色ARN,用于权限管理 办法sts凭证
#      arn: acs:ram::1254456248189381:role/aliyunossrole


# 日志级别
logging:
  level:
    root: INFO
    com.fj.report: DEBUG
    org.springframework.jdbc.core: DEBUG
    org.apache.ibatis: DEBUG
    com.baomidou.mybatisplus: DEBUG

添加依赖:

<?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 
         http://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.4.1</version>
        <relativePath/>
    </parent>

    <groupId>com.fj.report</groupId>
    <artifactId>fj-report-server</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    <name>fj-report-server</name>
    <description>Spring Boot服务用于报告生成和管理</description>

    <properties>
        <java.version>21</java.version>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <!-- 依赖版本管理 -->
        <mybatis-plus.version>3.5.7</mybatis-plus.version>
        <postgresql.version>42.7.3</postgresql.version>
        <spring-ai.version>1.0.0-M4</spring-ai.version>
        <lombok.version>1.18.30</lombok.version>
        <poi.version>5.2.4</poi.version>
        <itext.version>7.2.5</itext.version>
        <aliyun.oss.version>3.18.3</aliyun.oss.version>
        <aliyun.oss.sts.version>3.1.0</aliyun.oss.sts.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Spring AI BOM -->
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot Validation -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- MyBatis Plus (Spring Boot 3.x 专用) -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- PostgreSQL Driver -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>${postgresql.version}</version>
        </dependency>

        <!-- Spring Boot Data Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- Spring Kafka -->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>

        <!-- Spring AI OpenAI Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>

        <!-- Spring Boot Configuration Processor -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- SpringDoc OpenAPI (Swagger) -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.8.1</version>
        </dependency>

        <!-- Spring Boot Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 阿里云OSS -->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>${aliyun.oss.version}</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-sts</artifactId>
            <version>${aliyun.oss.sts.version}</version>
        </dependency>

        <!-- Excel处理 -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>${poi.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>${poi.version}</version>
        </dependency>

        <!-- PDF处理 -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>kernel</artifactId>
            <version>${itext.version}</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>layout</artifactId>
            <version>${itext.version}</version>
        </dependency>

    </dependencies>

    <build>
        <finalName>fj-report-server</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <!-- 阿里云仓库 -->
        <repository>
            <id>alimaven</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>

        <!-- Spring AI Alibaba 快照仓库 -->
        <repository>
            <id>sonatype-snapshots</id>
            <name>Sonatype Snapshots</name>
            <url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>

        <!-- Spring里程碑仓库 -->
        <repository>
            <id>spring-milestone</id>
            <name>Spring Milestone Repository</name>
            <url>https://repo.spring.io/milestone</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
        
        <!-- Maven 中央仓库 -->
        <repository>
            <id>central</id>
            <name>Maven Central</name>
            <url>https://repo1.maven.org/maven2/</url>
        </repository>
    </repositories>
</project>

创建AI配置类 AiConfig.Java:

package com.fj.report.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * AI配置类 - 使用 Spring AI OpenAI
 * Spring Boot 自动配置会根据 application-dev.yml 的配置创建 ChatModel Bean
 * 
 * @author fj
 */
@Slf4j
@Configuration
public class AiConfig {

    /**
     * 创建 ChatClient Bean
     * ChatModel 由 Spring AI OpenAI 自动配置提供
     */
    @Bean
    public ChatClient chatClient(ChatModel chatModel) {
        log.info("初始化 ChatClient");
        log.info("ChatModel 实现类: {}", chatModel.getClass().getName());
        
        return ChatClient.builder(chatModel)
                .defaultSystem("你是一个专业的文档分析和内容生成助手。" +
                        "能够根据提供的材料生成专业、准确、结构清晰的报告。" +
                        "请确保内容完整、逻辑严密、表达准确。")
                .build();
    }
}

创建Controller:

package com.fj.report.controller;

import com.fj.report.dto.MaterialsPromptsRequest;
import com.fj.report.service.AiService;
import com.fj.report.vo.ArticleVos;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 材料 + 提示词生成结果控制器
 *
 * @author fj
 */
@Slf4j
@RestController
@RequestMapping("materials-prompts")
@Tag(name = "材料 + 提示词生成结果", description = "材料 + 提示词生成结果相关操作API")
public class MaterialsPromptsController {

    @Autowired
    private AiService aiService;

    /**
     * 材料 + 提示词生成结果(流式方式)
     */
    @Operation(summary = "流式生成分析报告", description = "根据材料和提示词流式生成分析报告")
    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> generateOutputStream(@RequestBody MaterialsPromptsRequest request) {
        log.info("接收到流式材料生成请求,材料数: {}, 提示词: {}",
                request.getArticles().size(), request.getSystemPrompt());


        return aiService.generateFromArticlesStream(request.getArticles(), request.getSystemPrompt());
    }
}

创建AIservice:

package com.fj.report.service;


import com.fj.report.vo.ArticleVos;
import reactor.core.publisher.Flux;

import java.util.List;

/**
 * AI 服务接口
 */
public interface AiService {

    /**
     * 调用大模型生成文本(同步)
     */
    String generateText(String prompt);

    /**
     * 调用大模型(带系统提示词)
     */
    String generateWithSystemPrompt(String systemPrompt, String userPrompt);

    /**
     * 流式响应
     */
    Flux<String> generateStream(String prompt);

    /**
     * 流式响应(带系统提示词)
     */
    Flux<String> generateStreamWithSystemPrompt(String systemPrompt, String userPrompt);

    /**
     * 从材料列表生成内容(同步)
     */
    Flux<String> generateFromArticlesStream(List<ArticleVos> articles, String systemPrompt);
}

实现接口:

package com.fj.report.service.impl;

import com.fj.report.service.AiService;
import com.fj.report.vo.ArticleVos;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.stream.Collectors;


@Slf4j
@Service
public class AiServiceImpl implements AiService
{
    private final ChatClient chatClient;

    @Autowired
    public AiServiceImpl(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @Override
    public String generateText(String prompt) {
        log.info("接收到的提示词:{}", prompt);
        return chatClient.prompt()
                .user(prompt)
                .call()
                .content();
    }

    @Override
    public String generateWithSystemPrompt(String systemPrompt, String userPrompt) {
        return chatClient.prompt()
                .system(systemPrompt)
                .user(userPrompt)
                .call()
                .content();
    }

    @Override
    public Flux<String> generateStream(String prompt) {
        return chatClient.prompt()
                .user(prompt)
                .stream()
                .content()
                .doOnError(e -> log.error("流式响应出错:", e))
                .doOnComplete(() -> log.info("流式响应完成"));
    }

    @Override
    public Flux<String> generateStreamWithSystemPrompt(String systemPrompt, String userPrompt) {
        return chatClient.prompt()
                .system(systemPrompt)
                .user(userPrompt)
                .stream()
                .content()
                .doOnError(e -> log.error("流式响应出错:", e))
                .doOnComplete(() -> log.info("流式响应完成"));
    }

    @Override
    public Flux<String> generateFromArticlesStream(List<ArticleVos> articles, String systemPrompt) {
        // 拼接材料内容
        String combinedContent = articles.stream()
                .map(article ->"文件名:" + article.getFileName() + "\n内容:" + article.getContent())
                .collect(Collectors.joining("\n\n"));
        log.info("拼接后的内容为:{}", combinedContent);
        return generateStreamWithSystemPrompt(systemPrompt, combinedContent)
                .doOnError(e -> log.error("流式从文章内容生成文本失败: {}", e.getMessage(), e));}
}

实现视图类:

package com.fj.report.vo;

import lombok.Data;

@Data
public class ArticleVos {

    // 文件名
    String fileName;

    // 解析后的文件内容
    String content;
}
← 返回首页