From e82b5c2f0b9fdc1fd8803695c1acbaa334ce19cc Mon Sep 17 00:00:00 2001 From: JiaoTianBo Date: Thu, 26 Mar 2026 16:50:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(common):=20=E6=96=B0=E5=A2=9E=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E6=A8=A1=E5=9D=97=E5=8F=8AAI=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增基础响应类BaseResponse,实现统一返回格式 - 定义自定义异常BusinessException及全局异常处理机制 - 构建错误码枚举ErrorCode,包含常见HTTP及业务错误码 - 实现返回工具类ResultUtils提供成功与失败响应构建方法 - 添加分页查询支持PageQuery和TableDataInfo数据结构 - 配置MinIO客户端及服务类MinioFileService实现文件上传、下载、删除功能 - 实现AI项目初始化控制器ProjectInitController,支持文本、文件及URL输入生成项目结构化数据 - 定义项目初始化相关DTO,包括请求参数和复杂的结果结构体 - 补充项目开发环境配置文件,集成Spring AI、PostgreSQL和MinIO配置 - 项目主启动类YlhpAiProjectManagerApplication添加启动入口 - 更新Maven依赖,集成Spring AI、MyBatis Plus、MinIO及AWS S3客户端依赖 - 规范代码包结构,新增多模块package-info.java文件进行模块说明文档编写 --- pom.xml | 9 +- .../YlhpAiProjectManagerApplication.java | 2 +- .../common/base/BaseResponse.java | 35 +++ .../common/base/package-info.java | 0 .../common/config/MinioConfig.java | 44 ++++ .../common/config/SpringAiConfig.java | 23 ++ .../common/config/package-info.java | 0 .../common/constant/package-info.java | 0 .../cn.yinlihupo/common/enums/ErrorCode.java | 44 ++++ .../common/exception/BusinessException.java | 28 +++ .../exception/GlobalExceptionHandler.java | 94 ++++++++ .../common/exception/package-info.java | 0 .../cn.yinlihupo/common/page/PageQuery.java | 81 +++++++ .../common/page/TableDataInfo.java | 76 ++++++ .../cn.yinlihupo/common/result/Result.java | 89 +++++++ .../common/result/package-info.java | 0 .../cn.yinlihupo/common/util/ResultUtils.java | 54 +++++ .../common/util/package-info.java | 0 .../controller/ProjectInitController.java | 140 +++++++++++ .../domain/dto/ProjectInitRequest.java | 25 ++ .../domain/dto/ProjectInitResult.java | 222 ++++++++++++++++++ .../service/ai/MinioFileService.java | 71 ++++++ .../service/ai/ProjectInitService.java | 27 +++ .../service/ai/impl/MinioFileServiceImpl.java | 217 +++++++++++++++++ .../ai/impl/ProjectInitServiceImpl.java | 174 ++++++++++++++ src/main/resources/application-dev.yaml | 11 +- 26 files changed, 1463 insertions(+), 3 deletions(-) rename src/main/java/{cn/yinlihupo/ylhpaiprojectmanager => cn.yinlihupo}/YlhpAiProjectManagerApplication.java (88%) create mode 100644 src/main/java/cn.yinlihupo/common/base/BaseResponse.java rename src/main/java/{cn/yinlihupo/ylhpaiprojectmanager => cn.yinlihupo}/common/base/package-info.java (100%) create mode 100644 src/main/java/cn.yinlihupo/common/config/MinioConfig.java create mode 100644 src/main/java/cn.yinlihupo/common/config/SpringAiConfig.java rename src/main/java/{cn/yinlihupo/ylhpaiprojectmanager => cn.yinlihupo}/common/config/package-info.java (100%) rename src/main/java/{cn/yinlihupo/ylhpaiprojectmanager => cn.yinlihupo}/common/constant/package-info.java (100%) create mode 100644 src/main/java/cn.yinlihupo/common/enums/ErrorCode.java create mode 100644 src/main/java/cn.yinlihupo/common/exception/BusinessException.java create mode 100644 src/main/java/cn.yinlihupo/common/exception/GlobalExceptionHandler.java rename src/main/java/{cn/yinlihupo/ylhpaiprojectmanager => cn.yinlihupo}/common/exception/package-info.java (100%) create mode 100644 src/main/java/cn.yinlihupo/common/page/PageQuery.java create mode 100644 src/main/java/cn.yinlihupo/common/page/TableDataInfo.java create mode 100644 src/main/java/cn.yinlihupo/common/result/Result.java rename src/main/java/{cn/yinlihupo/ylhpaiprojectmanager => cn.yinlihupo}/common/result/package-info.java (100%) create mode 100644 src/main/java/cn.yinlihupo/common/util/ResultUtils.java rename src/main/java/{cn/yinlihupo/ylhpaiprojectmanager => cn.yinlihupo}/common/util/package-info.java (100%) create mode 100644 src/main/java/cn.yinlihupo/controller/ProjectInitController.java create mode 100644 src/main/java/cn.yinlihupo/domain/dto/ProjectInitRequest.java create mode 100644 src/main/java/cn.yinlihupo/domain/dto/ProjectInitResult.java create mode 100644 src/main/java/cn.yinlihupo/service/ai/MinioFileService.java create mode 100644 src/main/java/cn.yinlihupo/service/ai/ProjectInitService.java create mode 100644 src/main/java/cn.yinlihupo/service/ai/impl/MinioFileServiceImpl.java create mode 100644 src/main/java/cn.yinlihupo/service/ai/impl/ProjectInitServiceImpl.java diff --git a/pom.xml b/pom.xml index cfe1695..d4ca694 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,13 @@ spring-boot-starter-web + + + org.projectlombok + lombok + ${lombok.version} + + org.postgresql @@ -86,7 +93,7 @@ org.springframework.ai spring-ai-bom - 1.0.0 + ${spring-ai.version} pom import diff --git a/src/main/java/cn/yinlihupo/ylhpaiprojectmanager/YlhpAiProjectManagerApplication.java b/src/main/java/cn.yinlihupo/YlhpAiProjectManagerApplication.java similarity index 88% rename from src/main/java/cn/yinlihupo/ylhpaiprojectmanager/YlhpAiProjectManagerApplication.java rename to src/main/java/cn.yinlihupo/YlhpAiProjectManagerApplication.java index 72c7066..fb2f928 100644 --- a/src/main/java/cn/yinlihupo/ylhpaiprojectmanager/YlhpAiProjectManagerApplication.java +++ b/src/main/java/cn.yinlihupo/YlhpAiProjectManagerApplication.java @@ -1,4 +1,4 @@ -package cn.yinlihupo.ylhpaiprojectmanager; +package cn.yinlihupo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/cn.yinlihupo/common/base/BaseResponse.java b/src/main/java/cn.yinlihupo/common/base/BaseResponse.java new file mode 100644 index 0000000..77cea7f --- /dev/null +++ b/src/main/java/cn.yinlihupo/common/base/BaseResponse.java @@ -0,0 +1,35 @@ +package cn.yinlihupo.common.base; + +import cn.yinlihupo.common.enums.ErrorCode; +import lombok.Data; + +import java.io.Serializable; + +/** + * 通用返回类 + * + * @param + */ +@Data +public class BaseResponse implements Serializable { + + private int code; + + private T data; + + private String message; + + public BaseResponse(int code, T data, String message) { + this.code = code; + this.data = data; + this.message = message; + } + + public BaseResponse(int code, T data) { + this(code, data, ""); + } + + public BaseResponse(ErrorCode errorCode) { + this(errorCode.getCode(), null, errorCode.getMessage()); + } +} diff --git a/src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/base/package-info.java b/src/main/java/cn.yinlihupo/common/base/package-info.java similarity index 100% rename from src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/base/package-info.java rename to src/main/java/cn.yinlihupo/common/base/package-info.java diff --git a/src/main/java/cn.yinlihupo/common/config/MinioConfig.java b/src/main/java/cn.yinlihupo/common/config/MinioConfig.java new file mode 100644 index 0000000..c2b23a2 --- /dev/null +++ b/src/main/java/cn.yinlihupo/common/config/MinioConfig.java @@ -0,0 +1,44 @@ +package cn.yinlihupo.common.config; + +import io.minio.MinioClient; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * MinIO 配置类 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "minio") +public class MinioConfig { + + /** + * MinIO 服务端点 + */ + private String endpoint; + + /** + * 访问密钥 + */ + private String accessKey; + + /** + * 秘密密钥 + */ + private String secretKey; + + /** + * 默认存储桶名称 + */ + private String bucketName; + + @Bean + public MinioClient minioClient() { + return MinioClient.builder() + .endpoint(endpoint) + .credentials(accessKey, secretKey) + .build(); + } +} diff --git a/src/main/java/cn.yinlihupo/common/config/SpringAiConfig.java b/src/main/java/cn.yinlihupo/common/config/SpringAiConfig.java new file mode 100644 index 0000000..460bf7f --- /dev/null +++ b/src/main/java/cn.yinlihupo/common/config/SpringAiConfig.java @@ -0,0 +1,23 @@ +package cn.yinlihupo.common.config; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Spring AI 配置类 + */ +@Configuration +public class SpringAiConfig { + + /** + * 配置ChatClient + * + * @param builder ChatClient.Builder + * @return ChatClient + */ + @Bean + public ChatClient chatClient(ChatClient.Builder builder) { + return builder.build(); + } +} diff --git a/src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/config/package-info.java b/src/main/java/cn.yinlihupo/common/config/package-info.java similarity index 100% rename from src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/config/package-info.java rename to src/main/java/cn.yinlihupo/common/config/package-info.java diff --git a/src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/constant/package-info.java b/src/main/java/cn.yinlihupo/common/constant/package-info.java similarity index 100% rename from src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/constant/package-info.java rename to src/main/java/cn.yinlihupo/common/constant/package-info.java diff --git a/src/main/java/cn.yinlihupo/common/enums/ErrorCode.java b/src/main/java/cn.yinlihupo/common/enums/ErrorCode.java new file mode 100644 index 0000000..bdd0756 --- /dev/null +++ b/src/main/java/cn.yinlihupo/common/enums/ErrorCode.java @@ -0,0 +1,44 @@ +package cn.yinlihupo.common.enums; + +/** + * 自定义错误码 + * + */ +public enum ErrorCode { + + SUCCESS(200, "ok"), + PARAMS_ERROR(400, "请求参数错误"), + NOT_LOGIN_ERROR(401, "未登录"), + NO_AUTH_ERROR(402, "无权限"), + NOT_FOUND_ERROR(404, "请求数据不存在"), + FORBIDDEN_ERROR(403, "禁止访问"), + SYSTEM_ERROR(500, "系统内部异常"), + TOO_MANY_REQUEST(429, "请求过于频繁"), + TOKEN_INVALID(401, "token已失效"), + OPERATION_ERROR(500, "操作失败"); + + + /** + * 状态码 + */ + private final int code; + + /** + * 信息 + */ + private final String message; + + ErrorCode(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/cn.yinlihupo/common/exception/BusinessException.java b/src/main/java/cn.yinlihupo/common/exception/BusinessException.java new file mode 100644 index 0000000..47bd669 --- /dev/null +++ b/src/main/java/cn.yinlihupo/common/exception/BusinessException.java @@ -0,0 +1,28 @@ +package cn.yinlihupo.common.exception; + +import cn.yinlihupo.common.enums.ErrorCode; + +/** + * 自定义异常类 + */ +public class BusinessException extends RuntimeException { + + /** + * 错误码 + */ + private final int code; + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + } + + public BusinessException(ErrorCode errorCode, String message) { + super(message); + this.code = errorCode.getCode(); + } + + public int getCode() { + return code; + } +} diff --git a/src/main/java/cn.yinlihupo/common/exception/GlobalExceptionHandler.java b/src/main/java/cn.yinlihupo/common/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..01c5758 --- /dev/null +++ b/src/main/java/cn.yinlihupo/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,94 @@ +package cn.yinlihupo.common.exception; +import cn.yinlihupo.common.base.BaseResponse; +import cn.yinlihupo.common.enums.ErrorCode; +import cn.yinlihupo.common.util.ResultUtils; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.stream.Collectors; + +/** + * 全局异常处理器 + * + */ +@RestControllerAdvice +@Slf4j +@Order(Ordered.LOWEST_PRECEDENCE) +public class GlobalExceptionHandler { + + @ExceptionHandler(BusinessException.class) + public BaseResponse businessExceptionHandler(BusinessException e) { + log.error("BusinessException", e); + return ResultUtils.error(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(OrderException.class) + public BaseResponse orderExceptionHandler(OrderException e) { + log.error("OrderException", e); + // 如果没有设置错误码,使用默认的业务错误码 + Integer code = e.getCode(); + if (code == null) { + return ResultUtils.error(ErrorCode.OPERATION_ERROR, e.getMessage()); + } + return ResultUtils.error(code, e.getMessage()); + } + + @ExceptionHandler(ServiceException.class) + public BaseResponse serviceExceptionHandler(ServiceException e) { + log.error("ServiceException", e); + // 如果没有设置错误码,使用默认的业务错误码 + Integer code = e.getCode(); + if (code == null) { + return ResultUtils.error(ErrorCode.OPERATION_ERROR, e.getMessage()); + } + return ResultUtils.error(code, e.getMessage()); + } + + @ExceptionHandler(UtilException.class) + public BaseResponse utilExceptionHandler(UtilException e) { + log.error("UtilException", e); + return ResultUtils.error(ErrorCode.SYSTEM_ERROR, e.getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public BaseResponse methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { + log.error("MethodArgumentNotValidException", e); + String errorMsg = e.getBindingResult().getFieldErrors().stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining("; ")); + return ResultUtils.error(ErrorCode.PARAMS_ERROR, errorMsg); + } + + @ExceptionHandler(BindException.class) + public BaseResponse bindExceptionHandler(BindException e) { + log.error("BindException", e); + String errorMsg = e.getBindingResult().getFieldErrors().stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining("; ")); + return ResultUtils.error(ErrorCode.PARAMS_ERROR, errorMsg); + } + + @ExceptionHandler(ConstraintViolationException.class) + public BaseResponse constraintViolationExceptionHandler(ConstraintViolationException e) { + log.error("ConstraintViolationException", e); + String errorMsg = e.getConstraintViolations().stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.joining("; ")); + return ResultUtils.error(ErrorCode.PARAMS_ERROR, errorMsg); + } + + @ExceptionHandler(RuntimeException.class) + public BaseResponse runtimeExceptionHandler(RuntimeException e) { + log.error("RuntimeException", e); + return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误"); + } + +} diff --git a/src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/exception/package-info.java b/src/main/java/cn.yinlihupo/common/exception/package-info.java similarity index 100% rename from src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/exception/package-info.java rename to src/main/java/cn.yinlihupo/common/exception/package-info.java diff --git a/src/main/java/cn.yinlihupo/common/page/PageQuery.java b/src/main/java/cn.yinlihupo/common/page/PageQuery.java new file mode 100644 index 0000000..b00d77f --- /dev/null +++ b/src/main/java/cn.yinlihupo/common/page/PageQuery.java @@ -0,0 +1,81 @@ +package cn.yinlihupo.common.page; + +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 分页查询实体类 + */ +@Data +public class PageQuery implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 分页大小 + */ + private Integer pageSize; + + /** + * 当前页数 + */ + private Integer pageNum; + + /** + * 排序列 + */ + private String orderByColumn; + + /** + * 排序的方向desc或者asc + */ + private String isAsc; + + /** + * 当前记录起始索引 默认值 + */ + public static final int DEFAULT_PAGE_NUM = 1; + + /** + * 每页显示记录数 默认值 默认查全部 + */ + public static final int DEFAULT_PAGE_SIZE = 10; + + public Page build() { + Integer pageNum = this.pageNum == null ? DEFAULT_PAGE_NUM : this.pageNum; + Integer pageSize = this.pageSize == null ? DEFAULT_PAGE_SIZE : this.pageSize; + if (pageNum <= 0) { + pageNum = DEFAULT_PAGE_NUM; + } + return new Page<>(pageNum, pageSize); + } + + /** + * 构建排序 + */ + public List buildOrderItem() { + if (orderByColumn == null || orderByColumn.isEmpty() || isAsc == null || isAsc.isEmpty()) { + return null; + } + + List list = new ArrayList<>(); + String[] orderByArr = orderByColumn.split(","); + String[] isAscArr = isAsc.split(","); + + for (int i = 0; i < orderByArr.length; i++) { + String orderByStr = orderByArr[i].trim(); + String isAscStr = isAscArr.length == 1 ? isAscArr[0].trim() : isAscArr[i].trim(); + if ("asc".equalsIgnoreCase(isAscStr)) { + list.add(OrderItem.asc(orderByStr)); + } else if ("desc".equalsIgnoreCase(isAscStr)) { + list.add(OrderItem.desc(orderByStr)); + } + } + return list; + } +} diff --git a/src/main/java/cn.yinlihupo/common/page/TableDataInfo.java b/src/main/java/cn.yinlihupo/common/page/TableDataInfo.java new file mode 100644 index 0000000..e76299a --- /dev/null +++ b/src/main/java/cn.yinlihupo/common/page/TableDataInfo.java @@ -0,0 +1,76 @@ +package cn.yinlihupo.common.page; + +import cn.hutool.http.HttpStatus; +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + */ + +@Data +@NoArgsConstructor +public class TableDataInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 总记录数 + */ + private long total; + + /** + * 列表数据 + */ + private List rows; + + /** + * 消息状态码 + */ + private int code; + + /** + * 消息内容 + */ + private String msg; + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataInfo(List list, long total) { + this.rows = list; + this.total = total; + } + + public static TableDataInfo build(IPage page) { + TableDataInfo rspData = new TableDataInfo<>(); + rspData.setCode(HttpStatus.HTTP_OK); + rspData.setMsg("查询成功"); + rspData.setRows(page.getRecords()); + rspData.setTotal(page.getTotal()); + return rspData; + } + + public static TableDataInfo build(List list) { + TableDataInfo rspData = new TableDataInfo<>(); + rspData.setCode(HttpStatus.HTTP_OK); + rspData.setMsg("查询成功"); + rspData.setRows(list); + rspData.setTotal(list.size()); + return rspData; + } + + public static TableDataInfo build() { + TableDataInfo rspData = new TableDataInfo<>(); + rspData.setCode(HttpStatus.HTTP_OK); + rspData.setMsg("查询成功"); + return rspData; + } + +} diff --git a/src/main/java/cn.yinlihupo/common/result/Result.java b/src/main/java/cn.yinlihupo/common/result/Result.java new file mode 100644 index 0000000..fffc844 --- /dev/null +++ b/src/main/java/cn.yinlihupo/common/result/Result.java @@ -0,0 +1,89 @@ +package cn.yinlihupo.common.result; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 统一响应结果封装 + * + * @param 数据类型 + */ +@Data +public class Result implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 状态码 + */ + private Integer code; + + /** + * 响应消息 + */ + private String message; + + /** + * 响应数据 + */ + private T data; + + /** + * 时间戳 + */ + private Long timestamp; + + public Result() { + this.timestamp = System.currentTimeMillis(); + } + + public Result(Integer code, String message, T data) { + this.code = code; + this.message = message; + this.data = data; + this.timestamp = System.currentTimeMillis(); + } + + /** + * 成功响应 + */ + public static Result success() { + return new Result<>(200, "操作成功", null); + } + + /** + * 成功响应(带数据) + */ + public static Result success(T data) { + return new Result<>(200, "操作成功", data); + } + + /** + * 成功响应(带消息和数据) + */ + public static Result success(String message, T data) { + return new Result<>(200, message, data); + } + + /** + * 失败响应 + */ + public static Result error() { + return new Result<>(500, "操作失败", null); + } + + /** + * 失败响应(带消息) + */ + public static Result error(String message) { + return new Result<>(500, message, null); + } + + /** + * 失败响应(带状态码和消息) + */ + public static Result error(Integer code, String message) { + return new Result<>(code, message, null); + } +} diff --git a/src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/result/package-info.java b/src/main/java/cn.yinlihupo/common/result/package-info.java similarity index 100% rename from src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/result/package-info.java rename to src/main/java/cn.yinlihupo/common/result/package-info.java diff --git a/src/main/java/cn.yinlihupo/common/util/ResultUtils.java b/src/main/java/cn.yinlihupo/common/util/ResultUtils.java new file mode 100644 index 0000000..c8b6edc --- /dev/null +++ b/src/main/java/cn.yinlihupo/common/util/ResultUtils.java @@ -0,0 +1,54 @@ +package cn.yinlihupo.common.util; + + +import cn.yinlihupo.common.base.BaseResponse; +import cn.yinlihupo.common.enums.ErrorCode; + +/** + * 返回工具类 + * + */ +public class ResultUtils { + + /** + * 成功 + * + * @param data + * @param + * @return + */ + public static BaseResponse success(T data) { + return new BaseResponse<>(200, data, "ok"); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static BaseResponse error(ErrorCode errorCode) { + return new BaseResponse<>(errorCode); + } + + /** + * 失败 + * + * @param code + * @param message + * @return + */ + public static BaseResponse error(int code, String message) { + return new BaseResponse(code, null, message); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static BaseResponse error(ErrorCode errorCode, String message) { + return new BaseResponse(errorCode.getCode(), null, message); + } +} diff --git a/src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/util/package-info.java b/src/main/java/cn.yinlihupo/common/util/package-info.java similarity index 100% rename from src/main/java/cn/yinlihupo/ylhpaiprojectmanager/common/util/package-info.java rename to src/main/java/cn.yinlihupo/common/util/package-info.java diff --git a/src/main/java/cn.yinlihupo/controller/ProjectInitController.java b/src/main/java/cn.yinlihupo/controller/ProjectInitController.java new file mode 100644 index 0000000..a1581ef --- /dev/null +++ b/src/main/java/cn.yinlihupo/controller/ProjectInitController.java @@ -0,0 +1,140 @@ +package cn.yinlihupo.controller; + +import cn.yinlihupo.common.result.Result; +import cn.yinlihupo.domain.dto.ProjectInitRequest; +import cn.yinlihupo.domain.dto.ProjectInitResult; +import cn.yinlihupo.service.ai.MinioFileService; +import cn.yinlihupo.service.ai.ProjectInitService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +/** + * AI项目初始化控制器 + * 提供项目文档解析和结构化数据生成功能 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/project-init") +@RequiredArgsConstructor +public class ProjectInitController { + + private final ProjectInitService projectInitService; + private final MinioFileService minioFileService; + + /** + * 根据文本内容生成项目初始化数据 + * + * @param request 包含项目资料内容的请求 + * @return 项目初始化结构化数据 + */ + @PostMapping("/from-content") + public Result generateFromContent(@RequestBody ProjectInitRequest request) { + log.info("收到项目初始化请求(文本内容)"); + + if (request.getContent() == null || request.getContent().trim().isEmpty()) { + return Result.error("项目资料内容不能为空"); + } + + try { + ProjectInitResult result = projectInitService.generateProjectFromContent(request.getContent()); + return Result.success("项目初始化成功", result); + } catch (Exception e) { + log.error("项目初始化失败: {}", e.getMessage(), e); + return Result.error("项目初始化失败: " + e.getMessage()); + } + } + + /** + * 上传文件并生成项目初始化数据 + * + * @param file 项目资料文件 + * @return 项目初始化结构化数据 + */ + @PostMapping("/from-file") + public Result generateFromFile(@RequestParam("file") MultipartFile file) { + log.info("收到项目初始化请求(文件上传), 文件名: {}", file.getOriginalFilename()); + + if (file.isEmpty()) { + return Result.error("上传文件不能为空"); + } + + try { + // 上传文件到MinIO + String fileUrl = minioFileService.uploadFile(file, file.getOriginalFilename()); + log.info("文件上传成功,URL: {}", fileUrl); + + // 根据文件生成项目初始化数据 + String fileType = getFileExtension(file.getOriginalFilename()); + ProjectInitResult result = projectInitService.generateProjectFromFile(fileUrl, fileType); + + return Result.success("项目初始化成功", result); + } catch (Exception e) { + log.error("项目初始化失败: {}", e.getMessage(), e); + return Result.error("项目初始化失败: " + e.getMessage()); + } + } + + /** + * 根据已上传的文件URL生成项目初始化数据 + * + * @param request 包含文件URL的请求 + * @return 项目初始化结构化数据 + */ + @PostMapping("/from-url") + public Result generateFromUrl(@RequestBody ProjectInitRequest request) { + log.info("收到项目初始化请求(文件URL)"); + + if (request.getFileUrl() == null || request.getFileUrl().trim().isEmpty()) { + return Result.error("文件URL不能为空"); + } + + try { + ProjectInitResult result = projectInitService.generateProjectFromFile( + request.getFileUrl(), + request.getFileType() + ); + return Result.success("项目初始化成功", result); + } catch (Exception e) { + log.error("项目初始化失败: {}", e.getMessage(), e); + return Result.error("项目初始化失败: " + e.getMessage()); + } + } + + /** + * 仅上传文件到MinIO,返回文件URL + * + * @param file 文件 + * @return 文件URL + */ + @PostMapping("/upload") + public Result uploadFile(@RequestParam("file") MultipartFile file) { + log.info("收到文件上传请求, 文件名: {}", file.getOriginalFilename()); + + if (file.isEmpty()) { + return Result.error("上传文件不能为空"); + } + + try { + String fileUrl = minioFileService.uploadFile(file, file.getOriginalFilename()); + return Result.success("文件上传成功", fileUrl); + } catch (Exception e) { + log.error("文件上传失败: {}", e.getMessage(), e); + return Result.error("文件上传失败: " + e.getMessage()); + } + } + + /** + * 获取文件扩展名 + * + * @param fileName 文件名 + * @return 文件扩展名 + */ + private String getFileExtension(String fileName) { + if (fileName == null || fileName.lastIndexOf('.') == -1) { + return ""; + } + return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase(); + } +} diff --git a/src/main/java/cn.yinlihupo/domain/dto/ProjectInitRequest.java b/src/main/java/cn.yinlihupo/domain/dto/ProjectInitRequest.java new file mode 100644 index 0000000..3b31975 --- /dev/null +++ b/src/main/java/cn.yinlihupo/domain/dto/ProjectInitRequest.java @@ -0,0 +1,25 @@ +package cn.yinlihupo.domain.dto; + +import lombok.Data; + +/** + * 项目初始化请求DTO + */ +@Data +public class ProjectInitRequest { + + /** + * 项目资料文本内容(直接输入) + */ + private String content; + + /** + * MinIO文件URL(已上传的文件) + */ + private String fileUrl; + + /** + * 文件类型:text, pdf, word等 + */ + private String fileType; +} diff --git a/src/main/java/cn.yinlihupo/domain/dto/ProjectInitResult.java b/src/main/java/cn.yinlihupo/domain/dto/ProjectInitResult.java new file mode 100644 index 0000000..bdf4419 --- /dev/null +++ b/src/main/java/cn.yinlihupo/domain/dto/ProjectInitResult.java @@ -0,0 +1,222 @@ +package cn.yinlihupo.domain.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; + +/** + * AI项目初始化结果DTO + * 对应系统提示词中定义的JSON输出格式 + */ +@Data +public class ProjectInitResult { + + /** + * 项目基本信息 + */ + @JsonProperty("project") + private ProjectInfo project; + + /** + * 里程碑列表 + */ + @JsonProperty("milestones") + private List milestones; + + /** + * 任务清单 + */ + @JsonProperty("tasks") + private List tasks; + + /** + * 项目成员 + */ + @JsonProperty("members") + private List members; + + /** + * 资源需求 + */ + @JsonProperty("resources") + private List resources; + + /** + * 风险识别 + */ + @JsonProperty("risks") + private List risks; + + /** + * 时间节点 + */ + @JsonProperty("timeline_nodes") + private List timelineNodes; + + // ==================== 内部类定义 ==================== + + @Data + public static class ProjectInfo { + @JsonProperty("project_name") + private String projectName; + + @JsonProperty("project_type") + private String projectType; + + @JsonProperty("description") + private String description; + + @JsonProperty("objectives") + private String objectives; + + @JsonProperty("plan_start_date") + private LocalDate planStartDate; + + @JsonProperty("plan_end_date") + private LocalDate planEndDate; + + @JsonProperty("budget") + private BigDecimal budget; + + @JsonProperty("currency") + private String currency; + + @JsonProperty("priority") + private String priority; + + @JsonProperty("tags") + private List tags; + } + + @Data + public static class MilestoneInfo { + @JsonProperty("milestone_name") + private String milestoneName; + + @JsonProperty("description") + private String description; + + @JsonProperty("plan_date") + private LocalDate planDate; + + @JsonProperty("deliverables") + private String deliverables; + + @JsonProperty("owner_role") + private String ownerRole; + } + + @Data + public static class TaskInfo { + @JsonProperty("task_name") + private String taskName; + + @JsonProperty("parent_task_id") + private String parentTaskId; + + @JsonProperty("description") + private String description; + + @JsonProperty("plan_start_date") + private LocalDate planStartDate; + + @JsonProperty("plan_end_date") + private LocalDate planEndDate; + + @JsonProperty("estimated_hours") + private Integer estimatedHours; + + @JsonProperty("priority") + private String priority; + + @JsonProperty("assignee_role") + private String assigneeRole; + + @JsonProperty("dependencies") + private List dependencies; + + @JsonProperty("deliverables") + private String deliverables; + } + + @Data + public static class MemberInfo { + @JsonProperty("name") + private String name; + + @JsonProperty("role_code") + private String roleCode; + + @JsonProperty("responsibility") + private String responsibility; + + @JsonProperty("department") + private String department; + + @JsonProperty("weekly_hours") + private Integer weeklyHours; + } + + @Data + public static class ResourceInfo { + @JsonProperty("resource_name") + private String resourceName; + + @JsonProperty("resource_type") + private String resourceType; + + @JsonProperty("quantity") + private BigDecimal quantity; + + @JsonProperty("unit") + private String unit; + + @JsonProperty("unit_price") + private BigDecimal unitPrice; + + @JsonProperty("supplier") + private String supplier; + } + + @Data + public static class RiskInfo { + @JsonProperty("risk_name") + private String riskName; + + @JsonProperty("category") + private String category; + + @JsonProperty("description") + private String description; + + @JsonProperty("probability") + private Integer probability; + + @JsonProperty("impact") + private Integer impact; + + @JsonProperty("mitigation_plan") + private String mitigationPlan; + } + + @Data + public static class TimelineNodeInfo { + @JsonProperty("node_name") + private String nodeName; + + @JsonProperty("node_type") + private String nodeType; + + @JsonProperty("plan_date") + private LocalDate planDate; + + @JsonProperty("description") + private String description; + + @JsonProperty("kb_scope") + private List kbScope; + } +} diff --git a/src/main/java/cn.yinlihupo/service/ai/MinioFileService.java b/src/main/java/cn.yinlihupo/service/ai/MinioFileService.java new file mode 100644 index 0000000..0184b2c --- /dev/null +++ b/src/main/java/cn.yinlihupo/service/ai/MinioFileService.java @@ -0,0 +1,71 @@ +package cn.yinlihupo.service.ai; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; + +/** + * MinIO文件服务接口 + * 用于文件上传、下载和管理 + */ +public interface MinioFileService { + + /** + * 上传文件到MinIO + * + * @param file 文件 + * @param fileName 文件名 + * @return 文件访问URL + */ + String uploadFile(MultipartFile file, String fileName); + + /** + * 上传文件到MinIO(指定存储桶) + * + * @param file 文件 + * @param fileName 文件名 + * @param bucketName 存储桶名称 + * @return 文件访问URL + */ + String uploadFile(MultipartFile file, String fileName, String bucketName); + + /** + * 从URL读取文件内容为字符串 + * + * @param fileUrl 文件URL + * @return 文件内容 + */ + String readFileAsString(String fileUrl); + + /** + * 从URL获取文件输入流 + * + * @param fileUrl 文件URL + * @return 文件输入流 + */ + InputStream getFileInputStream(String fileUrl); + + /** + * 删除文件 + * + * @param fileUrl 文件URL + */ + void deleteFile(String fileUrl); + + /** + * 获取文件URL + * + * @param objectName 对象名称 + * @return 文件URL + */ + String getFileUrl(String objectName); + + /** + * 获取文件URL(指定存储桶) + * + * @param bucketName 存储桶名称 + * @param objectName 对象名称 + * @return 文件URL + */ + String getFileUrl(String bucketName, String objectName); +} diff --git a/src/main/java/cn.yinlihupo/service/ai/ProjectInitService.java b/src/main/java/cn.yinlihupo/service/ai/ProjectInitService.java new file mode 100644 index 0000000..0a21665 --- /dev/null +++ b/src/main/java/cn.yinlihupo/service/ai/ProjectInitService.java @@ -0,0 +1,27 @@ +package cn.yinlihupo.service.ai; + +import cn.yinlihupo.domain.dto.ProjectInitResult; + +/** + * AI项目初始化服务接口 + * 使用Spring AI结构化输出能力,从项目文档中提取结构化信息 + */ +public interface ProjectInitService { + + /** + * 根据项目资料内容生成项目初始化结构化数据 + * + * @param content 项目资料文本内容 + * @return 项目初始化结果,包含项目信息、里程碑、任务、成员、资源、风险等 + */ + ProjectInitResult generateProjectFromContent(String content); + + /** + * 根据MinIO文件URL生成项目初始化结构化数据 + * + * @param fileUrl MinIO文件URL + * @param fileType 文件类型 + * @return 项目初始化结果 + */ + ProjectInitResult generateProjectFromFile(String fileUrl, String fileType); +} diff --git a/src/main/java/cn.yinlihupo/service/ai/impl/MinioFileServiceImpl.java b/src/main/java/cn.yinlihupo/service/ai/impl/MinioFileServiceImpl.java new file mode 100644 index 0000000..1aec83d --- /dev/null +++ b/src/main/java/cn.yinlihupo/service/ai/impl/MinioFileServiceImpl.java @@ -0,0 +1,217 @@ +package cn.yinlihupo.service.ai.impl; + +import cn.yinlihupo.common.config.MinioConfig; +import cn.yinlihupo.service.ai.MinioFileService; +import io.minio.*; +import io.minio.errors.*; +import io.minio.http.Method; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * MinIO文件服务实现类 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MinioFileServiceImpl implements MinioFileService { + + private final MinioClient minioClient; + private final MinioConfig minioConfig; + + @Override + public String uploadFile(MultipartFile file, String fileName) { + return uploadFile(file, fileName, minioConfig.getBucketName()); + } + + @Override + public String uploadFile(MultipartFile file, String fileName, String bucketName) { + try { + // 确保存储桶存在 + ensureBucketExists(bucketName); + + // 生成唯一的对象名称 + String objectName = generateObjectName(fileName); + + // 上传文件 + minioClient.putObject( + PutObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .stream(file.getInputStream(), file.getSize(), -1) + .contentType(file.getContentType()) + .build() + ); + + log.info("文件上传成功: {}/{}", bucketName, objectName); + + // 返回文件URL + return getFileUrl(bucketName, objectName); + } catch (Exception e) { + log.error("文件上传失败: {}", e.getMessage(), e); + throw new RuntimeException("文件上传失败: " + e.getMessage(), e); + } + } + + @Override + public String readFileAsString(String fileUrl) { + try (InputStream inputStream = getFileInputStream(fileUrl); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + + return outputStream.toString(StandardCharsets.UTF_8); + } catch (Exception e) { + log.error("读取文件内容失败: {}", e.getMessage(), e); + throw new RuntimeException("读取文件内容失败: " + e.getMessage(), e); + } + } + + @Override + public InputStream getFileInputStream(String fileUrl) { + try { + // 解析URL获取bucket和object名称 + URL url = new URL(fileUrl); + String path = url.getPath(); + // 去掉开头的/ + if (path.startsWith("/")) { + path = path.substring(1); + } + + // 解析bucket和object + int slashIndex = path.indexOf('/'); + String bucketName; + String objectName; + + if (slashIndex > 0) { + bucketName = path.substring(0, slashIndex); + objectName = path.substring(slashIndex + 1); + } else { + bucketName = minioConfig.getBucketName(); + objectName = path; + } + + return minioClient.getObject( + GetObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .build() + ); + } catch (Exception e) { + log.error("获取文件输入流失败: {}", e.getMessage(), e); + throw new RuntimeException("获取文件输入流失败: " + e.getMessage(), e); + } + } + + @Override + public void deleteFile(String fileUrl) { + try { + // 解析URL获取bucket和object名称 + URL url = new URL(fileUrl); + String path = url.getPath(); + if (path.startsWith("/")) { + path = path.substring(1); + } + + int slashIndex = path.indexOf('/'); + String bucketName; + String objectName; + + if (slashIndex > 0) { + bucketName = path.substring(0, slashIndex); + objectName = path.substring(slashIndex + 1); + } else { + bucketName = minioConfig.getBucketName(); + objectName = path; + } + + minioClient.removeObject( + RemoveObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .build() + ); + + log.info("文件删除成功: {}/{}", bucketName, objectName); + } catch (Exception e) { + log.error("删除文件失败: {}", e.getMessage(), e); + throw new RuntimeException("删除文件失败: " + e.getMessage(), e); + } + } + + @Override + public String getFileUrl(String objectName) { + return getFileUrl(minioConfig.getBucketName(), objectName); + } + + @Override + public String getFileUrl(String bucketName, String objectName) { + try { + // 构建文件URL + String endpoint = minioConfig.getEndpoint(); + if (endpoint.endsWith("/")) { + endpoint = endpoint.substring(0, endpoint.length() - 1); + } + return endpoint + "/" + bucketName + "/" + objectName; + } catch (Exception e) { + log.error("获取文件URL失败: {}", e.getMessage(), e); + throw new RuntimeException("获取文件URL失败: " + e.getMessage(), e); + } + } + + /** + * 确保存储桶存在 + * + * @param bucketName 存储桶名称 + */ + private void ensureBucketExists(String bucketName) throws Exception { + boolean exists = minioClient.bucketExists( + BucketExistsArgs.builder() + .bucket(bucketName) + .build() + ); + + if (!exists) { + minioClient.makeBucket( + MakeBucketArgs.builder() + .bucket(bucketName) + .build() + ); + log.info("创建存储桶成功: {}", bucketName); + } + } + + /** + * 生成对象名称 + * + * @param originalFileName 原始文件名 + * @return 对象名称 + */ + private String generateObjectName(String originalFileName) { + String uuid = UUID.randomUUID().toString().replace("-", ""); + String timestamp = String.valueOf(System.currentTimeMillis()); + + // 提取文件扩展名 + String extension = ""; + int dotIndex = originalFileName.lastIndexOf('.'); + if (dotIndex > 0) { + extension = originalFileName.substring(dotIndex); + } + + return timestamp + "_" + uuid + extension; + } +} diff --git a/src/main/java/cn.yinlihupo/service/ai/impl/ProjectInitServiceImpl.java b/src/main/java/cn.yinlihupo/service/ai/impl/ProjectInitServiceImpl.java new file mode 100644 index 0000000..954f2a3 --- /dev/null +++ b/src/main/java/cn.yinlihupo/service/ai/impl/ProjectInitServiceImpl.java @@ -0,0 +1,174 @@ +package cn.yinlihupo.service.ai.impl; + +import cn.yinlihupo.domain.dto.ProjectInitResult; +import cn.yinlihupo.service.ai.ProjectInitService; +import cn.yinlihupo.service.ai.MinioFileService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; + +/** + * AI项目初始化服务实现类 + * 使用Spring AI结构化输出能力解析项目文档 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ProjectInitServiceImpl implements ProjectInitService { + + private final ChatClient chatClient; + private final MinioFileService minioFileService; + + /** + * 项目初始化系统提示词模板 + */ + private static final String PROJECT_INIT_SYSTEM_PROMPT = """ + # 角色 + + 你是一个专业的项目管理助手,擅长从项目文档中提取结构化信息,自动生成项目计划。 + + # 任务 + + 根据用户提供的项目资料,解析并生成以下结构化数据: + + 1. 项目基本信息(名称、周期、预算、目标) + 2. 项目里程碑(关键节点) + 3. 任务清单(WBS工作分解结构) + 4. 项目成员及角色 + 5. 资源需求 + 6. 风险识别 + + # 输出格式 + + 请严格按照以下JSON格式输出: + + ```json + { + "project": { + "project_name": "项目名称", + "project_type": "工程项目/研发项目/运营项目", + "description": "项目描述", + "objectives": "项目目标", + "plan_start_date": "2024-01-01", + "plan_end_date": "2024-12-31", + "budget": 1000000, + "currency": "CNY", + "priority": "high/medium/low", + "tags": ["标签1", "标签2"] + }, + "milestones": [ + { + "milestone_name": "里程碑名称", + "description": "描述", + "plan_date": "2024-03-01", + "deliverables": "交付物", + "owner_role": "负责人角色" + } + ], + "tasks": [ + { + "task_name": "任务名称", + "parent_task_id": null, + "description": "任务描述", + "plan_start_date": "2024-01-15", + "plan_end_date": "2024-02-15", + "estimated_hours": 80, + "priority": "high", + "assignee_role": "执行者角色", + "dependencies": ["前置任务ID"], + "deliverables": "交付物" + } + ], + "members": [ + { + "name": "姓名", + "role_code": "manager/leader/member", + "responsibility": "职责描述", + "department": "部门", + "weekly_hours": 40 + } + ], + "resources": [ + { + "resource_name": "资源名称", + "resource_type": "human/material/equipment/software/finance", + "quantity": 1, + "unit": "单位", + "unit_price": 1000, + "supplier": "供应商" + } + ], + "risks": [ + { + "risk_name": "风险名称", + "category": "technical/schedule/cost/quality/resource/external", + "description": "风险描述", + "probability": 60, + "impact": 4, + "mitigation_plan": "缓解措施" + } + ], + "timeline_nodes": [ + { + "node_name": "时间节点名称", + "node_type": "milestone/phase/event", + "plan_date": "2024-06-01", + "description": "描述", + "kb_scope": ["report", "file", "risk", "ticket"] + } + ] + } + ``` + + # 注意事项 + + 1. 日期格式统一使用 YYYY-MM-DD + 2. 任务之间要建立合理的依赖关系 + 3. 里程碑应该是关键节点,不宜过多 + 4. 资源要考虑人力、物料、设备等 + 5. 风险识别要基于项目特点 + 6. 如果文档信息不完整,根据项目类型合理推断 + """; + + /** + * 用户提示词模板 + */ + private static final String USER_PROMPT_TEMPLATE = """ + 请根据以下项目资料,生成完整的项目初始化结构化数据: + + {content} + + 请严格按照系统提示词中的JSON格式输出,确保所有字段都包含合理的值。 + """; + + @Override + public ProjectInitResult generateProjectFromContent(String content) { + log.info("开始根据内容生成项目初始化数据"); + + PromptTemplate promptTemplate = new PromptTemplate(USER_PROMPT_TEMPLATE); + String userPrompt = promptTemplate.createMessage(java.util.Map.of("content", content)).getContent(); + + return chatClient.prompt() + .system(PROJECT_INIT_SYSTEM_PROMPT) + .user(userPrompt) + .call() + .entity(ProjectInitResult.class); + } + + @Override + public ProjectInitResult generateProjectFromFile(String fileUrl, String fileType) { + log.info("开始根据文件生成项目初始化数据, fileUrl: {}, fileType: {}", fileUrl, fileType); + + // 从MinIO下载文件内容 + String content = minioFileService.readFileAsString(fileUrl); + + if (content == null || content.isEmpty()) { + throw new RuntimeException("无法读取文件内容: " + fileUrl); + } + + return generateProjectFromContent(content); + } +} diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 5ae6858..4595934 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -30,6 +30,16 @@ spring: logic-delete-value: 1 logic-not-delete-value: 0 + # Spring AI 配置 + ai: + openai: + api-key: ${OPENAI_API_KEY:your-api-key} + base-url: ${OPENAI_BASE_URL:https://api.openai.com} + chat: + options: + model: ${OPENAI_MODEL:gpt-4o} + temperature: 0.3 + # MinIO 对象存储配置 minio: endpoint: ${MINIO_ENDPOINT:http://localhost:9000} @@ -37,7 +47,6 @@ minio: secret-key: ${MINIO_SECRET_KEY:minioadmin} bucket-name: ${MINIO_BUCKET_NAME:ylhp-files} - # 日志配置 logging: level: