From 04b6793941a53c949e9b51ef567b406e3530dbc0 Mon Sep 17 00:00:00 2001 From: ganliang5722 <474591717@qq.com> Date: Wed, 5 Mar 2025 15:01:12 +0800 Subject: [PATCH] =?UTF-8?q?1.=E6=B7=BB=E5=8A=A0=E6=A8=A1=E5=9E=8B=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=8F=8A=E5=AF=B9=E8=AF=9D=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/langchat/LangChatApplication.java | 3 + .../module/langchat/constant/CommonConst.java | 46 ++++++ .../admin/aigc/AigcAppController.java | 130 +++++++++++++++ .../admin/aigc/AigcOssController.java | 66 ++++++++ .../admin/endpoint/ChatEndpoint.java | 149 +++++++++++++++++ .../module/langchat/dal/dto/ChatRes.java | 47 ++++++ .../yudao/module/langchat/dal/dto/ImageR.java | 56 +++++++ .../module/langchat/dal/dto/PromptConst.java | 77 +++++++++ .../yudao/module/langchat/enums/RoleEnum.java | 37 +++++ .../langchat/mapper/AigcAppApiMapper.java | 29 ++++ .../module/langchat/mapper/AigcAppMapper.java | 29 ++++ .../module/langchat/properties/ChatProps.java | 40 +++++ .../langchat/provider/ModelProvider.java | 59 +++++++ .../service/aigc/AigcAppApiService.java | 28 ++++ .../langchat/service/aigc/AigcAppService.java | 33 ++++ .../aigc/impl/AigcAppApiServiceImpl.java | 32 ++++ .../service/aigc/impl/AigcAppServiceImpl.java | 89 ++++++++++ .../module/langchat/service/core/Agent.java | 32 ++++ .../langchat/service/core/ChatService.java | 42 +++++ .../service/core/LangChatService.java | 38 +++++ .../service/core/impl/ChatServiceImpl.java | 135 +++++++++++++++ .../core/impl/LangChatServiceImpl.java | 155 ++++++++++++++++++ .../core/impl/PersistentChatMemoryStore.java | 95 +++++++++++ .../yudao/module/langchat/store/AppStore.java | 52 ++++++ .../module/langchat/utils/PromptUtil.java | 49 ++++++ .../module/langchat/utils/ServletUtil.java | 110 +++++++++++++ 26 files changed, 1658 insertions(+) create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/constant/CommonConst.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/aigc/AigcAppController.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/aigc/AigcOssController.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/endpoint/ChatEndpoint.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/ChatRes.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/ImageR.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/PromptConst.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/enums/RoleEnum.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/mapper/AigcAppApiMapper.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/mapper/AigcAppMapper.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/properties/ChatProps.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/provider/ModelProvider.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/AigcAppApiService.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/AigcAppService.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/impl/AigcAppApiServiceImpl.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/impl/AigcAppServiceImpl.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/Agent.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/ChatService.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/LangChatService.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/ChatServiceImpl.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/LangChatServiceImpl.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/PersistentChatMemoryStore.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/store/AppStore.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/utils/PromptUtil.java create mode 100644 yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/utils/ServletUtil.java diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/LangChatApplication.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/LangChatApplication.java index 4c8d0ed..04ab1cc 100644 --- a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/LangChatApplication.java +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/LangChatApplication.java @@ -18,8 +18,10 @@ package cn.iocoder.yudao.module.langchat; import cn.iocoder.yudao.module.langchat.config.EnableFileStorage; import cn.iocoder.yudao.module.langchat.config.SpringFileStorageProperties; +import cn.iocoder.yudao.module.langchat.properties.ChatProps; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; /** @@ -30,6 +32,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties @SpringBootApplication @EnableConfigurationProperties({ SpringFileStorageProperties.class, + ChatProps.class }) public class LangChatApplication { diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/constant/CommonConst.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/constant/CommonConst.java new file mode 100644 index 0000000..362f16f --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/constant/CommonConst.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.constant; + +/** + * @author tycoding + * @since 2024/1/15 + */ +public interface CommonConst { + + /** + * UTF-8 编码 + */ + String UTF_8 = "utf-8"; + + /** + * 菜单类型:menu + */ + String MENU_TYPE_MENU = "menu"; + + /** + * 菜单类型:button + */ + String MENU_TYPE_BUTTON = "button"; + + /** + * 菜单:默认Icon图标 + */ + String MENU_ICON = "alert"; + + String LAYOUT = "Layout"; +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/aigc/AigcAppController.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/aigc/AigcAppController.java new file mode 100644 index 0000000..1fec977 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/aigc/AigcAppController.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.controller.admin.aigc; + +import cn.hutool.core.lang.Dict; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; +import cn.iocoder.yudao.module.langchat.entity.AigcApp; +import cn.iocoder.yudao.module.langchat.entity.AigcAppApi; +import cn.iocoder.yudao.module.langchat.entity.AigcKnowledge; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcAppApiService; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcAppService; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcKnowledgeService; +import cn.iocoder.yudao.module.langchat.store.AppStore; +import cn.iocoder.yudao.module.langchat.utils.MybatisUtil; +import cn.iocoder.yudao.module.langchat.utils.QueryPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/aigc/app") +public class AigcAppController { + + private final AigcAppService aigcAppService; + private final AigcAppApiService aigcAppApiService; + private final AppStore appStore; + private final AigcKnowledgeService knowledgeService; + + @GetMapping("/channel/api/{appId}") + @TenantIgnore + public CommonResult getApiChanel(@PathVariable String appId) { + List list = aigcAppApiService.list(Wrappers.lambdaQuery().eq(AigcAppApi::getAppId, appId)); + return CommonResult.success(list.isEmpty() ? null : list.get(0)); + } + + @GetMapping("/list") + @TenantIgnore + public CommonResult> list(AigcApp data) { + return CommonResult.success(aigcAppService.list(data)); + } + + @GetMapping("/page") + @TenantIgnore + public CommonResult page(AigcApp data, QueryPage queryPage) { + return CommonResult.success(MybatisUtil.getData(aigcAppService.page(MybatisUtil.wrap(data, queryPage), + Wrappers.lambdaQuery() + .like(StringUtils.isNotEmpty(data.getName()), AigcApp::getName, data.getName()) + ))); + } + + @GetMapping("/{id}") + @TenantIgnore + public CommonResult findById(@PathVariable String id) { + AigcApp app = aigcAppService.getById(id); + return CommonResult.success(app); + } + + @PostMapping + @TenantIgnore + @PreAuthorize("@ss.hasPermission('aigc:app:add')") + public CommonResult add(@RequestBody AigcApp data) { + data.setCreateTime(new Date()); + data.setSaveTime(new Date()); + aigcAppService.save(data); + appStore.init(); + return CommonResult.success(null); + } + + @PutMapping + @TenantIgnore + @PreAuthorize("@ss.hasPermission('aigc:app:update')") + public CommonResult update(@RequestBody AigcApp data) { + // 校验知识库是否是同纬度 + List knowledgeIds = data.getKnowledgeIds(); + if (knowledgeIds != null && !knowledgeIds.isEmpty()) { + List list = knowledgeService.list(Wrappers.lambdaQuery().in(AigcKnowledge::getId, knowledgeIds)); + Set modelIds = new HashSet<>(); + Set storeIds = new HashSet<>(); + list.forEach(know -> { + modelIds.add(know.getEmbedModelId()); + storeIds.add(know.getEmbedStoreId()); + }); + if (modelIds.size() > 1) { + return CommonResult.error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "请选择相同向量库数据源配置的知识库"); + } + if (storeIds.size() > 1) { + return CommonResult.error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "请选择相同向量模型配置的知识库"); +// throw new ServiceException("请选择相同向量模型配置的知识库"); + } + } + + data.setSaveTime(new Date()); + aigcAppService.updateById(data); + appStore.init(); + return CommonResult.success(null); + } + + @DeleteMapping("/{id}") + @TenantIgnore + @PreAuthorize("@ss.hasPermission('aigc:app:delete')") + public CommonResult delete(@PathVariable String id) { + aigcAppService.removeById(id); + appStore.init(); + return CommonResult.success(null); + } +} \ No newline at end of file diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/aigc/AigcOssController.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/aigc/AigcOssController.java new file mode 100644 index 0000000..dbc7a3a --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/aigc/AigcOssController.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.controller.admin.aigc; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import cn.iocoder.yudao.module.langchat.entity.AigcOss; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcOssService; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * @author tycoding + * @since 2024/1/19 + */ +@RequestMapping("/aigc/oss") +@RestController +@AllArgsConstructor +public class AigcOssController { + + private final AigcOssService aigcOssService; + + @GetMapping("/list") + public CommonResult list() { + List list = aigcOssService.list(Wrappers.lambdaQuery() + .eq(AigcOss::getUserId, SecurityFrameworkUtils.getLoginUserId()) + .orderByDesc(AigcOss::getCreateTime) + ); + return CommonResult.success(null); + } + + @PostMapping("/upload") + public CommonResult upload(MultipartFile file) { + return CommonResult.success(aigcOssService.upload(file, String.valueOf(SecurityFrameworkUtils.getLoginUserId()))); + } + + @PutMapping + public CommonResult update(@RequestBody AigcOss data) { + aigcOssService.updateById(data); + return CommonResult.success(null); + } + + @DeleteMapping("/{id}") + public CommonResult delete(@PathVariable String id) { + aigcOssService.removeById(id); + return CommonResult.success(null); + } +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/endpoint/ChatEndpoint.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/endpoint/ChatEndpoint.java new file mode 100644 index 0000000..a94b8f3 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/controller/admin/endpoint/ChatEndpoint.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.controller.admin.endpoint; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.security.core.LoginUser; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq; +import cn.iocoder.yudao.module.langchat.dal.dto.ChatRes; +import cn.iocoder.yudao.module.langchat.dal.dto.ImageR; +import cn.iocoder.yudao.module.langchat.dal.dto.PromptConst; +import cn.iocoder.yudao.module.langchat.entity.AigcApp; +import cn.iocoder.yudao.module.langchat.entity.AigcMessage; +import cn.iocoder.yudao.module.langchat.entity.AigcModel; +import cn.iocoder.yudao.module.langchat.enums.RoleEnum; +import cn.iocoder.yudao.module.langchat.properties.ChatProps; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcAppService; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcMessageService; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcModelService; +import cn.iocoder.yudao.module.langchat.service.core.ChatService; +import cn.iocoder.yudao.module.langchat.service.core.impl.PersistentChatMemoryStore; +import cn.iocoder.yudao.module.langchat.utils.PromptUtil; +import cn.iocoder.yudao.module.langchat.utils.StreamEmitter; +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.SystemMessage; +import dev.langchain4j.data.message.UserMessage; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @author tycoding + * @since 2024/1/30 + */ +@Slf4j +@RequestMapping("/aigc") +@RestController +@AllArgsConstructor +public class ChatEndpoint { + + private final ChatService chatService; + private final AigcMessageService messageService; + private final AigcModelService aigcModelService; + private final AigcAppService appService; + private final ChatProps chatProps; + + @PostMapping("/chat/completions") + public SseEmitter chat(@RequestBody ChatReq req) { + StreamEmitter emitter = new StreamEmitter(); + req.setEmitter(emitter); + + req.setUserId(String.valueOf(SecurityFrameworkUtils.getLoginUserId())); + req.setUsername(SecurityFrameworkUtils.getLoginUserNickname()); + ExecutorService executor = Executors.newSingleThreadExecutor(); + req.setExecutor(executor); + return emitter.streaming(executor, () -> { + chatService.chat(req); + }); + } + + @GetMapping("/app/info") + public CommonResult appInfo(@RequestParam String appId, String conversationId) { + AigcApp app = appService.getById(appId); + if (StrUtil.isBlank(conversationId)) { + conversationId = app.getId(); + } + + if (StrUtil.isNotBlank(app.getPrompt())) { + // initialize chat memory + SystemMessage message = new SystemMessage(app.getPrompt()); + PersistentChatMemoryStore.init(conversationId, message); + } + + return CommonResult.success(app); + } + + @GetMapping("/chat/messages/{conversationId}") + public CommonResult messages(@PathVariable String conversationId) { + List list = messageService.getMessages(conversationId, String.valueOf(SecurityFrameworkUtils.getLoginUserId())); + + // initialize chat memory + List chatMessages = new ArrayList<>(); + list.forEach(item -> { + if (chatMessages.size() >= chatProps.getMemoryMaxMessage()) { + return; + } + if (item.getRole().equals(RoleEnum.ASSISTANT.getName())) { + chatMessages.add(new AiMessage(item.getMessage())); + } else { + chatMessages.add(new UserMessage(item.getMessage())); + } + }); + PersistentChatMemoryStore.init(conversationId, chatMessages); + return CommonResult.success(list); + } + + @DeleteMapping("/chat/messages/clean/{conversationId}") + public CommonResult cleanMessage(@PathVariable String conversationId) { + messageService.clearMessage(conversationId); + + // clean chat memory + PersistentChatMemoryStore.clean(conversationId); + return CommonResult.success(null); + } + + @PostMapping("/chat/mindmap") + public CommonResult mindmap(@RequestBody ChatReq req) { + req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.MINDMAP)); + return CommonResult.success(new ChatRes(chatService.text(req))); + } + + @PostMapping("/chat/image") + public CommonResult image(@RequestBody ImageR req) { + req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.IMAGE)); + return CommonResult.success(chatService.image(req)); + } + + @GetMapping("/chat/getImageModels") + public CommonResult> getImageModels() { + List list = aigcModelService.getImageModels(); + list.forEach(i -> { + i.setApiKey(null); + i.setSecretKey(null); + }); + return CommonResult.success(list); + } +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/ChatRes.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/ChatRes.java new file mode 100644 index 0000000..f77fca4 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/ChatRes.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.dal.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @author tycoding + * @since 2024/1/29 + */ +@Data +@Accessors(chain = true) +public class ChatRes { + + private boolean isDone = false; + + private String message; + + private Integer usedToken; + + private long time; + + public ChatRes(String message) { + this.message = message; + } + + public ChatRes(Integer usedToken, long startTime) { + this.isDone = true; + this.usedToken = usedToken; + this.time = System.currentTimeMillis() - startTime; + } +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/ImageR.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/ImageR.java new file mode 100644 index 0000000..433ee50 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/ImageR.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.dal.dto; + +import dev.langchain4j.model.input.Prompt; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @author tycoding + * @since 2024/1/6 + */ +@Data +@Accessors(chain = true) +public class ImageR { + + private String modelId; + private String modelName; + private String modelProvider; + + private Prompt prompt; + + /** + * 内容 + */ + private String message; + + /** + * 质量 + */ + private String quality; + + /** + * 尺寸 + */ + private String size; + + /** + * 风格 + */ + private String style; +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/PromptConst.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/PromptConst.java new file mode 100644 index 0000000..30439a9 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/dal/dto/PromptConst.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.dal.dto; + +/** + * @author tycoding + * @since 2024/3/1 + */ +public interface PromptConst { + + String QUESTION = "question"; + + String EMPTY = """ + + ------ + {{question}} + ------ + """; + + String DOCUMENT = "You are good at analyzing documents. Please analyze my questions according to the following documents, question: [{{question}}], [docs]"; + + String MINDMAP = """ + # Role + You are a Markdown outline format engineer who focuses on answering user questions. You can quickly and accurately convert user questions into refined Markdown outline titles, and refine the specific details of each title. + + ## Skills + ### Skill 1: Identify user question intent + - Accurately understand the specific content and needs of user questions. + ### Skill 2: Convert to Markdown outline + - Simplify user questions into Markdown outline-style titles. + ### Skill 3: Return to user + - Return the optimized outline to the user. + + ## Constraints + - Only return the organized Markdown format content, without other explanation information + - Answer the question in the language used by the user. + - Return the answer in Markdown style, keep the main title as concise as possible; and refine the specific step information of each main title in the subtitle. + """; + + String WRITE = """ + # 角色 + 你是一名专业文案撰写师。你擅长运用行业领域相关知识,以专业的视角为用户生成Markdown文档。 + + ## 技能 + ### 技能 1: 写作 + - 提取用户输入的主题和关键信息。 + + ### 技能 2: 专业知识应用 + - 了解相关行业的相关知识。 + - 在撰写内容时,运用专业的语言和视角。 + + ### 技能 3: 遵循Markdown格式 + - 拆分文档内容,以Markdown大纲格式分段内容,更易于用户阅读 + + ## 限制 + - 只讨论写作相关的话题,不要返回其他任何内容和解释。 + - 始终以用户输入的信息为主题,撰写内容。 + """; + + String IMAGE = """ + Please generate the corresponding pictures according to the following requirements. + """; +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/enums/RoleEnum.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/enums/RoleEnum.java new file mode 100644 index 0000000..4aa5388 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/enums/RoleEnum.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.enums; + +import lombok.Getter; + +/** + * @author tycoding + * @since 2024/2/20 + */ +@Getter +public enum RoleEnum { + USER("user"), + ASSISTANT("assistant"), + SYSTEM("system"), + ; + + private final String name; + + RoleEnum(String name) { + this.name = name; + } +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/mapper/AigcAppApiMapper.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/mapper/AigcAppApiMapper.java new file mode 100644 index 0000000..3b76290 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/mapper/AigcAppApiMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.mapper; + +import cn.iocoder.yudao.module.langchat.entity.AigcAppApi; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author tycoding + * @since 2024/7/26 + */ +@Mapper +public interface AigcAppApiMapper extends BaseMapper { +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/mapper/AigcAppMapper.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/mapper/AigcAppMapper.java new file mode 100644 index 0000000..d8c1b64 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/mapper/AigcAppMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.mapper; + +import cn.iocoder.yudao.module.langchat.entity.AigcApp; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author tycoding + * @since 2024/7/26 + */ +@Mapper +public interface AigcAppMapper extends BaseMapper { +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/properties/ChatProps.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/properties/ChatProps.java new file mode 100644 index 0000000..7b6ee21 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/properties/ChatProps.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author tycoding + * @since 2024/8/21 + */ +@Data +@ConfigurationProperties("langchat.chat") +public class ChatProps { + + /** + * 上下文的长度 + */ + private Integer memoryMaxMessage = 20; + + /** + * 前端渲染的消息长度(过长会导致页面渲染卡顿) + */ + private Integer previewMaxMessage = 100; +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/provider/ModelProvider.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/provider/ModelProvider.java new file mode 100644 index 0000000..93da7e4 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/provider/ModelProvider.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.provider; + +import cn.hutool.core.util.ObjectUtil; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.chat.StreamingChatLanguageModel; +import dev.langchain4j.model.image.ImageModel; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * @author tycoding + * @since 2024/3/8 + */ +@Component +@AllArgsConstructor +public class ModelProvider { + + private final ModelStoreFactory modelStoreFactory; + + public StreamingChatLanguageModel stream(String modelId) { + StreamingChatLanguageModel streamingChatModel = modelStoreFactory.getStreamingChatModel(modelId); + if (ObjectUtil.isNotEmpty(streamingChatModel)) { + return streamingChatModel; + } + throw new RuntimeException("没有匹配到模型,请检查模型配置!"); + } + + public ChatLanguageModel text(String modelId) { + ChatLanguageModel chatLanguageModel = modelStoreFactory.getChatLanguageModel(modelId); + if (ObjectUtil.isNotEmpty(chatLanguageModel)) { + return chatLanguageModel; + } + throw new RuntimeException("没有匹配到模型,请检查模型配置!"); + } + + public ImageModel image(String modelId) { + ImageModel imageModel = modelStoreFactory.getImageModel(modelId); + if (ObjectUtil.isNotEmpty(imageModel)) { + return imageModel; + } + throw new RuntimeException("没有匹配到模型,请检查模型配置!"); + } +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/AigcAppApiService.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/AigcAppApiService.java new file mode 100644 index 0000000..377dc98 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/AigcAppApiService.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.service.aigc; + +import cn.iocoder.yudao.module.langchat.entity.AigcAppApi; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * @author tycoding + * @since 2024/7/26 + */ +public interface AigcAppApiService extends IService { + +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/AigcAppService.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/AigcAppService.java new file mode 100644 index 0000000..a2bf5dc --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/AigcAppService.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.service.aigc; + +import cn.iocoder.yudao.module.langchat.entity.AigcApp; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * @author tycoding + * @since 2024/7/26 + */ +public interface AigcAppService extends IService { + + List list(AigcApp data); + + AigcApp getById(String id); +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/impl/AigcAppApiServiceImpl.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/impl/AigcAppApiServiceImpl.java new file mode 100644 index 0000000..409f6f3 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/impl/AigcAppApiServiceImpl.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.service.aigc.impl; + +import cn.iocoder.yudao.module.langchat.entity.AigcAppApi; +import cn.iocoder.yudao.module.langchat.mapper.AigcAppApiMapper; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcAppApiService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * @author tycoding + * @since 2024/7/26 + */ +@Service +public class AigcAppApiServiceImpl extends ServiceImpl implements AigcAppApiService { + +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/impl/AigcAppServiceImpl.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/impl/AigcAppServiceImpl.java new file mode 100644 index 0000000..08707a4 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/aigc/impl/AigcAppServiceImpl.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.service.aigc.impl; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.langchat.entity.AigcApp; +import cn.iocoder.yudao.module.langchat.entity.AigcKnowledge; +import cn.iocoder.yudao.module.langchat.entity.AigcModel; +import cn.iocoder.yudao.module.langchat.mapper.AigcAppMapper; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcAppService; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcKnowledgeService; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcModelService; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author tycoding + * @since 2024/7/26 + */ +@RequiredArgsConstructor +@Service +public class AigcAppServiceImpl extends ServiceImpl implements AigcAppService { + + private final AigcModelService aigcModelService; + private final AigcKnowledgeService aigcKnowledgeService; + + @Override + public List list(AigcApp data) { + List list = baseMapper.selectList(Wrappers.lambdaQuery() + .like(StrUtil.isNotBlank(data.getName()), AigcApp::getName, data.getName())); + + Map> modelMap = aigcModelService.list(new AigcModel()).stream().collect(Collectors.groupingBy(AigcModel::getId)); + Map> knowledgeMap = aigcKnowledgeService.list().stream().collect(Collectors.groupingBy(AigcKnowledge::getId)); + list.forEach(i -> { + List models = modelMap.get(i.getModelId()); + if (models != null) { + i.setModel(models.get(0)); + } + if (i.getKnowledgeIds() != null) { + List knowledges = new ArrayList<>(); + i.getKnowledgeIds().forEach(k -> { + List items = knowledgeMap.get(k); + if (items != null) { + knowledges.add(items.get(0)); + } + }); + i.setKnowledges(knowledges); + } + }); + return list; + } + + @Override + public AigcApp getById(String id) { + AigcApp app = baseMapper.selectById(id); + if (app != null) { + String modelId = app.getModelId(); + if (modelId != null) { + app.setModel(aigcModelService.selectById(modelId)); + } + List knowledgeIds = app.getKnowledgeIds(); + if (knowledgeIds != null && !knowledgeIds.isEmpty()) { + app.setKnowledges(aigcKnowledgeService.list(Wrappers.lambdaQuery().in(AigcKnowledge::getId, knowledgeIds))); + } + } + return app; + } +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/Agent.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/Agent.java new file mode 100644 index 0000000..cc501cd --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/Agent.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.service.core; + +import dev.langchain4j.service.MemoryId; +import dev.langchain4j.service.TokenStream; +import dev.langchain4j.service.UserMessage; + +/** + * @author tycoding + * @since 2024/3/8 + */ +public interface Agent { + + TokenStream stream(@MemoryId String id, @UserMessage String message); + + String text(@MemoryId String id, @UserMessage String message); +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/ChatService.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/ChatService.java new file mode 100644 index 0000000..de253f7 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/ChatService.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.service.core; + + +import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq; +import cn.iocoder.yudao.module.langchat.dal.dto.ImageR; +import cn.iocoder.yudao.module.langchat.entity.AigcOss; + +/** + * @author tycoding + * @since 2024/1/4 + */ +public interface ChatService { + + void chat(ChatReq req); + + + /** + * 文本请求 + */ + String text(ChatReq req); + + /** + * 文生图 + */ + AigcOss image(ImageR req); +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/LangChatService.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/LangChatService.java new file mode 100644 index 0000000..c765a21 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/LangChatService.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.service.core; + +import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq; +import cn.iocoder.yudao.module.langchat.dal.dto.ImageR; +import dev.langchain4j.data.image.Image; +import dev.langchain4j.model.output.Response; +import dev.langchain4j.service.TokenStream; + +/** + * @author tycoding + * @since 2024/3/8 + */ +public interface LangChatService { + + TokenStream chat(ChatReq req); + + TokenStream singleChat(ChatReq req); + + String text(ChatReq req); + + Response image(ImageR req); +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/ChatServiceImpl.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/ChatServiceImpl.java new file mode 100644 index 0000000..5d13fb4 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/ChatServiceImpl.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.service.core.impl; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq; +import cn.iocoder.yudao.module.langchat.dal.dto.ChatRes; +import cn.iocoder.yudao.module.langchat.dal.dto.ImageR; +import cn.iocoder.yudao.module.langchat.entity.AigcApp; +import cn.iocoder.yudao.module.langchat.entity.AigcMessage; +import cn.iocoder.yudao.module.langchat.entity.AigcOss; +import cn.iocoder.yudao.module.langchat.enums.RoleEnum; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcMessageService; +import cn.iocoder.yudao.module.langchat.service.core.ChatService; +import cn.iocoder.yudao.module.langchat.service.core.LangChatService; +import cn.iocoder.yudao.module.langchat.store.AppStore; +import cn.iocoder.yudao.module.langchat.utils.ServletUtil; +import cn.iocoder.yudao.module.langchat.utils.StreamEmitter; +import dev.langchain4j.data.image.Image; +import dev.langchain4j.model.output.Response; +import dev.langchain4j.model.output.TokenUsage; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; + +/** + * @author tycoding + * @since 2024/1/4 + */ +@Slf4j +@Service +@AllArgsConstructor +public class ChatServiceImpl implements ChatService { + + private final LangChatService langChatService; + private final AigcMessageService aigcMessageService; + private final AppStore appStore; + + @Override + public void chat(ChatReq req) { + StreamEmitter emitter = req.getEmitter(); + long startTime = System.currentTimeMillis(); + StringBuilder text = new StringBuilder(); + + if (StrUtil.isNotBlank(req.getAppId())) { + AigcApp app = appStore.get(req.getAppId()); + if (app != null) { + req.setModelId(app.getModelId()); + req.setPromptText(app.getPrompt()); + req.setKnowledgeIds(app.getKnowledgeIds()); + } + } + + // save user message + req.setRole(RoleEnum.USER.getName()); + saveMessage(req, 0, 0); + + try { + langChatService + .chat(req) + .onNext(e -> { + text.append(e); + emitter.send(new ChatRes(e)); + }) + .onComplete((e) -> { + TokenUsage tokenUsage = e.tokenUsage(); + ChatRes res = new ChatRes(tokenUsage.totalTokenCount(), startTime); + emitter.send(res); + emitter.complete(); + + // save assistant message + req.setMessage(text.toString()); + req.setRole(RoleEnum.ASSISTANT.getName()); + saveMessage(req, tokenUsage.inputTokenCount(), tokenUsage.outputTokenCount()); + }) + .onError((e) -> { + emitter.error(e.getMessage()); + throw new RuntimeException(e.getMessage()); + }) + .start(); + } catch (Exception e) { + e.printStackTrace(); + emitter.error(e.getMessage()); + throw new RuntimeException(e.getMessage()); + } + } + + private void saveMessage(ChatReq req, Integer inputToken, Integer outputToken) { + if (req.getConversationId() != null) { + AigcMessage message = new AigcMessage(); + BeanUtils.copyProperties(req, message); + message.setIp(ServletUtil.getIpAddr()); + message.setPromptTokens(inputToken); + message.setTokens(outputToken); + aigcMessageService.addMessage(message); + } + } + + @Override + public String text(ChatReq req) { + String text; + try { + text = langChatService.text(req); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e.getMessage()); + } + return text; + } + + @Override + public AigcOss image(ImageR req) { + Response res = langChatService.image(req); + + String path = res.content().url().toString(); + AigcOss oss = new AigcOss(); + oss.setUrl(path); + return oss; + } +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/LangChatServiceImpl.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/LangChatServiceImpl.java new file mode 100644 index 0000000..c4593a1 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/LangChatServiceImpl.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.service.core.impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq; +import cn.iocoder.yudao.module.langchat.dal.dto.ImageR; +import cn.iocoder.yudao.module.langchat.properties.ChatProps; +import cn.iocoder.yudao.module.langchat.provider.EmbeddingProvider; +import cn.iocoder.yudao.module.langchat.provider.ModelProvider; +import cn.iocoder.yudao.module.langchat.service.core.Agent; +import cn.iocoder.yudao.module.langchat.service.core.LangChatService; +import cn.iocoder.yudao.module.langchat.utils.PromptUtil; +import dev.langchain4j.data.image.Image; +import dev.langchain4j.memory.chat.MessageWindowChatMemory; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.chat.StreamingChatLanguageModel; +import dev.langchain4j.model.image.ImageModel; +import dev.langchain4j.model.output.Response; +import dev.langchain4j.rag.DefaultRetrievalAugmentor; +import dev.langchain4j.rag.content.retriever.ContentRetriever; +import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; +import dev.langchain4j.rag.query.Query; +import dev.langchain4j.service.AiServices; +import dev.langchain4j.service.TokenStream; +import dev.langchain4j.store.embedding.filter.Filter; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.function.Function; + +import static cn.iocoder.yudao.module.langchat.enums.EmbedConst.KNOWLEDGE; +import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey; + +/** + * @author tycoding + * @since 2024/3/8 + */ +@Slf4j +@Service +@AllArgsConstructor +public class LangChatServiceImpl implements LangChatService { + + private final ModelProvider provider; + private final EmbeddingProvider embeddingProvider; + private final ChatProps chatProps; + + private AiServices build(StreamingChatLanguageModel streamModel, ChatLanguageModel model, ChatReq req) { + AiServices aiServices = AiServices.builder(Agent.class) + .chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder() + .id(req.getConversationId()) + .chatMemoryStore(new PersistentChatMemoryStore()) + .maxMessages(chatProps.getMemoryMaxMessage()) + .build()); + if (StrUtil.isNotBlank(req.getPromptText())) { + aiServices.systemMessageProvider(memoryId -> req.getPromptText()); + } + if (streamModel != null) { + aiServices.streamingChatLanguageModel(streamModel); + } + if (model != null) { + aiServices.chatLanguageModel(model); + } + return aiServices; + } + + @Override + public TokenStream chat(ChatReq req) { + StreamingChatLanguageModel model = provider.stream(req.getModelId()); + if (StrUtil.isBlank(req.getConversationId())) { + req.setConversationId(IdUtil.simpleUUID()); + } + + AiServices aiServices = build(model, null, req); + + if (StrUtil.isNotBlank(req.getKnowledgeId())) { + req.getKnowledgeIds().add(req.getKnowledgeId()); + } + + if (req.getKnowledgeIds() != null && !req.getKnowledgeIds().isEmpty()) { + Function filter = (query) -> metadataKey(KNOWLEDGE).isIn(req.getKnowledgeIds()); + ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder() + .embeddingStore(embeddingProvider.getEmbeddingStore(req.getKnowledgeIds())) + .embeddingModel(embeddingProvider.getEmbeddingModel(req.getKnowledgeIds())) + .dynamicFilter(filter) + .build(); + aiServices.retrievalAugmentor(DefaultRetrievalAugmentor + .builder() + .contentRetriever(contentRetriever) + .build()); + } + Agent agent = aiServices.build(); + return agent.stream(req.getConversationId(), req.getMessage()); + } + + @Override + public TokenStream singleChat(ChatReq req) { + StreamingChatLanguageModel model = provider.stream(req.getModelId()); + if (StrUtil.isBlank(req.getConversationId())) { + req.setConversationId(IdUtil.simpleUUID()); + } + + Agent agent = build(model, null, req).build(); + if (req.getPrompt() == null) { + req.setPrompt(PromptUtil.build(req.getMessage(), req.getPromptText())); + } + return agent.stream(req.getConversationId(), req.getPrompt().text()); + } + + @Override + public String text(ChatReq req) { + if (StrUtil.isBlank(req.getConversationId())) { + req.setConversationId(IdUtil.simpleUUID()); + } + + try { + ChatLanguageModel model = provider.text(req.getModelId()); + Agent agent = build(null, model, req).build(); + String text = agent.text(req.getConversationId(), req.getMessage()); + return text; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public Response image(ImageR req) { + try { + ImageModel model = provider.image(req.getModelId()); + return model.generate(req.getPrompt().text()); + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "图片生成失败"); + } + } +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/PersistentChatMemoryStore.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/PersistentChatMemoryStore.java new file mode 100644 index 0000000..2b7091d --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/service/core/impl/PersistentChatMemoryStore.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.service.core.impl; + +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.SystemMessage; +import dev.langchain4j.store.memory.chat.ChatMemoryStore; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author tycoding + * @since 2024/8/15 + */ +@Slf4j +public class PersistentChatMemoryStore implements ChatMemoryStore { + + private static final Map> store = new HashMap<>(); + private static final Map initSystemMessageStore = new HashMap<>(); + private static final Map initMessageStore = new HashMap<>(); + + public static void clean(Object memoryId) { + log.info("clean message memory store to: {}", memoryId); + store.remove(memoryId); + } + + public static void init(Object memoryId, SystemMessage message) { + Boolean isInitSystemMessage = initSystemMessageStore.get(memoryId); + if (isInitSystemMessage != null && isInitSystemMessage) { + return; + } + + List list = store.get(memoryId); + if (list == null) { + store.put(memoryId, new ArrayList<>(List.of(message))); + } else { + list.add(message); + } + initSystemMessageStore.put(memoryId, true); + } + + public static void init(Object memoryId, List messages) { + log.info("initialize message memory store to: {}", memoryId); + + Boolean isInitMessage = initMessageStore.get(memoryId); + if (isInitMessage != null && isInitMessage) { + return; + } + + List list = store.get(memoryId); + if (list == null) { + store.put(memoryId, messages); + } else { + list.addAll(messages); + } + initMessageStore.put(memoryId, true); + } + + @Override + public List getMessages(Object memoryId) { + List list = store.get(memoryId); + if (list == null) { + return new ArrayList<>(); + } + return list; + } + + @Override + public void updateMessages(Object memoryId, List messages) { + store.put(memoryId, messages); + } + + @Override + public void deleteMessages(Object memoryId) { + store.remove(memoryId); + } +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/store/AppStore.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/store/AppStore.java new file mode 100644 index 0000000..1456cb6 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/store/AppStore.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.store; + +import cn.iocoder.yudao.module.langchat.entity.AigcApp; +import cn.iocoder.yudao.module.langchat.service.aigc.AigcAppService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author tycoding + * @since 2024/8/8 + */ +@Slf4j +@Component +@AllArgsConstructor +public class AppStore { + + private static final Map appMap = new HashMap<>(); + private final AigcAppService aigcAppService; + + @PostConstruct + public void init() { + log.info("initialize app config list..."); + List list = aigcAppService.list(); + list.forEach(i -> appMap.put(i.getId(), i)); + } + + public AigcApp get(String appId) { + return appMap.get(appId); + } +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/utils/PromptUtil.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/utils/PromptUtil.java new file mode 100644 index 0000000..e18e120 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/utils/PromptUtil.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.utils; + +import cn.hutool.core.bean.BeanUtil; +import cn.iocoder.yudao.module.langchat.dal.dto.PromptConst; +import dev.langchain4j.model.input.Prompt; +import dev.langchain4j.model.input.PromptTemplate; + +import java.util.Map; + +/** + * @author tycoding + * @since 2024/3/1 + */ +public class PromptUtil { + + public static Prompt build(String message) { + return new Prompt(message); + } + + public static Prompt build(String message, String promptText) { + return new PromptTemplate(promptText + PromptConst.EMPTY).apply(Map.of(PromptConst.QUESTION, message)); + } + + public static Prompt build(String message, String promptText, Object param) { + Map params = BeanUtil.beanToMap(param, false, true); + params.put(PromptConst.QUESTION, message); + return new PromptTemplate(promptText).apply(params); + } + + public static Prompt buildDocs(String message) { + return new PromptTemplate(PromptConst.DOCUMENT).apply(Map.of(PromptConst.QUESTION, message)); + } +} diff --git a/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/utils/ServletUtil.java b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/utils/ServletUtil.java new file mode 100644 index 0000000..79a77d4 --- /dev/null +++ b/yudao-module-langchat/yudao-module-langchat-biz/src/main/java/cn/iocoder/yudao/module/langchat/utils/ServletUtil.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024 LangChat. TyCoding All Rights Reserved. + * + * Licensed under the GNU Affero General Public License, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/agpl-3.0.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.module.langchat.utils; + +import cn.hutool.json.JSONUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.langchat.constant.CommonConst; +import lombok.SneakyThrows; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author tycoding + * @since 2024/1/2 + */ +public class ServletUtil { + + @SneakyThrows + public static void write(HttpServletResponse response, CommonResult data) { + response.setStatus(data.getCode()); + response.setHeader("Content-type", "application/json;charset=" + CommonConst.UTF_8); + response.setCharacterEncoding(CommonConst.UTF_8); + response.getWriter().write(JSONUtil.toJsonStr(data)); + } + + @SneakyThrows + public static void write(HttpServletResponse response, int status, CommonResult data) { + response.setStatus(status); + response.setHeader("Content-type", "application/json;charset=" + CommonConst.UTF_8); + response.setCharacterEncoding(CommonConst.UTF_8); + response.getWriter().write(JSONUtil.toJsonStr(data)); + } + + public static HttpServletRequest getRequest() { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (servletRequestAttributes != null) { + return servletRequestAttributes.getRequest(); + } + return null; + } + + public static String getAuthorizationToken() { + String token = getRequest().getHeader("Authorization"); + if (token != null && token.toLowerCase().startsWith("bearer")) { + return token.toLowerCase().replace("bearer", "").trim(); + } + return null; + } + + public static String getToken(String token) { + if (token != null && token.toLowerCase().startsWith("bearer")) { + return token.replace("bearer", "").trim(); + } + return token; + } + + public static String getIpAddr() { + HttpServletRequest request = getRequest(); + if (request == null) { + return "unknown"; + } else { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Forwarded-For"); + } + + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + + if ("0:0:0:0:0:0:0:1".equals(ip)) { + ip = "127.0.0.1"; + } + + if (ip.contains(",")) { + ip = ip.split(",")[0]; + } + return ip; + } + } +} \ No newline at end of file