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: