diff --git a/Dockerfile b/Dockerfile index 562e7fee..177a9e5f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,11 @@ FROM eclipse-temurin:17-jdk-jammy WORKDIR /app COPY --from=build /app/app/target/tiny-engine-app-*.jar /app/tiny-engine-app.jar COPY --from=build /app/base/target/tiny-engine-base-*.jar /app/tiny-engine-base.jar - +# 设置环境变量 +ENV ACCESS_KEY_ID=" " +ENV ACCESS_KEY_SECRET = " " +ENV INDEX_ID = " " +ENV WORK_SPACE_ID = "" ENTRYPOINT ["java", "-jar", "tiny-engine-app.jar", "--spring.profiles.active=alpha"] EXPOSE 9090 diff --git a/base/src/main/java/com/tinyengine/it/controller/AiChatController.java b/base/src/main/java/com/tinyengine/it/controller/AiChatController.java index 69a066fb..b806f6f9 100644 --- a/base/src/main/java/com/tinyengine/it/controller/AiChatController.java +++ b/base/src/main/java/com/tinyengine/it/controller/AiChatController.java @@ -14,9 +14,8 @@ import com.tinyengine.it.common.base.Result; import com.tinyengine.it.common.log.SystemControllerLog; -import com.tinyengine.it.model.dto.AiParam; import com.tinyengine.it.model.dto.ChatRequest; -import com.tinyengine.it.service.app.AiChatService; +import com.tinyengine.it.model.dto.NodeDto; import com.tinyengine.it.service.app.v1.AiChatV1Service; import io.swagger.v3.oas.annotations.Operation; @@ -37,7 +36,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; -import java.util.Map; +import java.util.List; /** * The type Ai chat controller. @@ -49,12 +48,6 @@ @RequestMapping("/app-center/api") @Tag(name = "AIChat") public class AiChatController { - /** - * The Ai chat service. - */ - @Autowired - private AiChatService aiChatService; - /** * The Ai chat v1 service. */ @@ -64,18 +57,34 @@ public class AiChatController { /** * AI api * - * @param aiParam the AI param + * @param request the AI param * @return ai回答信息 result */ - @Operation(summary = "获取ai回答信息", description = "获取ai回答信息", parameters = { - @Parameter(name = "AiParam", description = "入参对象")}, responses = { + @Operation(summary = "获取ai回答信息", description = "获取ai回答信息", + parameters = { + @Parameter(name = "ChatRequest", description = "入参对象") + }, responses = { @ApiResponse(responseCode = "200", description = "返回信息", - content = @Content(mediaType = "application/json", schema = @Schema())), - @ApiResponse(responseCode = "400", description = "请求失败")}) + content = @Content(mediaType = "application/json", schema = @Schema())), + @ApiResponse(responseCode = "400", description = "请求失败") + }) @SystemControllerLog(description = "AI api") @PostMapping("/ai/chat") - public Result> aiChat(@RequestBody AiParam aiParam) { - return aiChatService.getAnswerFromAi(aiParam); + public ResponseEntity aiChat(@RequestBody ChatRequest request) { + try { + Object response = aiChatV1Service.chatCompletion(request); + + if (request.isStream()) { + return ResponseEntity.ok() + .contentType(MediaType.TEXT_EVENT_STREAM) + .body((StreamingResponseBody) response); + } else { + return ResponseEntity.ok(response); + } + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(e.getMessage()); + } } /** @@ -84,11 +93,14 @@ public Result> aiChat(@RequestBody AiParam aiParam) { * @param request the AI param * @return ai回答信息 result */ - @Operation(summary = "获取ai回答信息", description = "获取ai回答信息", parameters = { - @Parameter(name = "ChatRequest", description = "入参对象")}, responses = { + @Operation(summary = "获取ai回答信息", description = "获取ai回答信息", + parameters = { + @Parameter(name = "ChatRequest", description = "入参对象") + }, responses = { @ApiResponse(responseCode = "200", description = "返回信息", content = @Content(mediaType = "application/json", schema = @Schema())), - @ApiResponse(responseCode = "400", description = "请求失败")}) + @ApiResponse(responseCode = "400", description = "请求失败") + }) @SystemControllerLog(description = "AI api v1") @PostMapping("/chat/completions") public ResponseEntity chat(@RequestBody ChatRequest request) { @@ -97,14 +109,34 @@ public ResponseEntity chat(@RequestBody ChatRequest request) { if (request.isStream()) { return ResponseEntity.ok() - .contentType(MediaType.TEXT_EVENT_STREAM) - .body((StreamingResponseBody) response); + .contentType(MediaType.TEXT_EVENT_STREAM) + .body((StreamingResponseBody) response); } else { return ResponseEntity.ok(response); } } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(e.getMessage()); + .body(e.getMessage()); } } + + /** + * AI search api + * + * @param content the AI search param + * @return ai回答信息 result + */ + @Operation(summary = "搜索知识库", description = "搜索知识库", + parameters = { + @Parameter(name = "content", description = "入参对象") + }, responses = { + @ApiResponse(responseCode = "200", description = "返回信息", + content = @Content(mediaType = "application/json", schema = @Schema())), + @ApiResponse(responseCode = "400", description = "请求失败") + }) + @SystemControllerLog(description = "AI serarch api") + @PostMapping("/ai/search") + public Result> search(@RequestBody String content) throws Exception { + return aiChatV1Service.chatSearch(content); + } } diff --git a/base/src/main/java/com/tinyengine/it/model/dto/ChatRequest.java b/base/src/main/java/com/tinyengine/it/model/dto/ChatRequest.java index f237fc37..ef3c6d40 100644 --- a/base/src/main/java/com/tinyengine/it/model/dto/ChatRequest.java +++ b/base/src/main/java/com/tinyengine/it/model/dto/ChatRequest.java @@ -27,5 +27,6 @@ public class ChatRequest { private Object messages; private Object tools; private Double temperature = 0.7; - private boolean stream = false; // 流式开关 + private boolean stream = false; + private Integer maxTokens; } diff --git a/base/src/main/java/com/tinyengine/it/model/dto/NodeDto.java b/base/src/main/java/com/tinyengine/it/model/dto/NodeDto.java new file mode 100644 index 00000000..4e77fb7d --- /dev/null +++ b/base/src/main/java/com/tinyengine/it/model/dto/NodeDto.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +package com.tinyengine.it.model.dto; + +import lombok.Data; + +/** + * Node dto + * + * @since 2025-09-16 + */ +@Data +public class NodeDto { + private Double score; + private String docName; + private String content; +} diff --git a/base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java b/base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java index 70a67c40..c5cf93d2 100644 --- a/base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java +++ b/base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java @@ -12,29 +12,37 @@ package com.tinyengine.it.service.app.impl.v1; +import com.aliyun.bailian20231229.Client; +import com.aliyun.tea.TeaException; +import com.aliyun.teaopenapi.models.Config; +import com.aliyun.teaopenapi.models.OpenApiRequest; +import com.aliyun.teaopenapi.models.Params; +import com.aliyun.teautil.models.RuntimeOptions; import com.fasterxml.jackson.databind.JsonNode; -import com.tinyengine.it.common.exception.ExceptionEnum; -import com.tinyengine.it.common.exception.ServiceException; +import com.tinyengine.it.common.base.Result; import com.tinyengine.it.common.log.SystemServiceLog; import com.tinyengine.it.common.utils.JsonUtils; import com.tinyengine.it.config.OpenAIConfig; import com.tinyengine.it.model.dto.ChatRequest; +import com.tinyengine.it.model.dto.NodeDto; import com.tinyengine.it.service.app.v1.AiChatV1Service; -import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import java.io.IOException; -import java.io.OutputStream; +import java.io.InputStream; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.stream.Stream; /** * The type AiChat v1 service. @@ -42,12 +50,17 @@ * @since 2025-08-06 */ @Service -@Slf4j public class AiChatV1ServiceImpl implements AiChatV1Service { + private static final String ACCESS_KEY_ID = System.getenv("ACCESS_KEY_ID"); + private static final String ACCESS_KEY_SECRET = System.getenv("ACCESS_KEY_SECRET"); + private static final String ENDPOINT = "bailian.cn-beijing.aliyuncs.com"; + private static final String INDEX_ID = System.getenv("INDEX_ID"); + private static final String WORK_SPACE_ID = System.getenv("WORK_SPACE_ID"); private final OpenAIConfig config = new OpenAIConfig(); + private static final Logger log = LoggerFactory.getLogger(AiChatV1ServiceImpl.class); private HttpClient httpClient = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(config.getTimeoutSeconds())) - .build(); + .connectTimeout(Duration.ofSeconds(config.getTimeoutSeconds())) + .build(); /** * chatCompletion. @@ -60,13 +73,16 @@ public class AiChatV1ServiceImpl implements AiChatV1Service { public Object chatCompletion(ChatRequest request) throws Exception { String requestBody = buildRequestBody(request); String apiKey = request.getApiKey() != null ? request.getApiKey() : config.getApiKey(); - String baseUrl = request.getBaseUrl() != null ? request.getBaseUrl() : config.getBaseUrl(); + String baseUrl = request.getBaseUrl(); + + // 规范化URL处理 + String normalizedUrl = normalizeApiUrl(baseUrl); + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() - .uri(URI.create(baseUrl)) + .uri(URI.create(normalizedUrl)) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + apiKey) .POST(HttpRequest.BodyPublishers.ofString(requestBody)); - if (request.isStream()) { requestBuilder.header("Accept", "text/event-stream"); return processStreamResponse(requestBuilder); @@ -75,14 +91,203 @@ public Object chatCompletion(ChatRequest request) throws Exception { } } + /** + * 规范化API URL,兼容不同厂商 + */ + private String normalizeApiUrl(String baseUrl) { + if (baseUrl == null || baseUrl.trim().isEmpty()) { + baseUrl = config.getBaseUrl(); + } + baseUrl = baseUrl.trim(); + + if (baseUrl.contains("/chat/completions") || baseUrl.contains("/v1/chat/completions")) { + return ensureUrlProtocol(baseUrl); + } + + if (baseUrl.contains("v1")) { + return ensureUrlProtocol(baseUrl) + "/chat/completions"; + } else { + return ensureUrlProtocol(baseUrl) + "/v1/chat/completions"; + } + } + + /** + * 确保URL有正确的协议前缀 + */ + private String ensureUrlProtocol(String url) { + if (url.startsWith("http://") || url.startsWith("https://")) { + return url; + } + // 默认使用https + return "https://" + url; + } + + /** + * 创建客户端 + */ + private Client createClient() throws Exception { + return new Client(new Config() + .setAccessKeyId(ACCESS_KEY_ID) + .setAccessKeySecret(ACCESS_KEY_SECRET) + .setEndpoint(ENDPOINT) + .setEndpointType("access_key")); + } + + /** + * 创建API信息 + */ + private Params createApiInfo(String WorkspaceId) throws Exception { + return new Params() + // 接口名称 + .setAction("Retrieve") + // 接口版本 + .setVersion("2023-12-29") + // 接口协议 + .setProtocol("HTTPS") + // 接口 HTTP 方法 + .setMethod("POST") + .setAuthType("AK") + .setStyle("ROA") + // 接口 PATH + .setPathname("/" + com.aliyun.openapiutil.Client.getEncodeParam(WorkspaceId) + "/index/retrieve") + // 接口请求体内容格式 + .setReqBodyType("json") + // 接口响应体内容格式 + .setBodyType("json"); + } + + /** + * 安全类型转换工具方法 + */ + private T safeCast(Object obj, Class clazz, T defaultValue) { + if (obj == null) { + return defaultValue; + } + try { + return clazz.cast(obj); + } catch (ClassCastException e) { + log.warn("类型转换失败: {} 无法转换为 {}", obj.getClass().getName(), clazz.getName()); + return defaultValue; + } + } + + private String safeCastToString(Object obj) { + return safeCast(obj, String.class, ""); + } + + private Double safeCastToDouble(Object obj) { + return safeCast(obj, Double.class, 0.0); + } + + private Long safeCastToLong(Object obj) { + return safeCast(obj, Long.class, 0L); + } + + /** + * chatSearch. + * + * @param content the content + * @return String the String + */ + public Result chatSearch(String content) { + try { + Client client = createClient(); + Params params = createApiInfo(WORK_SPACE_ID); + + Map queries = new HashMap<>(); + queries.put("IndexId", INDEX_ID); + queries.put("Query", content); + queries.put("EnableRewrite", "true"); + + RuntimeOptions runtime = new RuntimeOptions(); + OpenApiRequest request = new OpenApiRequest() + .setQuery(com.aliyun.openapiutil.Client.query(queries)); + + Map response = client.callApi(params, request, runtime); + Map body = (Map) response.get("body"); + + if (body == null) { + return Result.failed("响应体为空"); + } + + long status = safeCastToLong(body.get("Status")); + if (status != 200L) { + String message = safeCastToString(body.get("Message")); + log.error("搜索失败: status={}, message={}", status, message); + return Result.failed("搜索失败: " + message); + } + + Map data = safeCast(body.get("Data"), Map.class, new HashMap<>()); + if (data == null || data.isEmpty()) { + return Result.success(new ArrayList<>()); + } + + List nodes = safeCast(data.get("Nodes"), List.class, new ArrayList<>()); + if (nodes.isEmpty()) { + return Result.success(new ArrayList<>()); + } + + List nodeDtos = convertToNodeDtos(nodes); + return Result.success(nodeDtos); + + } catch (TeaException e) { + log.error("阿里云Tea异常: {}", e.getMessage(), e); + return Result.failed("阿里云服务异常: " + e.getMessage()); + } catch (Exception e) { + log.error("搜索异常: {}", e.getMessage(), e); + return Result.failed("系统异常: " + e.getMessage()); + } + } + + /** + * 转换节点数据 + */ + private List convertToNodeDtos(List> nodes) { + List nodeDtos = new ArrayList<>(); + + for (Map node : nodes) { + try { + NodeDto nodeDto = new NodeDto(); + + // 安全获取文本内容 + nodeDto.setContent(safeCastToString(node.get("Text"))); + + // 安全获取分数 + Object scoreObj = node.get("Score"); + if (scoreObj instanceof Number) { + nodeDto.setScore(((Number) scoreObj).doubleValue()); + } else { + nodeDto.setScore(safeCastToDouble(scoreObj)); + } + + // 安全获取元数据 + Map metadata = safeCast(node.get("Metadata"), Map.class, new HashMap<>()); + if (metadata != null) { + nodeDto.setDocName(safeCastToString(metadata.get("doc_name"))); + } + + nodeDtos.add(nodeDto); + + } catch (Exception e) { + log.warn("节点数据转换失败: {}", e.getMessage()); + } + } + + return nodeDtos; + } + private String buildRequestBody(ChatRequest request) { Map body = new HashMap<>(); body.put("model", request.getModel() != null ? request.getModel() : config.getDefaultModel()); body.put("messages", request.getMessages()); - body.put("temperature", request.getTemperature()); body.put("stream", request.isStream()); body.put("tools", request.getTools()); - + if (request.getMaxTokens() != null) { + body.put("max_tokens", request.getMaxTokens()); + } + if (request.getTemperature() != null) { + body.put("temperature", request.getTemperature()); + } return JsonUtils.encode(body); } @@ -96,55 +301,39 @@ private JsonNode processStandardResponse(HttpRequest.Builder requestBuilder) private StreamingResponseBody processStreamResponse(HttpRequest.Builder requestBuilder) { return outputStream -> { try { - HttpResponse> response = httpClient.send( - requestBuilder.build(), HttpResponse.BodyHandlers.ofLines()); - processLines(response.body(), outputStream); + HttpClient client = HttpClient.newHttpClient(); + HttpResponse response = client.send( + requestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream() + ); + if (response.statusCode() != 200) { + String errorBody = new String(response.body().readAllBytes(), StandardCharsets.UTF_8); + throw new IOException("API请求失败: " + response.statusCode() + " - " + errorBody); + } + try (InputStream inputStream = response.body()) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + outputStream.flush(); + } + } } catch (Exception e) { - handleError(e, outputStream); - } finally { - closeStream(outputStream); - } - }; - } + // 简单的错误处理:如果是客户端断开连接,忽略错误 + String errorMsg = e.getMessage(); + if (errorMsg != null && + (errorMsg.contains("Broken pipe") || errorMsg.contains("Connection reset"))) { + return; + } - private void processLines(Stream lines, OutputStream outputStream) { - try (Stream filteredLines = lines.filter(line -> !line.isEmpty())) { - filteredLines.forEach(line -> writeLine(line, outputStream)); - } - } + try { + String errorEvent = "data: {\"error\": \"" + e.getMessage() + "\"}\n\n"; + outputStream.write(errorEvent.getBytes(StandardCharsets.UTF_8)); + outputStream.flush(); + } catch (IOException ignored) { - private void writeLine(String line, OutputStream outputStream) { - try { - if (!line.startsWith("data:")) { - line = "data: " + line; - } - if (!line.endsWith("\n\n")) { - line = line + "\n\n"; + } } - outputStream.write(line.getBytes(StandardCharsets.UTF_8)); - outputStream.flush(); - } catch (IOException e) { - throw new ServiceException(ExceptionEnum.CM326.getResultCode(), - ExceptionEnum.CM326.getResultMsg()); - } - } - - private void handleError(Exception e, OutputStream outputStream) { - try { - String errorEvent = "data: " + JsonUtils.encode(Map.of("error", e.getMessage())) + "\n\n"; - outputStream.write(errorEvent.getBytes(StandardCharsets.UTF_8)); - outputStream.flush(); - } catch (IOException ioException) { - throw new ServiceException(ExceptionEnum.CM326.getResultCode(), ExceptionEnum.CM326.getResultMsg()); - } - } - - private void closeStream(OutputStream outputStream) { - try { - outputStream.close(); - } catch (IOException e) { - // 忽略关闭异常 - } + }; } - } diff --git a/base/src/main/java/com/tinyengine/it/service/app/v1/AiChatV1Service.java b/base/src/main/java/com/tinyengine/it/service/app/v1/AiChatV1Service.java index d2bda179..4a05d05b 100644 --- a/base/src/main/java/com/tinyengine/it/service/app/v1/AiChatV1Service.java +++ b/base/src/main/java/com/tinyengine/it/service/app/v1/AiChatV1Service.java @@ -12,7 +12,11 @@ package com.tinyengine.it.service.app.v1; +import com.tinyengine.it.common.base.Result; import com.tinyengine.it.model.dto.ChatRequest; +import com.tinyengine.it.model.dto.NodeDto; + +import java.util.List; /** * The interface AIChat v 1 service. @@ -27,4 +31,12 @@ public interface AiChatV1Service { * @return Object the Object */ public Object chatCompletion(ChatRequest request) throws Exception; + + /** + * chatSearch. + * + * @param content the content + * @return String the String + */ + public Result> chatSearch(String content) throws Exception; } diff --git a/base/src/main/java/com/tinyengine/it/service/material/impl/BlockServiceImpl.java b/base/src/main/java/com/tinyengine/it/service/material/impl/BlockServiceImpl.java index 7a3867b0..24161939 100644 --- a/base/src/main/java/com/tinyengine/it/service/material/impl/BlockServiceImpl.java +++ b/base/src/main/java/com/tinyengine/it/service/material/impl/BlockServiceImpl.java @@ -605,7 +605,10 @@ public IPage findBlocksByConditionPagetion(Map request) { @Override public List getUsers(List blocksList) { Set userSet = new HashSet<>(); - + List users = new ArrayList<>(); + if(blocksList.isEmpty()) { + return users; + } // 提取 createdBy 列表中的唯一值 blocksList.forEach(item -> { if (item.getCreatedBy() != null && !userSet.contains(item.getCreatedBy())) { diff --git a/base/src/main/resources/mappers/BlockGroupMapper.xml b/base/src/main/resources/mappers/BlockGroupMapper.xml index a4c254ff..a10bc8ec 100644 --- a/base/src/main/resources/mappers/BlockGroupMapper.xml +++ b/base/src/main/resources/mappers/BlockGroupMapper.xml @@ -182,7 +182,7 @@ LEFT JOIN r_block_group_block rbg ON rbg.block_group_id = bg.id LEFT JOIN - t_block b ON b.id = rbg.block_id + t_block b ON b.id = rbg.block_id AND b.id IS NOT NULL AND b.created_by = #{blockCreatedBy} @@ -247,7 +247,7 @@ LEFT JOIN r_block_group_block rbg ON rbg.block_group_id = bg.id LEFT JOIN - t_block b ON b.id = rbg.block_id + t_block b ON b.id = rbg.block_id AND b.id IS NOT NULL AND b.created_by = #{blockCreatedBy} @@ -314,7 +314,7 @@ LEFT JOIN r_block_group_block rbg ON rbg.block_group_id = bg.id LEFT JOIN - t_block b ON b.id = rbg.block_id + t_block b ON b.id = rbg.block_id AND b.id IS NOT NULL AND b.created_by = #{blockCreatedBy} diff --git a/base/src/test/java/com/tinyengine/it/controller/AiChatControllerTest.java b/base/src/test/java/com/tinyengine/it/controller/AiChatControllerTest.java index 165aba00..fde49572 100644 --- a/base/src/test/java/com/tinyengine/it/controller/AiChatControllerTest.java +++ b/base/src/test/java/com/tinyengine/it/controller/AiChatControllerTest.java @@ -16,6 +16,7 @@ import com.tinyengine.it.common.base.Result; import com.tinyengine.it.model.dto.AiParam; +import com.tinyengine.it.model.dto.ChatRequest; import com.tinyengine.it.service.app.AiChatService; import org.junit.jupiter.api.Assertions; @@ -34,30 +35,9 @@ * @since 2024-10-29 */ class AiChatControllerTest { - @Mock - private AiChatService aiChatService; - @InjectMocks - private AiChatController aiChatController; - @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); } - - @Test - void testAiChat() { - AiParam aiParam = new AiParam(); - HashMap mockData = new HashMap() { - { - put("key", "parameter"); - } - }; - Result> mapResult = Result.success(mockData); - - when(aiChatService.getAnswerFromAi(aiParam)).thenReturn(mapResult); - - Result> result = aiChatController.aiChat(aiParam); - Assertions.assertEquals("parameter", result.getData().get("key")); - } } diff --git a/pom.xml b/pom.xml index b9458b29..c70e77d2 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,12 @@ fastjson ${fastjson.version} + + + com.aliyun + bailian20231229 + 2.4.1 + com.baomidou @@ -72,7 +78,6 @@ mybatis-plus-generator ${mybatis-plus.version} - cn.hutool hutool-all