diff --git a/pom.xml b/pom.xml index d8f67281..fde84309 100644 --- a/pom.xml +++ b/pom.xml @@ -14,25 +14,26 @@ 1.0.0 - 3.0.6 + 3.4.4 UTF-8 UTF-8 17 - 3.0.1 + 8.0.33 + 3.5.16 2.1.0 0.15.0 5.2.3 3.2.1 2.3 1.34.0 - 3.5.3.1 + 3.5.11 3.9.1 - 5.8.18 + 5.8.35 4.10.0 + 4.3.1 3.0.3 3.20.1 2.2.4 - 3.6.1 2.14.2 2.4.0 1.2.1 @@ -41,10 +42,6 @@ 1.72 2.7.0 - - - 1.33 - 1.12.400 @@ -60,6 +57,7 @@ 4.5.0 4.6.0 4.6.0 + 4.6.0 @@ -96,6 +94,12 @@ + + mysql + mysql-connector-java + ${mysql.version} + + org.springframework.boot @@ -194,22 +198,22 @@ ${satoken.version} - - - com.baomidou - dynamic-datasource-spring-boot-starter - ${dynamic-ds.version} - - org.mybatis.spring.boot - mybatis-spring-boot-starter - ${spring-boot.mybatis} + org.mybatis + mybatis + ${mybatis.version} com.baomidou - mybatis-plus-boot-starter + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + + com.baomidou + mybatis-plus-jsqlparser ${mybatis-plus.version} @@ -219,6 +223,13 @@ ${mybatis-plus.version} + + + com.baomidou + dynamic-datasource-spring-boot3-starter + ${dynamic-ds.version} + + p6spy @@ -244,18 +255,6 @@ ${tencent.sms.version} - - - - - - - - - - - - org.redisson redisson-spring-boot-starter @@ -281,13 +280,6 @@ ${alibaba-ttl.version} - - - org.yaml - snakeyaml - ${snakeyaml.version} - - org.bouncycastle @@ -321,10 +313,21 @@ ${revision} + + org.ruoyi + ruoyi-knowledge-api + ${revision} + org.ruoyi - ruoyi-knowledge + ruoyi-chat-api + ${revision} + + + + org.ruoyi + ruoyi-system-api ${revision} @@ -334,20 +337,23 @@ ${revision} - - org.ruoyi - ruoyi-demo - ${revision} - + + + + + - ruoyi-admin ruoyi-common ruoyi-modules + ruoyi-modules-api + ruoyi-admin + ruoyi-extend + pom diff --git a/ruoyi-admin/Dockerfile b/ruoyi-admin/Dockerfile deleted file mode 100644 index bf14c4ee..00000000 --- a/ruoyi-admin/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -#基础镜像 -FROM findepi/graalvm:java17-native - -# 设置环境变量 -ENV LANG C.UTF-8 -ENV LANGUAGE C.UTF-8 -ENV LC_ALL C.UTF-8 -ENV SERVER_PORT=6039 - -MAINTAINER ageerle - -RUN mkdir -p /ruoyi/server/logs \ - /ruoyi/server/temp \ - /ruoyi/skywalking/agent - - -#工作空间 -WORKDIR /ruoyi/server - - - -EXPOSE ${SERVER_PORT} - -ADD ./target/ruoyi-admin.jar ./app.jar - - -ENTRYPOINT ["java", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dserver.port=${SERVER_PORT}", \ - # 应用名称 如果想区分集群节点监控 改成不同的名称即可 -# "-Dskywalking.agent.service_name=ruoyi-server", \ -# "-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar", \ - "-jar", "app.jar"] - - diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 5ad40cdd..89003c2a 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -42,10 +42,11 @@ mssql-jdbc - - org.ruoyi - ruoyi-common-doc - + + + + + org.ruoyi @@ -57,41 +58,6 @@ ruoyi-chat - - org.ruoyi - ruoyi-knowledge - - - - org.ruoyi - ruoyi-generator - - - - - org.ruoyi - ruoyi-demo - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - net.coobird - thumbnailator - 0.4.11 - - - io.github.ollama4j - ollama4j - 1.0.79 - compile - - diff --git a/ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java b/ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java index 10931863..5a7a514c 100644 --- a/ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java +++ b/ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java @@ -2,8 +2,6 @@ package org.ruoyi.controller; import cn.dev33.satoken.annotation.SaIgnore; import cn.hutool.core.collection.CollUtil; -import cn.hutool.json.JSONUtil; -import me.chanjar.weixin.common.error.WxErrorException; import org.ruoyi.common.core.constant.Constants; import org.ruoyi.common.core.domain.R; import org.ruoyi.common.core.domain.model.EmailLoginBody; @@ -16,18 +14,17 @@ import org.ruoyi.common.core.utils.StreamUtils; import org.ruoyi.common.core.utils.StringUtils; import org.ruoyi.common.satoken.utils.LoginHelper; import org.ruoyi.common.tenant.helper.TenantHelper; -import org.ruoyi.system.domain.bo.SysTenantBo; -import org.ruoyi.system.domain.vo.LoginTenantVo; -import org.ruoyi.system.domain.vo.SysTenantVo; -import org.ruoyi.system.domain.vo.TenantListVo; -import org.ruoyi.system.service.ISysTenantService; - -import org.ruoyi.system.service.SysLoginService; -import org.ruoyi.system.service.SysRegisterService; -import org.ruoyi.system.domain.vo.LoginVo; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import org.ruoyi.system.domain.bo.SysTenantBo; +import org.ruoyi.system.domain.vo.LoginTenantVo; +import org.ruoyi.system.domain.vo.LoginVo; +import org.ruoyi.system.domain.vo.SysTenantVo; +import org.ruoyi.system.domain.vo.TenantListVo; +import org.ruoyi.system.service.ISysTenantService; +import org.ruoyi.system.service.SysLoginService; +import org.ruoyi.system.service.SysRegisterService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -50,15 +47,6 @@ public class AuthController { private final SysRegisterService registerService; private final ISysTenantService tenantService; - - @PostMapping("/xcxLogin") - public R login(@Validated @RequestBody String xcxCode) throws WxErrorException { - - String openidFromCode = loginService.getOpenidFromCode((String) JSONUtil.parseObj(xcxCode).get("xcxCode")); - LoginVo loginVo = loginService.mpLogin(openidFromCode); - return R.ok(loginVo); - } - /** * 登录方法 * @@ -75,7 +63,6 @@ public class AuthController { body.getUsername(), body.getPassword(), body.getCode(), body.getUuid()); loginVo.setToken(token); - loginVo.setAccess_token(token); loginVo.setUserInfo(LoginHelper.getLoginUser()); return R.ok(loginVo); } @@ -97,7 +84,6 @@ public class AuthController { /** * 访客登录 - * * @param loginBody 登录信息 * @return token信息 */ @@ -136,7 +122,7 @@ public class AuthController { */ @PostMapping("/register") public R register(@Validated @RequestBody RegisterBody user, HttpServletRequest request) { - String domainName = request.getServerName(); + String domainName = request.getServerName(); user.setDomainName(domainName); registerService.register(user); return R.ok(); diff --git a/ruoyi-admin/src/main/java/org/ruoyi/controller/IndexController.java b/ruoyi-admin/src/main/java/org/ruoyi/controller/IndexController.java index ecbfe71e..2a4ee1f0 100644 --- a/ruoyi-admin/src/main/java/org/ruoyi/controller/IndexController.java +++ b/ruoyi-admin/src/main/java/org/ruoyi/controller/IndexController.java @@ -2,6 +2,7 @@ package org.ruoyi.controller; import cn.dev33.satoken.annotation.SaIgnore; import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -11,7 +12,6 @@ import org.springframework.web.bind.annotation.RestController; * @author Lion Li */ @SaIgnore -@RequiredArgsConstructor @RestController public class IndexController { @@ -20,7 +20,9 @@ public class IndexController { */ @GetMapping("/") public String index() { - return "RuoYi-AI 启动成功"; + return "RuoYi AI启动成功!"; } + + } diff --git a/ruoyi-admin/src/main/java/org/ruoyi/controller/KnowledgeController.java b/ruoyi-admin/src/main/java/org/ruoyi/controller/KnowledgeController.java deleted file mode 100644 index e399e888..00000000 --- a/ruoyi-admin/src/main/java/org/ruoyi/controller/KnowledgeController.java +++ /dev/null @@ -1,184 +0,0 @@ -package org.ruoyi.controller; - -import cn.dev33.satoken.stp.StpUtil; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.ruoyi.common.chat.domain.request.ChatRequest; -import org.ruoyi.common.chat.entity.chat.Message; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.core.validate.AddGroup; -import org.ruoyi.common.excel.utils.ExcelUtil; -import org.ruoyi.common.log.annotation.Log; -import org.ruoyi.common.log.enums.BusinessType; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.common.satoken.utils.LoginHelper; -import org.ruoyi.common.web.core.BaseController; -import org.ruoyi.knowledge.chain.vectorstore.VectorStore; -import org.ruoyi.knowledge.domain.bo.KnowledgeAttachBo; -import org.ruoyi.knowledge.domain.bo.KnowledgeFragmentBo; -import org.ruoyi.knowledge.domain.bo.KnowledgeInfoBo; -import org.ruoyi.knowledge.domain.req.KnowledgeInfoUploadRequest; -import org.ruoyi.knowledge.domain.vo.KnowledgeAttachVo; -import org.ruoyi.knowledge.domain.vo.KnowledgeFragmentVo; -import org.ruoyi.knowledge.domain.vo.KnowledgeInfoVo; -import org.ruoyi.knowledge.service.EmbeddingService; -import org.ruoyi.knowledge.service.IKnowledgeAttachService; -import org.ruoyi.knowledge.service.IKnowledgeFragmentService; -import org.ruoyi.knowledge.service.IKnowledgeInfoService; -import org.ruoyi.system.service.ISseService; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.util.List; - - -/** - * 知识库 - * - * @author Lion Li - * @date 2024-10-21 - */ -@Validated -@RequiredArgsConstructor -@RestController -@RequestMapping("/knowledge") -public class KnowledgeController extends BaseController { - - private final IKnowledgeInfoService knowledgeInfoService; - - private final VectorStore vectorStore; - - private final IKnowledgeAttachService attachService; - - private final IKnowledgeFragmentService fragmentService; - - private final EmbeddingService embeddingService; - - private final ISseService sseService; - - /** - * 知识库对话 - */ - @PostMapping("/send") - public SseEmitter send(@RequestBody @Valid ChatRequest chatRequest, HttpServletRequest request) { - List messages = chatRequest.getMessages(); - // 获取知识库信息 - Message message = messages.get(messages.size() - 1); - StringBuilder sb = new StringBuilder(message.getContent().toString()); - List nearestList; - List queryVector = embeddingService.getQueryVector(message.getContent().toString(), chatRequest.getKid()); - nearestList = vectorStore.nearest(queryVector, chatRequest.getKid()); - for (String prompt : nearestList) { - sb.append("\n####").append(prompt); - } - sb.append( (nearestList.size() > 0 ? "\n\n注意:回答问题时,须严格根据我给你的系统上下文内容原文进行回答,请不要自己发挥,回答时保持原来文本的段落层级" : "")); - message.setContent(sb.toString()); - return sseService.sseChat(chatRequest, request); - } - - /** - * 根据用户信息查询本地知识库 - */ - @GetMapping("/list") - public TableDataInfo list(KnowledgeInfoBo bo, PageQuery pageQuery) { - if(!StpUtil.isLogin()){ - return null; - } - bo.setUid(LoginHelper.getUserId()); - return knowledgeInfoService.queryPageList(bo, pageQuery); - } - - /** - * 新增知识库 - */ - @Log(title = "知识库", businessType = BusinessType.INSERT) - @PostMapping("/save") - public R save(@Validated(AddGroup.class) @RequestBody KnowledgeInfoBo bo) { - knowledgeInfoService.saveOne(bo); - return R.ok(); - } - - /** - * 删除知识库 - */ - @PostMapping("/remove/{id}") - public R remove(@PathVariable String id){ - knowledgeInfoService.removeKnowledge(id); - return R.ok("删除知识库成功!"); - } - - /** - * 修改知识库 - */ - @Log(title = "知识库", businessType = BusinessType.UPDATE) - @PostMapping("/edit") - public R edit( @RequestBody KnowledgeInfoBo bo) { - return toAjax(knowledgeInfoService.updateByBo(bo)); - } - - /** - * 导出知识库列表 - */ - @Log(title = "知识库", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(KnowledgeInfoBo bo, HttpServletResponse response) { - List list = knowledgeInfoService.queryList(bo); - ExcelUtil.exportExcel(list, "知识库", KnowledgeInfoVo.class, response); - } - - /** - * 查询知识附件信息 - */ - @GetMapping("/detail/{kid}") - public TableDataInfo attach(KnowledgeAttachBo bo, PageQuery pageQuery,@PathVariable String kid){ - bo.setKid(kid); - return attachService.queryPageList(bo, pageQuery); - } - - /** - * 上传知识库附件 - */ - @PostMapping(value = "/attach/upload") - public R upload(KnowledgeInfoUploadRequest request){ - knowledgeInfoService.upload(request); - return R.ok("上传知识库附件成功!"); - } - - /** - * 获取知识库附件详细信息 - * - * @param id 主键 - */ - @GetMapping("attach/info/{id}") - public R getAttachInfo(@NotNull(message = "主键不能为空") - @PathVariable Long id) { - return R.ok(attachService.queryById(id)); - } - - /** - * 删除知识库附件 - * - */ - @PostMapping("attach/remove/{docId}") - public R removeAttach(@NotEmpty(message = "主键不能为空") @PathVariable String docId) { - attachService.removeKnowledgeAttach(docId); - return R.ok(); - } - - - /** - * 查询知识片段 - */ - @GetMapping("/fragment/list/{docId}") - public TableDataInfo fragmentList(KnowledgeFragmentBo bo, PageQuery pageQuery, @PathVariable String docId) { - bo.setDocId(docId); - return fragmentService.queryPageList(bo, pageQuery); - } - -} diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 44530d49..66a63e71 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -25,10 +25,9 @@ spring: master: type: ${spring.datasource.type} driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://43.139.70.230:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true - username: ruoyi-ai - password: ruoyi-ai - + url: jdbc:mysql://43.139.70.230:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true + username: ry-vue + password: xx hikari: # 最大连接池数量 @@ -61,9 +60,6 @@ spring.data: # password: 123456 # 连接超时时间 timeout: 10S - # 是否开启ssl - ssl: false - redisson: # redis key前缀 keyPrefix: @@ -97,3 +93,4 @@ sms: signName: 测试 # 腾讯专用 sdkAppId: + diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 26173564..07508f78 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -49,8 +49,10 @@ server: # 日志配置 logging: level: - org.ruoyi: @logging.level@ + org.dromara: @logging.level@ org.springframework: warn + org.mybatis.spring.mapper: error + org.apache.fury: warn config: classpath:logback-plus.xml # 用户配置 @@ -318,5 +320,19 @@ wechat: token: '' aesKey: '' - +spring: + ai: + openai: + api-key: sk-xxx + base-url: https://api.pandarobot.chat/ + mcp: + client: + enabled: true + name: ruoyi-ai-mcp + sse: + connections: + server: + url: http://127.0.0.1:8081 + stdio: + servers-configuration: classpath:mcp-server.json diff --git a/ruoyi-admin/src/main/resources/mcp-server.json b/ruoyi-admin/src/main/resources/mcp-server.json new file mode 100644 index 00000000..ab8ad189 --- /dev/null +++ b/ruoyi-admin/src/main/resources/mcp-server.json @@ -0,0 +1,22 @@ +{ + "mcpServers": { + "fileSystem": { + "command": "C:\\Program Files\\nodejs\\npx.cmd", + "args": [ + "-y", + "@modelcontextprotocol/server-filesystem", + "D:\\software" + ] + }, + "search1api": { + "command": "C:\\Program Files\\nodejs\\npx.cmd", + "args": [ + "-y", + "search1api-mcp" + ], + "env": { + "SEARCH1API_KEY": "92A3D8F1-9BFA-485A-90E9-7680914CB666" + } + } + } +} diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index f4b1eb80..e617b6dd 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -2,13 +2,20 @@ + 4.0.0 ruoyi-ai org.ruoyi ${revision} ../pom.xml - 4.0.0 + + ruoyi-common + pom + + + common 通用模块 + ruoyi-common-bom @@ -32,16 +39,9 @@ ruoyi-common-encrypt ruoyi-common-tenant ruoyi-common-chat - ruoyi-common-pay ruoyi-common-wechat + ruoyi-common-pay ruoyi-common-live - ruoyi-common - pom - - - common 通用模块 - - diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml index 2adc1ba2..bd1847fd 100644 --- a/ruoyi-common/ruoyi-common-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-bom/pom.xml @@ -159,29 +159,14 @@ ${revision} - - - org.ruoyi - ruoyi-common-wechat - ${revision} - - - org.ruoyi ruoyi-chat ${revision} - - - - - org.ruoyi - ruoyi-common-pay - ${revision} - + diff --git a/ruoyi-common/ruoyi-common-chat/pom.xml b/ruoyi-common/ruoyi-common-chat/pom.xml index b06f19c1..b701e53b 100644 --- a/ruoyi-common/ruoyi-common-chat/pom.xml +++ b/ruoyi-common/ruoyi-common-chat/pom.xml @@ -18,6 +18,10 @@ 2.9.0 + 1.0.0-beta.12 + release-V4-2.3.0 + 2.7.5 + 0.5.0 @@ -26,38 +30,22 @@ ruoyi-common-core + - mysql - mysql-connector-java - 8.0.33 + org.ruoyi + ruoyi-common-json + + + + + org.ruoyi + ruoyi-common-redis com.azure azure-ai-openai - 1.0.0-beta.12 - - - - io.github.ollama4j - ollama4j - 1.0.79 - - - - - org.ruoyi - ruoyi-common-json - 1.0.0 - - - - org.ruoyi - ruoyi-common-redis - - - org.ruoyi - ruoyi-common-satoken + ${azure.version} @@ -79,13 +67,7 @@ com.knuddels jtokkit - 0.5.0 - - - - cn.hutool - hutool-all - 5.8.12 + ${jtokkit.version} @@ -98,21 +80,18 @@ - - junit - junit - cn.bigmodel.openapi oapi-java-sdk - release-V4-2.3.0 + ${chatglm.version} + com.squareup.okhttp okhttp - 2.7.5 - compile + ${okhttp.version} + diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/properties/WebSocketProperties.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/properties/WebSocketProperties.java index eb36cc77..7567fc9e 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/properties/WebSocketProperties.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/properties/WebSocketProperties.java @@ -12,6 +12,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @Data public class WebSocketProperties { + /** + * 是否开启 + */ private Boolean enabled; /** diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV2.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV2.java deleted file mode 100644 index 21bb3d5b..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV2.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.ruoyi.common.chat.demo; - -import cn.hutool.json.JSONUtil; - -import lombok.Getter; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse; - -import java.util.Objects; -import java.util.concurrent.CountDownLatch; - -/** - * 描述: sse - * - * @author https:www.unfbx.com - * 2023-06-15 - */ -@Slf4j -public class ConsoleEventSourceListenerV2 extends EventSourceListener { - @Getter - String args = ""; - final CountDownLatch countDownLatch; - - public ConsoleEventSourceListenerV2(CountDownLatch countDownLatch) { - this.countDownLatch = countDownLatch; - } - - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("OpenAI建立sse连接..."); - } - - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("OpenAI返回数据:{}", data); - if (data.equals("[DONE]")) { - log.info("OpenAI返回数据结束了"); - countDownLatch.countDown(); - return; - } - ChatCompletionResponse chatCompletionResponse = JSONUtil.toBean(data, ChatCompletionResponse.class); - if(Objects.nonNull(chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall())){ - args += chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall().getArguments(); - } - } - - @Override - public void onClosed(EventSource eventSource) { - log.info("OpenAI关闭sse连接..."); - } - - @SneakyThrows - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - if(Objects.isNull(response)){ - log.error("OpenAI sse连接异常:{}", t); - eventSource.cancel(); - return; - } - ResponseBody body = response.body(); - if (Objects.nonNull(body)) { - log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), t); - } else { - log.error("OpenAI sse连接异常data:{},异常:{}", response, t); - } - eventSource.cancel(); - } -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV3.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV3.java deleted file mode 100644 index 22661efe..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV3.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.ruoyi.common.chat.demo; - -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.json.JSONUtil; -import lombok.Getter; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse; -import org.ruoyi.common.chat.entity.chat.Message; -import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction; -import org.ruoyi.common.chat.entity.chat.tool.ToolCalls; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CountDownLatch; - -/** - * 描述: demo测试实现类,仅供思路参考 - * - * @author https:www.unfbx.com - * 2023-11-12 - */ -@Slf4j -public class ConsoleEventSourceListenerV3 extends EventSourceListener { - @Getter - List choices = new ArrayList<>(); - @Getter - ToolCalls toolCalls = new ToolCalls(); - @Getter - ToolCallFunction toolCallFunction = ToolCallFunction.builder().name("").arguments("").build(); - final CountDownLatch countDownLatch; - - public ConsoleEventSourceListenerV3(CountDownLatch countDownLatch) { - this.countDownLatch = countDownLatch; - } - - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("OpenAI建立sse连接..."); - } - - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("OpenAI返回数据:{}", data); - if (data.equals("[DONE]")) { - log.info("OpenAI返回数据结束了"); - return; - } - ChatCompletionResponse chatCompletionResponse = JSONUtil.toBean(data, ChatCompletionResponse.class); - Message delta = chatCompletionResponse.getChoices().get(0).getDelta(); - if (CollectionUtil.isNotEmpty(delta.getToolCalls())) { - choices.addAll(delta.getToolCalls()); - } - } - - @Override - public void onClosed(EventSource eventSource) { - if(CollectionUtil.isNotEmpty(choices)){ - toolCalls.setId(choices.get(0).getId()); - toolCalls.setType(choices.get(0).getType()); - choices.forEach(e -> { - toolCallFunction.setName(e.getFunction().getName()); - toolCallFunction.setArguments(toolCallFunction.getArguments() + e.getFunction().getArguments()); - toolCalls.setFunction(toolCallFunction); - }); - } - log.info("OpenAI关闭sse连接..."); - countDownLatch.countDown(); - } - - @SneakyThrows - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - if(Objects.isNull(response)){ - log.error("OpenAI sse连接异常:{}", t); - eventSource.cancel(); - return; - } - ResponseBody body = response.body(); - if (Objects.nonNull(body)) { - log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), t); - } else { - log.error("OpenAI sse连接异常data:{},异常:{}", response, t); - } - eventSource.cancel(); - } -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/PluginTest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/PluginTest.java deleted file mode 100644 index eeda6d4b..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/PluginTest.java +++ /dev/null @@ -1,417 +0,0 @@ -package org.ruoyi.common.chat.demo; - -import cn.hutool.json.JSONUtil; -import com.alibaba.fastjson.JSONObject; -import lombok.Builder; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import okhttp3.OkHttpClient; -import okhttp3.logging.HttpLoggingInterceptor; -import org.junit.Before; -import org.junit.Test; -import org.ruoyi.common.chat.entity.chat.*; -import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction; -import org.ruoyi.common.chat.entity.chat.tool.ToolCalls; -import org.ruoyi.common.chat.entity.chat.tool.Tools; -import org.ruoyi.common.chat.entity.chat.tool.ToolsFunction; -import org.ruoyi.common.chat.openai.OpenAiClient; -import org.ruoyi.common.chat.openai.OpenAiStreamClient; -import org.ruoyi.common.chat.openai.function.KeyRandomStrategy; -import org.ruoyi.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor; -import org.ruoyi.common.chat.openai.interceptor.OpenAILogger; -import org.ruoyi.common.chat.openai.interceptor.OpenAiResponseInterceptor; -import org.ruoyi.common.chat.openai.plugin.PluginAbstract; -import org.ruoyi.common.chat.plugin.CmdPlugin; -import org.ruoyi.common.chat.plugin.CmdReq; -import org.ruoyi.common.chat.sse.ConsoleEventSourceListener; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * 描述: - * - * @author ageerle@163.com - * date 2025/3/8 - */ -@Slf4j -public class PluginTest { - - private OpenAiClient openAiClient; - private OpenAiStreamClient openAiStreamClient; - - @Before - public void before() { - //可以为null -// Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890)); - HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger()); - //!!!!千万别再生产或者测试环境打开BODY级别日志!!!! - //!!!生产或者测试环境建议设置为这三种级别:NONE,BASIC,HEADERS,!!! - httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS); - OkHttpClient okHttpClient = new OkHttpClient - .Builder() -// .proxy(proxy) - .addInterceptor(httpLoggingInterceptor) - .addInterceptor(new OpenAiResponseInterceptor()) - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .build(); - openAiClient = OpenAiClient.builder() - //支持多key传入,请求时候随机选择 - .apiKey(Arrays.asList("sk-xx")) - //自定义key的获取策略:默认KeyRandomStrategy - //.keyStrategy(new KeyRandomStrategy()) - .keyStrategy(new KeyRandomStrategy()) - .okHttpClient(okHttpClient) - //自己做了代理就传代理地址,没有可不不传,(关注公众号回复:openai ,获取免费的测试代理地址) - .apiHost("https://api.pandarobot.chat/") - .build(); - - openAiStreamClient = OpenAiStreamClient.builder() - //支持多key传入,请求时候随机选择 - .apiKey(Arrays.asList("sk-xx")) - //自定义key的获取策略:默认KeyRandomStrategy - .keyStrategy(new KeyRandomStrategy()) - .authInterceptor(new DynamicKeyOpenAiAuthInterceptor()) - .okHttpClient(okHttpClient) - //自己做了代理就传代理地址,没有可不不传,(关注公众号回复:openai ,获取免费的测试代理地址) - .apiHost("https://api.pandarobot.chat/") - .build(); - } - - - @Test - public void chatFunction() { - //模型:GPT_3_5_TURBO_16K_0613 - Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语,并解释下词语对应物品的用途").build(); - //属性一 - JSONObject wordLength = new JSONObject(); - wordLength.put("type", "number"); - wordLength.put("description", "词语的长度"); - //属性二 - JSONObject language = new JSONObject(); - language.put("type", "string"); - language.put("enum", Arrays.asList("zh", "en")); - language.put("description", "语言类型,例如:zh代表中文、en代表英语"); - //参数 - JSONObject properties = new JSONObject(); - properties.put("wordLength", wordLength); - properties.put("language", language); - - Parameters parameters = Parameters.builder() - .type("object") - .properties(properties) - .required(Collections.singletonList("wordLength")).build(); - Functions functions = Functions.builder() - .name("getOneWord") - .description("获取一个指定长度和语言类型的词语") - .parameters(parameters) - .build(); - - ChatCompletion chatCompletion = ChatCompletion - .builder() - .messages(Collections.singletonList(message)) - .functions(Collections.singletonList(functions)) - .functionCall("auto") - .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName()) - .build(); - ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion); - - ChatChoice chatChoice = chatCompletionResponse.getChoices().get(0); - log.info("构造的方法值:{}", chatChoice.getMessage().getFunctionCall()); - log.info("构造的方法名称:{}", chatChoice.getMessage().getFunctionCall().getName()); - log.info("构造的方法参数:{}", chatChoice.getMessage().getFunctionCall().getArguments()); - WordParam wordParam = JSONUtil.toBean(chatChoice.getMessage().getFunctionCall().getArguments(), WordParam.class); - String oneWord = getOneWord(wordParam); - - FunctionCall functionCall = FunctionCall.builder() - .arguments(chatChoice.getMessage().getFunctionCall().getArguments()) - .name("getOneWord") - .build(); - Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").functionCall(functionCall).build(); - String content - = "{ " + - "\"wordLength\": \"3\", " + - "\"language\": \"zh\", " + - "\"word\": \"" + oneWord + "\"," + - "\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" + - "}"; - Message message3 = Message.builder().role(Message.Role.FUNCTION).name("getOneWord").content(content).build(); - List messageList = Arrays.asList(message, message2, message3); - ChatCompletion chatCompletionV2 = ChatCompletion - .builder() - .messages(messageList) - .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName()) - .build(); - ChatCompletionResponse chatCompletionResponseV2 = openAiClient.chatCompletion(chatCompletionV2); - log.info("自定义的方法返回值:{}",chatCompletionResponseV2.getChoices().get(0).getMessage().getContent()); - } - - - @Test - public void plugin() { - CmdPlugin plugin = new CmdPlugin(CmdReq.class); - // 插件名称 - plugin.setName("命令行工具"); - // 方法名称 - plugin.setFunction("openCmd"); - // 方法说明 - plugin.setDescription("提供一个命令行指令,比如<记事本>,指令使用中文,以function返回结果为准"); - - PluginAbstract.Arg arg = new PluginAbstract.Arg(); - // 参数名称 - arg.setName("cmd"); - // 参数说明 - arg.setDescription("命令行指令"); - // 参数类型 - arg.setType("string"); - arg.setRequired(true); - plugin.setArgs(Collections.singletonList(arg)); - - Message message2 = Message.builder().role(Message.Role.USER).content("帮我打开计算器,结合上下文判断指令是否执行成功,只用回复成功或者失败").build(); - List messages = new ArrayList<>(); - messages.add(message2); - //有四个重载方法,都可以使用 - ChatCompletionResponse response = openAiClient.chatCompletionWithPlugin(messages,"gpt-4o-mini",plugin); - log.info("自定义的方法返回值:{}", response.getChoices().get(0).getMessage().getContent()); - } - - /** - * 自定义返回数据格式 - */ - @Test - public void diyReturnDataModelChat() { - Message message = Message.builder().role(Message.Role.USER).content("随机输出10个单词,使用json输出").build(); - ChatCompletion chatCompletion = ChatCompletion - .builder() - .messages(Collections.singletonList(message)) - .responseFormat(ResponseFormat.builder().type(ResponseFormat.Type.JSON_OBJECT.getName()).build()) - .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName()) - .build(); - ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion); - chatCompletionResponse.getChoices().forEach(e -> System.out.println(e.getMessage())); - } - - @Test - public void streamPlugin() { - WeatherPlugin plugin = new WeatherPlugin(WeatherReq.class); - plugin.setName("知心天气"); - plugin.setFunction("getLocationWeather"); - plugin.setDescription("提供一个地址,方法将会获取该地址的天气的实时温度信息。"); - PluginAbstract.Arg arg = new PluginAbstract.Arg(); - arg.setName("location"); - arg.setDescription("地名"); - arg.setType("string"); - arg.setRequired(true); - plugin.setArgs(Collections.singletonList(arg)); - -// Message message1 = Message.builder().role(Message.Role.USER).content("秦始皇统一了哪六国。").build(); - Message message2 = Message.builder().role(Message.Role.USER).content("获取上海市的天气现在多少度,然后再给出3个推荐的户外运动。").build(); - List messages = new ArrayList<>(); -// messages.add(message1); - messages.add(message2); - //默认模型:GPT_3_5_TURBO_16K_0613 - //有四个重载方法,都可以使用 - openAiStreamClient.streamChatCompletionWithPlugin(messages, ChatCompletion.Model.GPT_4_1106_PREVIEW.getName(), new ConsoleEventSourceListener(), plugin); - CountDownLatch countDownLatch = new CountDownLatch(1); - try { - countDownLatch.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - /** - * tools使用示例 - */ - @Test - public void toolsChat() { - Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语,并解释下词语对应物品的用途").build(); - //属性一 - JSONObject wordLength = new JSONObject(); - wordLength.put("type", "number"); - wordLength.put("description", "词语的长度"); - //属性二 - JSONObject language = new JSONObject(); - language.put("type", "string"); - language.put("enum", Arrays.asList("zh", "en")); - language.put("description", "语言类型,例如:zh代表中文、en代表英语"); - //参数 - JSONObject properties = new JSONObject(); - properties.put("wordLength", wordLength); - properties.put("language", language); - Parameters parameters = Parameters.builder() - .type("object") - .properties(properties) - .required(Collections.singletonList("wordLength")).build(); - Tools tools = Tools.builder() - .type(Tools.Type.FUNCTION.getName()) - .function(ToolsFunction.builder().name("getOneWord").description("获取一个指定长度和语言类型的词语").parameters(parameters).build()) - .build(); - - ChatCompletion chatCompletion = ChatCompletion - .builder() - .messages(Collections.singletonList(message)) - .tools(Collections.singletonList(tools)) - .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName()) - .build(); - ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion); - - ChatChoice chatChoice = chatCompletionResponse.getChoices().get(0); - log.info("构造的方法值:{}", chatChoice.getMessage().getToolCalls()); - - ToolCalls openAiReturnToolCalls = chatChoice.getMessage().getToolCalls().get(0); - WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class); - String oneWord = getOneWord(wordParam); - - - ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build(); - ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build(); - //构造tool call - Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").toolCalls(Collections.singletonList(tc)).build(); - String content - = "{ " + - "\"wordLength\": \"3\", " + - "\"language\": \"zh\", " + - "\"word\": \"" + oneWord + "\"," + - "\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" + - "}"; - Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build(); - List messageList = Arrays.asList(message, message2, message3); - ChatCompletion chatCompletionV2 = ChatCompletion - .builder() - .messages(messageList) - .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName()) - .build(); - ChatCompletionResponse chatCompletionResponseV2 = openAiClient.chatCompletion(chatCompletionV2); - log.info("自定义的方法返回值:{}", chatCompletionResponseV2.getChoices().get(0).getMessage().getContent()); - - } - - /** - * tools流式输出使用示例 - */ - @Test - public void streamToolsChat() { - - CountDownLatch countDownLatch = new CountDownLatch(1); - ConsoleEventSourceListenerV3 eventSourceListener = new ConsoleEventSourceListenerV3(countDownLatch); - - Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语,并解释下词语对应物品的用途").build(); - //属性一 - JSONObject wordLength = new JSONObject(); - wordLength.put("type", "number"); - wordLength.put("description", "词语的长度"); - //属性二 - JSONObject language = new JSONObject(); - language.put("type", "string"); - language.put("enum", Arrays.asList("zh", "en")); - language.put("description", "语言类型,例如:zh代表中文、en代表英语"); - //参数 - JSONObject properties = new JSONObject(); - properties.put("wordLength", wordLength); - properties.put("language", language); - Parameters parameters = Parameters.builder() - .type("object") - .properties(properties) - .required(Collections.singletonList("wordLength")).build(); - Tools tools = Tools.builder() - .type(Tools.Type.FUNCTION.getName()) - .function(ToolsFunction.builder().name("getOneWord").description("获取一个指定长度和语言类型的词语").parameters(parameters).build()) - .build(); - - ChatCompletion chatCompletion = ChatCompletion - .builder() - .messages(Collections.singletonList(message)) - .tools(Collections.singletonList(tools)) - .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName()) - .build(); - openAiStreamClient.streamChatCompletion(chatCompletion, eventSourceListener); - - try { - countDownLatch.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - ToolCalls openAiReturnToolCalls = eventSourceListener.getToolCalls(); - WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class); - String oneWord = getOneWord(wordParam); - - - ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build(); - ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build(); - //构造tool call - Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").toolCalls(Collections.singletonList(tc)).build(); - String content - = "{ " + - "\"wordLength\": \"3\", " + - "\"language\": \"zh\", " + - "\"word\": \"" + oneWord + "\"," + - "\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" + - "}"; - Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build(); - List messageList = Arrays.asList(message, message2, message3); - ChatCompletion chatCompletionV2 = ChatCompletion - .builder() - .messages(messageList) - .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName()) - .build(); - - - CountDownLatch countDownLatch1 = new CountDownLatch(1); - openAiStreamClient.streamChatCompletion(chatCompletionV2, new ConsoleEventSourceListenerV3(countDownLatch)); - try { - countDownLatch1.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - try { - countDownLatch1.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - } - - - @Data - @Builder - static class WordParam { - private int wordLength; - @Builder.Default - private String language = "zh"; - } - - - /** - * 获取一个词语(根据语言和字符长度查询) - * @param wordParam - * @return - */ - public String getOneWord(WordParam wordParam) { - - List zh = Arrays.asList("大香蕉", "哈密瓜", "苹果"); - List en = Arrays.asList("apple", "banana", "cantaloupe"); - if (wordParam.getLanguage().equals("zh")) { - for (String e : zh) { - if (e.length() == wordParam.getWordLength()) { - return e; - } - } - } - if (wordParam.getLanguage().equals("en")) { - for (String e : en) { - if (e.length() == wordParam.getWordLength()) { - return e; - } - } - } - return "西瓜"; - } -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherPlugin.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherPlugin.java deleted file mode 100644 index 787e5983..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherPlugin.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.ruoyi.common.chat.demo; - - -import org.ruoyi.common.chat.openai.plugin.PluginAbstract; - -public class WeatherPlugin extends PluginAbstract { - - public WeatherPlugin(Class r) { - super(r); - } - - @Override - public WeatherResp func(WeatherReq args) { - WeatherResp weatherResp = new WeatherResp(); - weatherResp.setTemp("25到28摄氏度"); - weatherResp.setLevel(3); - return weatherResp; - } - - @Override - public String content(WeatherResp weatherResp) { - return "当前天气温度:" + weatherResp.getTemp() + ",风力等级:" + weatherResp.getLevel(); - } -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherReq.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherReq.java deleted file mode 100644 index b0670e5b..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherReq.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.ruoyi.common.chat.demo; - - -import lombok.Data; -import org.ruoyi.common.chat.openai.plugin.PluginParam; - -@Data -public class WeatherReq extends PluginParam { - /** - * 城市 - */ - private String location; -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherResp.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherResp.java deleted file mode 100644 index 360c56c8..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherResp.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.ruoyi.common.chat.demo; - -import lombok.Data; - -@Data -public class WeatherResp { - /** - * 温度 - */ - private String temp; - /** - * 风力等级 - */ - private Integer level; -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WebSearchToolsTest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WebSearchToolsTest.java deleted file mode 100644 index 34871f5c..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WebSearchToolsTest.java +++ /dev/null @@ -1,223 +0,0 @@ -package org.ruoyi.common.chat.demo; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.zhipu.oapi.ClientV4; -import com.zhipu.oapi.Constants; -import com.zhipu.oapi.service.v4.tools.*; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - - -import com.zhipu.oapi.service.v4.model.*; -import io.reactivex.Flowable; - -import java.util.HashMap; -import java.util.Map; - - -public class WebSearchToolsTest { - - private final static Logger logger = LoggerFactory.getLogger(WebSearchToolsTest.class); - private static final String API_SECRET_KEY = "xx"; - - private static final ClientV4 client = new ClientV4.Builder(API_SECRET_KEY) - .networkConfig(300, 100, 100, 100, TimeUnit.SECONDS) - .connectionPool(new okhttp3.ConnectionPool(8, 1, TimeUnit.SECONDS)) - .build(); - private static final ObjectMapper mapper = new ObjectMapper(); - // 请自定义自己的业务id - private static final String requestIdTemplate = "mycompany-%d"; - - - @Test - public void test1() throws JsonProcessingException { - -// json 转换 ArrayList - String jsonString = "[\n" + - " {\n" + - " \"content\": \"今天武汉天气怎么样\",\n" + - " \"role\": \"user\"\n" + - " }\n" + - " ]"; - - ArrayList messages = new ObjectMapper().readValue(jsonString, new TypeReference>() { - }); - - - String requestId = String.format(requestIdTemplate, System.currentTimeMillis()); - WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder() - .model("web-search-pro") - .stream(Boolean.TRUE) - .messages(messages) - .requestId(requestId) - .build(); - WebSearchApiResponse webSearchApiResponse = client.webSearchProStreamingInvoke(chatCompletionRequest); - if (webSearchApiResponse.isSuccess()) { - AtomicBoolean isFirst = new AtomicBoolean(true); - List choices = new ArrayList<>(); - AtomicReference lastAccumulator = new AtomicReference<>(); - - webSearchApiResponse.getFlowable().map(result -> result) - .doOnNext(accumulator -> { - { - if (isFirst.getAndSet(false)) { - logger.info("Response: "); - } - ChoiceDelta delta = accumulator.getChoices().get(0).getDelta(); - if (delta != null && delta.getToolCalls() != null) { - logger.info("tool_calls: {}", mapper.writeValueAsString(delta.getToolCalls())); - } - choices.add(delta); - lastAccumulator.set(accumulator); - - } - }) - .doOnComplete(() -> System.out.println("Stream completed.")) - .doOnError(throwable -> System.err.println("Error: " + throwable)) // Handle errors - .blockingSubscribe();// Use blockingSubscribe instead of blockingGet() - - WebSearchPro chatMessageAccumulator = lastAccumulator.get(); - - webSearchApiResponse.setFlowable(null);// 打印前置空 - webSearchApiResponse.setData(chatMessageAccumulator); - } - logger.info("model output: {}", mapper.writeValueAsString(webSearchApiResponse)); - client.getConfig().getHttpClient().dispatcher().executorService().shutdown(); - - client.getConfig().getHttpClient().connectionPool().evictAll(); - // List all active threads - for (Thread t : Thread.getAllStackTraces().keySet()) { - logger.info("Thread: " + t.getName() + " State: " + t.getState()); - } - - } - - - @Test - public void test2() throws JsonProcessingException { - -// json 转换 ArrayList - String jsonString = "[\n" + - " {\n" + - " \"content\": \"今天天气怎么样\",\n" + - " \"role\": \"user\"\n" + - " }\n" + - " ]"; - - ArrayList messages = new ObjectMapper().readValue(jsonString, new TypeReference>() { - }); - - - String requestId = String.format(requestIdTemplate, System.currentTimeMillis()); - WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder() - .model("web-search-pro") - .stream(Boolean.FALSE) - .messages(messages) - .requestId(requestId) - .build(); - WebSearchApiResponse webSearchApiResponse = client.invokeWebSearchPro(chatCompletionRequest); - - logger.info("model output: {}", mapper.writeValueAsString(webSearchApiResponse)); - - } - - - @Test - public void testFunctionSSE() throws JsonProcessingException { - List messages = new ArrayList<>(); - ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "成都到北京要多久,天气如何"); - messages.add(chatMessage); - String requestId = String.format(requestIdTemplate, System.currentTimeMillis()); - // 函数调用参数构建部分 - List chatToolList = new ArrayList<>(); - ChatTool chatTool = new ChatTool(); - - chatTool.setType(ChatToolType.FUNCTION.value()); - ChatFunctionParameters chatFunctionParameters = new ChatFunctionParameters(); - chatFunctionParameters.setType("object"); - Map properties = new HashMap<>(); - properties.put("location", new HashMap() {{ - put("type", "string"); - put("description", "城市,如:北京"); - }}); - properties.put("unit", new HashMap() {{ - put("type", "string"); - put("enum", new ArrayList() {{ - add("celsius"); - add("fahrenheit"); - }}); - }}); - chatFunctionParameters.setProperties(properties); - ChatFunction chatFunction = ChatFunction.builder() - .name("get_weather") - .description("Get the current weather of a location") - .parameters(chatFunctionParameters) - .build(); - chatTool.setFunction(chatFunction); - chatToolList.add(chatTool); - HashMap extraJson = new HashMap<>(); - extraJson.put("temperature", 0.5); - extraJson.put("max_tokens", 50); - - ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() - .model(Constants.ModelChatGLM4) - .stream(Boolean.TRUE) - .messages(messages) - .requestId(requestId) - .tools(chatToolList) - .toolChoice("auto") - .extraJson(extraJson) - .build(); - ModelApiResponse sseModelApiResp = client.invokeModelApi(chatCompletionRequest); - if (sseModelApiResp.isSuccess()) { - AtomicBoolean isFirst = new AtomicBoolean(true); - List choices = new ArrayList<>(); - ChatMessageAccumulator chatMessageAccumulator = mapStreamToAccumulator(sseModelApiResp.getFlowable()) - .doOnNext(accumulator -> { - { - if (isFirst.getAndSet(false)) { - logger.info("Response: "); - } - if (accumulator.getDelta() != null && accumulator.getDelta().getTool_calls() != null) { - String jsonString = mapper.writeValueAsString(accumulator.getDelta().getTool_calls()); - logger.info("tool_calls: {}", jsonString); - } - if (accumulator.getDelta() != null && accumulator.getDelta().getContent() != null) { - logger.info(accumulator.getDelta().getContent()); - } - choices.add(accumulator.getChoice()); - } - }) - .doOnComplete(System.out::println) - .lastElement() - .blockingGet(); - - - ModelData data = new ModelData(); - data.setChoices(choices); - data.setUsage(chatMessageAccumulator.getUsage()); - data.setId(chatMessageAccumulator.getId()); - data.setCreated(chatMessageAccumulator.getCreated()); - data.setRequestId(chatCompletionRequest.getRequestId()); - sseModelApiResp.setFlowable(null);// 打印前置空 - sseModelApiResp.setData(data); - } - logger.info("model output: {}", mapper.writeValueAsString(sseModelApiResp)); - } - - public static Flowable mapStreamToAccumulator(Flowable flowable) { - return flowable.map(chunk -> { - return new ChatMessageAccumulator(chunk.getChoices().get(0).getDelta(), null, chunk.getChoices().get(0), chunk.getUsage(), chunk.getCreated(), chunk.getId()); - }); - } - -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/ChatRequest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/ChatRequest.java deleted file mode 100644 index 65f76fff..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/ChatRequest.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.ruoyi.common.chat.domain.request; - -import org.ruoyi.common.chat.entity.chat.Message; -import jakarta.validation.constraints.NotEmpty; -import lombok.Data; - -import java.util.List; - -/** - * 描述: - * - * @author https:www.unfbx.com - * @sine 2023-04-08 - */ -@Data -public class ChatRequest { - - - private String frequency_penalty; - - private String max_tokens; - - @NotEmpty(message = "对话消息不能为空") - List messages; - - @NotEmpty(message = "传入的模型不能为空") - private String model; - - private String presence_penalty; - - private String stream; - - private double temperature; - - private double top_p = 1; - - /** - * 知识库id - */ - private String kid; - - private String userId; - - /** - * 1 联网搜索 - */ - private int chat_type; - - /** - * 应用ID - */ - private String appId; -// - -// -// /** -// * gpt的默认设置 -// */ -// private String systemMessage = ""; -// -// -// -// private double temperature = 0.2; -// -// /** -// * 上下文的条数 -// */ -// private Integer contentNumber = 10; -// -// /** -// * 是否携带上下文 -// */ -// private Boolean usingContext = Boolean.TRUE; - -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/Dall3Request.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/Dall3Request.java deleted file mode 100644 index df220566..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/Dall3Request.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.ruoyi.common.chat.domain.request; - -import jakarta.validation.constraints.NotEmpty; -import lombok.Data; - -/** - * 描述: - * - * @author https:www.unfbx.com - * @sine 2023-04-08 - */ -@Data -public class Dall3Request { - - @NotEmpty(message = "传入的模型不能为空") - private String model; - - @NotEmpty(message = "提示词不能为空") - private String prompt; - - /** 图片大小 */ - @NotEmpty(message = "图片大小不能为空") - private String size ; - - /** 图片质量 */ - @NotEmpty(message = "图片质量不能为空") - private String quality; - - /** 图片风格 */ - @NotEmpty(message = "图片风格不能为空") - private String style; - -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/handler/PlusWebSocketHandler.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/handler/PlusWebSocketHandler.java index 00de76e7..5f94b47b 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/handler/PlusWebSocketHandler.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/handler/PlusWebSocketHandler.java @@ -2,7 +2,6 @@ package org.ruoyi.common.chat.handler; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; -import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.ruoyi.common.chat.config.LocalCache; import org.ruoyi.common.chat.entity.chat.ChatCompletion; @@ -12,7 +11,6 @@ import org.ruoyi.common.chat.listener.WebSocketEventListener; import org.ruoyi.common.chat.openai.OpenAiStreamClient; import org.ruoyi.common.chat.utils.WebSocketUtils; import org.ruoyi.common.core.utils.SpringUtils; -import org.springframework.beans.factory.annotation.Value; import org.springframework.web.socket.*; import org.springframework.web.socket.handler.AbstractWebSocketHandler; diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/localModels/LocalModelsofitClient.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/localModels/LocalModelsofitClient.java deleted file mode 100644 index 606a7c25..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/localModels/LocalModelsofitClient.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.ruoyi.common.chat.localModels; - -import io.micrometer.common.util.StringUtils; -import lombok.extern.slf4j.Slf4j; -import okhttp3.OkHttpClient; -import org.ruoyi.common.chat.entity.models.LocalModelsSearchRequest; -import org.ruoyi.common.chat.entity.models.LocalModelsSearchResponse; -import org.springframework.stereotype.Service; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.jackson.JacksonConverterFactory; - -import java.util.List; -import java.util.concurrent.CountDownLatch; - -@Slf4j -@Service -public class LocalModelsofitClient { - private static final String BASE_URL = "http://127.0.0.1:5000"; // Flask 服务的 URL - private static Retrofit retrofit = null; - - // 获取 Retrofit 实例 - public static Retrofit getRetrofitInstance() { - if (retrofit == null) { - OkHttpClient client = new OkHttpClient.Builder() - .build(); - - retrofit = new Retrofit.Builder() - .baseUrl(BASE_URL) - .client(client) - .addConverterFactory(JacksonConverterFactory.create()) // 使用 Jackson 处理 JSON 转换 - .build(); - } - return retrofit; - } - - /** - * 向 Flask 服务发送文本向量化请求 - * - * @param queries 查询文本列表 - * @param modelName 模型名称 - * @param delimiter 文本分隔符 - * @param topK 返回的结果数 - * @param blockSize 文本块大小 - * @param overlapChars 重叠字符数 - * @return 返回计算得到的 Top K 嵌入向量列表 - */ - - public static List> getTopKEmbeddings( - List queries, - String modelName, - String delimiter, - int topK, - int blockSize, - int overlapChars) { - - modelName = (!StringUtils.isEmpty(modelName)) ? modelName : "msmarco-distilbert-base-tas-b"; // 默认模型名称 - delimiter = (!StringUtils.isEmpty(delimiter) ) ? delimiter : "."; // 默认分隔符 - topK = (topK > 0) ? topK : 3; // 默认返回 3 个结果 - blockSize = (blockSize > 0) ? blockSize : 500; // 默认文本块大小为 500 - overlapChars = (overlapChars > 0) ? overlapChars : 50; // 默认重叠字符数为 50 - - // 创建 Retrofit 实例 - Retrofit retrofit = getRetrofitInstance(); - - // 创建 SearchService 接口 - SearchService service = retrofit.create(SearchService.class); - - // 创建请求对象 LocalModelsSearchRequest - LocalModelsSearchRequest request = new LocalModelsSearchRequest( - queries, // 查询文本列表 - modelName, // 模型名称 - delimiter, // 文本分隔符 - topK, // 返回的结果数 - blockSize, // 文本块大小 - overlapChars // 重叠字符数 - ); - - final CountDownLatch latch = new CountDownLatch(1); // 创建一个 CountDownLatch - final List>[] topKEmbeddings = new List[]{null}; // 使用数组来存储结果(因为 Java 不支持直接修改 List) - - // 发起异步请求 - service.vectorize(request).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - LocalModelsSearchResponse searchResponse = response.body(); - if (searchResponse != null) { - topKEmbeddings[0] = searchResponse.getTopKEmbeddings().get(0); // 获取结果 - log.info("Successfully retrieved embeddings"); - } else { - log.error("Response body is null"); - } - } else { - log.error("Request failed. HTTP error code: " + response.code()); - } - latch.countDown(); // 请求完成,减少计数 - } - - @Override - public void onFailure(Call call, Throwable t) { - t.printStackTrace(); - log.error("Request failed: ", t); - latch.countDown(); // 请求失败,减少计数 - } - }); - - try { - latch.await(); // 等待请求完成 - } catch (InterruptedException e) { - e.printStackTrace(); - } - - return topKEmbeddings[0]; // 返回结果 - } - -// public static void main(String[] args) { -// // 示例调用 -// List queries = Arrays.asList("What is artificial intelligence?", "AI is transforming industries."); -// String modelName = "msmarco-distilbert-base-tas-b"; -// String delimiter = "."; -// int topK = 3; -// int blockSize = 500; -// int overlapChars = 50; -// -// List> topKEmbeddings = getTopKEmbeddings(queries, modelName, delimiter, topK, blockSize, overlapChars); -// -// // 打印结果 -// if (topKEmbeddings != null) { -// System.out.println("Top K embeddings: "); -// for (List embedding : topKEmbeddings) { -// System.out.println(embedding); -// } -// } else { -// System.out.println("No embeddings returned."); -// } -// } - - -// public static void main(String[] args) { -// // 创建 Retrofit 实例 -// Retrofit retrofit = LocalModelsofitClient.getRetrofitInstance(); -// -// // 创建 SearchService 接口 -// SearchService service = retrofit.create(SearchService.class); -// -// // 创建请求对象 LocalModelsSearchRequest -// LocalModelsSearchRequest request = new LocalModelsSearchRequest( -// Arrays.asList("What is artificial intelligence?", "AI is transforming industries."), // 查询文本列表 -// "msmarco-distilbert-base-tas-b", // 模型名称 -// ".", // 分隔符 -// 3, // 返回的结果数 -// 500, // 文本块大小 -// 50 // 重叠字符数 -// ); -// -// // 发起请求 -// service.vectorize(request).enqueue(new Callback() { -// @Override -// public void onResponse(Call call, Response response) { -// if (response.isSuccessful()) { -// LocalModelsSearchResponse searchResponse = response.body(); -// System.out.println("Response Body: " + response.body()); // Print the whole response body for debugging -// -// if (searchResponse != null) { -// // If the response is not null, process it. -// // Example: Extract the embeddings and print them -// List>> topKEmbeddings = searchResponse.getTopKEmbeddings(); -// if (topKEmbeddings != null) { -// // Print the Top K embeddings -// -// } else { -// System.err.println("Top K embeddings are null"); -// } -// -// // If there is more information you want to process, handle it here -// -// } else { -// System.err.println("Response body is null"); -// } -// } else { -// System.err.println("Request failed. HTTP error code: " + response.code()); -// log.error("Failed to retrieve data. HTTP error code: " + response.code()); -// } -// } -// -// @Override -// public void onFailure(Call call, Throwable t) { -// // 请求失败,打印错误 -// t.printStackTrace(); -// log.error("Request failed: ", t); -// } -// }); -// } - -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/localModels/SearchService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/localModels/SearchService.java deleted file mode 100644 index 3fa131e5..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/localModels/SearchService.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.ruoyi.common.chat.localModels; - - - -import org.ruoyi.common.chat.entity.models.LocalModelsSearchRequest; -import org.ruoyi.common.chat.entity.models.LocalModelsSearchResponse; -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.POST; -/** - * @program: RUOYIAI - * @ClassName SearchService - * @description: 请求模型 - * @author: hejh - * @create: 2025-03-15 17:27 - * @Version 1.0 - **/ - - -public interface SearchService { - @POST("/vectorize") // 与 Flask 服务中的路由匹配 - Call vectorize(@Body LocalModelsSearchRequest request); -} - - diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java index e7992022..ef03cb24 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java @@ -466,8 +466,8 @@ public class OpenAiStreamClient { * @since 1.1.3 */ public ResponseBody textToSpeech(TextToSpeech textToSpeech){ - Call responseBody = this.openAiApi.textToSpeech(textToSpeech); try { + Call responseBody = this.openAiApi.textToSpeech(textToSpeech); return responseBody.execute().body(); } catch (IOException e) { throw new BaseException("文本转语音(同步)失败: "+e.getMessage()); @@ -593,11 +593,6 @@ public class OpenAiStreamClient { } - - - - - /** * 构造 * diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdPlugin.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdPlugin.java deleted file mode 100644 index 428d6e0e..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdPlugin.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.ruoyi.common.chat.plugin; - -import org.ruoyi.common.chat.openai.plugin.PluginAbstract; - -import java.io.IOException; - -public class CmdPlugin extends PluginAbstract { - - public CmdPlugin(Class r) { - super(r); - } - - @Override - public CmdResp func(CmdReq args) { - try { - if("计算器".equals(args.getCmd())){ - Runtime.getRuntime().exec("calc"); - }else if("记事本".equals(args.getCmd())){ - Runtime.getRuntime().exec("notepad"); - }else if("命令行".equals(args.getCmd())){ - String [] cmd={"cmd","/C","start copy exel exe2"}; - Runtime.getRuntime().exec(cmd); - } - } catch (IOException e) { - throw new RuntimeException("指令执行失败"); - } - CmdResp resp = new CmdResp(); - resp.setResult(args.getCmd()+"指令执行成功!"); - return resp; - } - - @Override - public String content(CmdResp resp) { - return resp.getResult(); - } -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java index 3b0af00a..0172fa53 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java @@ -2,31 +2,49 @@ package org.ruoyi.common.chat.request; import jakarta.validation.constraints.NotEmpty; import lombok.Data; -import org.ruoyi.common.chat.entity.chat.Content; import org.ruoyi.common.chat.entity.chat.Message; import java.util.List; /** - * 描述: + * 描述:对话请求对象 * - * @author https:www.unfbx.com + * @author ageerle * @sine 2023-04-08 */ @Data public class ChatRequest { - @NotEmpty(message = "传入的模型不能为空") - private String model; - @NotEmpty(message = "对话消息不能为空") List messages; - List imageContent; + @NotEmpty(message = "传入的模型不能为空") + private String model; + /** + * 提示词 + */ private String prompt; - private String userId; + /** + * 系统提示词 + */ + private String sysPrompt; + + /** + * 是否开启流式对话 + */ + private Boolean stream = Boolean.TRUE; + + /** + * 是否开启联网搜索(0关闭 1开启) + */ + private Boolean search = Boolean.FALSE; + + /** + * 是否开启mcp + */ + private Boolean isMcp = Boolean.FALSE; /** * 知识库id @@ -34,13 +52,14 @@ public class ChatRequest { private String kid; /** - * gpt的默认设置 + * 用户id */ - private String systemMessage = ""; + private Long userId; - private double top_p = 1; - - private double temperature = 0.2; + /** + * 应用ID + */ + private String appId; /** * 上下文的条数 @@ -52,4 +71,5 @@ public class ChatRequest { */ private Boolean usingContext = Boolean.TRUE; + } diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/ConsoleEventSourceListener.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/ConsoleEventSourceListener.java index c05fa19f..e02b2655 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/ConsoleEventSourceListener.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/ConsoleEventSourceListener.java @@ -28,7 +28,6 @@ public class ConsoleEventSourceListener extends EventSourceListener { log.info("OpenAI返回数据:{}", data); if ("[DONE]".equals(data)) { log.info("OpenAI返回数据结束了"); - return; } } diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/PluginListener.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/PluginListener.java index 6701a251..8b9d4a4f 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/PluginListener.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/PluginListener.java @@ -8,7 +8,6 @@ import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import org.jetbrains.annotations.NotNull; -import org.ruoyi.common.chat.constant.OpenAIConst; import org.ruoyi.common.chat.entity.chat.ChatCompletion; import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse; import org.ruoyi.common.chat.entity.chat.FunctionCall; diff --git a/ruoyi-common/ruoyi-common-core/pom.xml b/ruoyi-common/ruoyi-common-core/pom.xml index 36bd0d8e..52934671 100644 --- a/ruoyi-common/ruoyi-common-core/pom.xml +++ b/ruoyi-common/ruoyi-common-core/pom.xml @@ -47,25 +47,11 @@ jakarta.servlet-api + cn.hutool - hutool-core - - - - cn.hutool - hutool-http - - - - cn.hutool - hutool-extra - - - - cn.hutool - hutool-json - provided + hutool-all + ${hutool.version} @@ -73,18 +59,6 @@ lombok - - com.github.binarywang - weixin-java-cp - ${weixin-java-miniapp.version} - - - - com.github.binarywang - weixin-java-cp - ${weixin-java-cp.version} - - org.springframework.boot @@ -108,6 +82,11 @@ ip2region + + com.github.binarywang + weixin-java-cp + ${weixin-java-cp.version} + com.github.binarywang @@ -130,16 +109,19 @@ com.squareup.okhttp3 okhttp + ${okhttp.version} com.squareup.okhttp3 okhttp-sse + ${okhttp.version} com.squareup.okhttp3 logging-interceptor + ${okhttp.version} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/RegexConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/RegexConstants.java new file mode 100644 index 00000000..0e9abd8a --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/RegexConstants.java @@ -0,0 +1,59 @@ +package org.ruoyi.common.core.constant; + +import cn.hutool.core.lang.RegexPool; + +/** + * 常用正则表达式字符串 + *

+ * 常用正则表达式集合,更多正则见: https://any86.github.io/any-rule/ + * + * @author Feng + */ +public interface RegexConstants extends RegexPool { + + /** + * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线) + */ + String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$"; + + /** + * 权限标识必须符合以下格式: + * 1. 标准格式:xxx:yyy:zzz + * - 第一部分(xxx):只能包含字母、数字和下划线(_),不能使用 `*` + * - 第二部分(yyy):可以包含字母、数字、下划线(_)和 `*` + * - 第三部分(zzz):可以包含字母、数字、下划线(_)和 `*` + * 2. 允许空字符串(""),表示没有权限标识 + */ + String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$"; + + /** + * 身份证号码(后6位) + */ + String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$"; + + /** + * QQ号码 + */ + String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$"; + + /** + * 邮政编码 + */ + String POSTAL_CODE = "^[1-9]\\d{5}$"; + + /** + * 注册账号 + */ + String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$"; + + /** + * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符 + */ + String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$"; + + /** + * 通用状态(0表示正常,1表示停用) + */ + String STATUS = "^[01]$"; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/SystemConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/SystemConstants.java new file mode 100644 index 00000000..adde6d58 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/SystemConstants.java @@ -0,0 +1,80 @@ +package org.ruoyi.common.core.constant; + +/** + * 系统常量信息 + * + * @author Lion Li + */ +public interface SystemConstants { + + /** + * 正常状态 + */ + String NORMAL = "0"; + + /** + * 异常状态 + */ + String DISABLE = "1"; + + /** + * 是否为系统默认(是) + */ + String YES = "Y"; + + /** + * 是否为系统默认(否) + */ + String NO = "N"; + + /** + * 是否菜单外链(是) + */ + String YES_FRAME = "0"; + + /** + * 是否菜单外链(否) + */ + String NO_FRAME = "1"; + + /** + * 菜单类型(目录) + */ + String TYPE_DIR = "M"; + + /** + * 菜单类型(菜单) + */ + String TYPE_MENU = "C"; + + /** + * 菜单类型(按钮) + */ + String TYPE_BUTTON = "F"; + + /** + * Layout组件标识 + */ + String LAYOUT = "Layout"; + + /** + * ParentView组件标识 + */ + String PARENT_VIEW = "ParentView"; + + /** + * InnerLink组件标识 + */ + String INNER_LINK = "InnerLink"; + + /** + * 超级管理员ID + */ + Long SUPER_ADMIN_ID = 1L; + + /** + * 根部门祖级列表 + */ + String ROOT_DEPT_ANCESTORS = "0"; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/factory/RegexPatternPoolFactory.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/factory/RegexPatternPoolFactory.java new file mode 100644 index 00000000..c15bdbac --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/factory/RegexPatternPoolFactory.java @@ -0,0 +1,53 @@ +package org.ruoyi.common.core.factory; + +import cn.hutool.core.lang.PatternPool; +import org.ruoyi.common.core.constant.RegexConstants; + + +import java.util.regex.Pattern; + +/** + * 正则表达式模式池工厂 + *

初始化的时候将正则表达式加入缓存池当中

+ *

提高正则表达式的性能,避免重复编译相同的正则表达式

+ * + * @author 21001 + */ +public class RegexPatternPoolFactory extends PatternPool { + + /** + * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线) + */ + public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE); + + /** + * 身份证号码(后6位) + */ + public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6); + + /** + * QQ号码 + */ + public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER); + + /** + * 邮政编码 + */ + public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE); + + /** + * 注册账号 + */ + public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT); + + /** + * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符 + */ + public static final Pattern PASSWORD = get(RegexConstants.PASSWORD); + + /** + * 通用状态(0表示正常,1表示停用) + */ + public static final Pattern STATUS = get(RegexConstants.STATUS); + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/factory/YmlPropertySourceFactory.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/factory/YmlPropertySourceFactory.java new file mode 100644 index 00000000..3b356d82 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/factory/YmlPropertySourceFactory.java @@ -0,0 +1,32 @@ +package org.ruoyi.common.core.factory; + + +import org.ruoyi.common.core.utils.StringUtils; +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.support.DefaultPropertySourceFactory; +import org.springframework.core.io.support.EncodedResource; + +import java.io.IOException; + +/** + * yml 配置源工厂 + * + * @author Lion Li + */ +public class YmlPropertySourceFactory extends DefaultPropertySourceFactory { + + @Override + public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { + String sourceName = resource.getResource().getFilename(); + if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(resource.getResource()); + factory.afterPropertiesSet(); + return new PropertiesPropertySource(sourceName, factory.getObject()); + } + return super.createPropertySource(name, resource); + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ObjectUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ObjectUtils.java new file mode 100644 index 00000000..7f7d5910 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ObjectUtils.java @@ -0,0 +1,60 @@ +package org.ruoyi.common.core.utils; + +import cn.hutool.core.util.ObjectUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.function.Function; + +/** + * 对象工具类 + * + * @author 秋辞未寒 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ObjectUtils extends ObjectUtil { + + /** + * 如果对象不为空,则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName); + * + * @param obj 对象 + * @param func 获取方法 + * @return 对象字段 + */ + public static E notNullGetter(T obj, Function func) { + if (isNotNull(obj) && isNotNull(func)) { + return func.apply(obj); + } + return null; + } + + /** + * 如果对象不为空,则获取对象中的某个字段,否则返回默认值 + * + * @param obj 对象 + * @param func 获取方法 + * @param defaultValue 默认值 + * @return 对象字段 + */ + public static E notNullGetter(T obj, Function func, E defaultValue) { + if (isNotNull(obj) && isNotNull(func)) { + return func.apply(obj); + } + return defaultValue; + } + + /** + * 如果值不为空,则返回值,否则返回默认值 + * + * @param obj 对象 + * @param defaultValue 默认值 + * @return 对象字段 + */ + public static T notNull(T obj, T defaultValue) { + if (isNotNull(obj)) { + return obj; + } + return defaultValue; + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/SpringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/SpringUtils.java index 99937c79..bd5dc544 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/SpringUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/SpringUtils.java @@ -1,9 +1,10 @@ package org.ruoyi.common.core.utils; import cn.hutool.extra.spring.SpringUtil; -import org.springframework.aop.framework.AopContext; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.context.ApplicationContext; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; /** @@ -48,7 +49,7 @@ public final class SpringUtils extends SpringUtil { */ @SuppressWarnings("unchecked") public static T getAopProxy(T invoker) { - return (T) AopContext.currentProxy(); + return (T) getBean(invoker.getClass()); } @@ -59,4 +60,8 @@ public final class SpringUtils extends SpringUtil { return getApplicationContext(); } + public static boolean isVirtual() { + return Threading.VIRTUAL.isActive(getBean(Environment.class)); + } + } diff --git a/ruoyi-common/ruoyi-common-encrypt/pom.xml b/ruoyi-common/ruoyi-common-encrypt/pom.xml index da85259e..28608bd5 100644 --- a/ruoyi-common/ruoyi-common-encrypt/pom.xml +++ b/ruoyi-common/ruoyi-common-encrypt/pom.xml @@ -23,11 +23,6 @@ ruoyi-common-core
- - org.mybatis.spring.boot - mybatis-spring-boot-starter - - org.bouncycastle bcprov-jdk15to18 @@ -38,6 +33,23 @@ hutool-crypto + + org.springframework + spring-webmvc + + + + com.baomidou + mybatis-plus-spring-boot3-starter + true + + + org.mybatis + mybatis-spring + + + + diff --git a/ruoyi-common/ruoyi-common-live/pom.xml b/ruoyi-common/ruoyi-common-live/pom.xml index 9e6fe9b8..26082fcd 100644 --- a/ruoyi-common/ruoyi-common-live/pom.xml +++ b/ruoyi-common/ruoyi-common-live/pom.xml @@ -1,17 +1,19 @@ + 4.0.0 org.ruoyi ruoyi-common ${revision} ../pom.xml + pom - 4.0.0 + ruoyi-common-live - 弹幕监听 + AI直播 @@ -31,7 +33,7 @@ 1.13.0 2.16.0 - 5.8.24 + 5.8.35 4.1.104.Final 1.4.12 1.18.30 diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/MailConfig.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/MailConfig.java index acd82278..28001d16 100644 --- a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/MailConfig.java +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/MailConfig.java @@ -7,8 +7,7 @@ import org.ruoyi.common.core.service.ConfigService; import org.ruoyi.common.mail.utils.MailAccount; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Scope; -import org.springframework.scheduling.annotation.Scheduled; + /** * JavaMail 配置 @@ -22,10 +21,9 @@ import org.springframework.scheduling.annotation.Scheduled; public class MailConfig { private final ConfigService configService; - private MailAccount account; // 缓存MailAccount实例 + private MailAccount account; @Bean - @Scope("singleton") public MailAccount mailAccount() { if (account == null) { account = new MailAccount(); @@ -34,7 +32,6 @@ public class MailConfig { return account; } - @Scheduled(fixedDelay = 10000) // 每10秒检查一次 public void updateMailAccount() { account.setHost(getKey("host")); account.setPort(NumberUtils.toInt(getKey("port"), 465)); diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/properties/MailProperties.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/properties/MailProperties.java similarity index 95% rename from ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/properties/MailProperties.java rename to ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/properties/MailProperties.java index 246172bb..e2a9e1db 100644 --- a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/properties/MailProperties.java +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/properties/MailProperties.java @@ -1,4 +1,4 @@ -package org.ruoyi.common.mail.config.properties; +package org.ruoyi.common.mail.properties; import lombok.Data; diff --git a/ruoyi-common/ruoyi-common-mybatis/pom.xml b/ruoyi-common/ruoyi-common-mybatis/pom.xml index 0f6505e0..138732c5 100644 --- a/ruoyi-common/ruoyi-common-mybatis/pom.xml +++ b/ruoyi-common/ruoyi-common-mybatis/pom.xml @@ -6,7 +6,6 @@ org.ruoyi ruoyi-common ${revision} - ../pom.xml 4.0.0 @@ -30,12 +29,17 @@ com.baomidou - dynamic-datasource-spring-boot-starter + dynamic-datasource-spring-boot3-starter com.baomidou - mybatis-plus-boot-starter + mybatis-plus-spring-boot3-starter + + + + com.baomidou + mybatis-plus-jsqlparser diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/baomidou/dynamic/datasource/processor/jakarta/DsJakartaHeaderProcessor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/baomidou/dynamic/datasource/processor/jakarta/DsJakartaHeaderProcessor.java deleted file mode 100644 index f0a50a2d..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/baomidou/dynamic/datasource/processor/jakarta/DsJakartaHeaderProcessor.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright © 2018 organization baomidou - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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 com.baomidou.dynamic.datasource.processor.jakarta; - -import com.baomidou.dynamic.datasource.processor.DsProcessor; -import jakarta.servlet.http.HttpServletRequest; -import org.aopalliance.intercept.MethodInvocation; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -/** - * @author TaoYu - * @since 3.6.0 - */ -public class DsJakartaHeaderProcessor extends DsProcessor { - - /** - * header prefix - */ - private static final String HEADER_PREFIX = "#header"; - - @Override - public boolean matches(String key) { - return key.startsWith(HEADER_PREFIX); - } - - @Override - public String doDetermineDatasource(MethodInvocation invocation, String key) { - HttpServletRequest request = (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); - return request.getHeader(key.substring(8)); - } -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/baomidou/dynamic/datasource/processor/jakarta/DsJakartaSessionProcessor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/baomidou/dynamic/datasource/processor/jakarta/DsJakartaSessionProcessor.java deleted file mode 100644 index 0ea8a130..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/baomidou/dynamic/datasource/processor/jakarta/DsJakartaSessionProcessor.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright © 2018 organization baomidou - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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 com.baomidou.dynamic.datasource.processor.jakarta; - -import com.baomidou.dynamic.datasource.processor.DsProcessor; -import jakarta.servlet.http.HttpServletRequest; -import org.aopalliance.intercept.MethodInvocation; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - - -/** - * @author TaoYu - * @since 3.6.0 - */ -public class DsJakartaSessionProcessor extends DsProcessor { - - /** - * session开头 - */ - private static final String SESSION_PREFIX = "#session"; - - @Override - public boolean matches(String key) { - return key.startsWith(SESSION_PREFIX); - } - - @Override - public String doDetermineDatasource(MethodInvocation invocation, String key) { - HttpServletRequest request = (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); - return request.getSession().getAttribute(key.substring(9)).toString(); - } -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/annotation/DataColumn.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/annotation/DataColumn.java new file mode 100644 index 00000000..706b1e8f --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/annotation/DataColumn.java @@ -0,0 +1,40 @@ +package org.ruoyi.annotation; + +import java.lang.annotation.*; + +/** + * 数据权限注解,用于标记数据权限的占位符关键字和替换值 + *

+ * 一个注解只能对应一个模板 + *

+ * + * @author Lion Li + * @version 3.5.0 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataColumn { + + /** + * 数据权限模板的占位符关键字,默认为 "deptName" + * + * @return 占位符关键字数组 + */ + String[] key() default "deptName"; + + /** + * 数据权限模板的占位符替换值,默认为 "dept_id" + * + * @return 占位符替换值数组 + */ + String[] value() default "dept_id"; + + /** + * 权限标识符 用于通过菜单权限标识符来获取数据权限 + * 拥有此标识符的角色 将不会拼接此角色的数据过滤sql + * + * @return 权限标识符 + */ + String permission() default ""; +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/annotation/DataPermission.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/annotation/DataPermission.java new file mode 100644 index 00000000..24170faa --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/annotation/DataPermission.java @@ -0,0 +1,30 @@ +package org.ruoyi.annotation; + +import java.lang.annotation.*; + +/** + * 数据权限组注解,用于标记数据权限配置数组 + * + * @author Lion Li + * @version 3.5.0 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataPermission { + + /** + * 数据权限配置数组,用于指定数据权限的占位符关键字和替换值 + * + * @return 数据权限配置数组 + */ + DataColumn[] value(); + + /** + * 权限拼接标识符(用于指定连接语句的sql符号) + * 如不填 默认 select 用 OR 其他语句用 AND + * 内容 OR 或者 AND + */ + String joinStr() default ""; + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/aspect/DataPermissionAspect.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/aspect/DataPermissionAspect.java new file mode 100644 index 00000000..f9026e91 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/aspect/DataPermissionAspect.java @@ -0,0 +1,50 @@ +package org.ruoyi.aspect; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.ruoyi.annotation.DataPermission; +import org.ruoyi.helper.DataPermissionHelper; + +/** + * 数据权限处理 + * + * @author Lion Li + */ +@Slf4j +@Aspect +public class DataPermissionAspect { + + /** + * 处理请求前执行 + */ + @Before(value = "@annotation(dataPermission)") + public void doBefore(JoinPoint joinPoint, DataPermission dataPermission) { + DataPermissionHelper.setPermission(dataPermission); + } + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(dataPermission)") + public void doAfterReturning(JoinPoint joinPoint, DataPermission dataPermission) { + DataPermissionHelper.removePermission(); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(dataPermission)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, DataPermission dataPermission, Exception e) { + DataPermissionHelper.removePermission(); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataColumn.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataColumn.java deleted file mode 100644 index a3419da5..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataColumn.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.ruoyi.common.mybatis.annotation; - -import java.lang.annotation.*; - -/** - * 数据权限 - * - * 一个注解只能对应一个模板 - * - * @author Lion Li - * @version 3.5.0 - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface DataColumn { - - /** - * 占位符关键字 - */ - String[] key() default "deptName"; - - /** - * 占位符替换值 - */ - String[] value() default "dept_id"; - -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataPermission.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataPermission.java deleted file mode 100644 index 037eb81a..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataPermission.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.ruoyi.common.mybatis.annotation; - -import java.lang.annotation.*; - -/** - * 数据权限组 - * - * @author Lion Li - * @version 3.5.0 - */ -@Target({ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface DataPermission { - - DataColumn[] value(); - -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/mapper/BaseMapperPlus.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/mapper/BaseMapperPlus.java deleted file mode 100644 index b14c14ef..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/mapper/BaseMapperPlus.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.ruoyi.common.mybatis.core.mapper; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjectUtil; -import com.baomidou.mybatisplus.core.conditions.Wrapper; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.baomidou.mybatisplus.extension.toolkit.Db; -import org.apache.ibatis.logging.Log; -import org.apache.ibatis.logging.LogFactory; -import org.ruoyi.common.core.utils.MapstructUtils; - -import java.io.Serializable; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * 自定义 Mapper 接口, 实现 自定义扩展 - * - * @param table 泛型 - * @param vo 泛型 - * @author Lion Li - * @since 2021-05-13 - */ -@SuppressWarnings("unchecked") -public interface BaseMapperPlus extends BaseMapper { - - Log log = LogFactory.getLog(BaseMapperPlus.class); - - default Class currentVoClass() { - return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1); - } - - default Class currentModelClass() { - return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0); - } - - default List selectList() { - return this.selectList(new QueryWrapper<>()); - } - - /** - * 批量插入 - */ - default boolean insertBatch(Collection entityList) { - return Db.saveBatch(entityList); - } - - /** - * 批量更新 - */ - default boolean updateBatchById(Collection entityList) { - return Db.updateBatchById(entityList); - } - - /** - * 批量插入或更新 - */ - default boolean insertOrUpdateBatch(Collection entityList) { - return Db.saveOrUpdateBatch(entityList); - } - - /** - * 批量插入(包含限制条数) - */ - default boolean insertBatch(Collection entityList, int batchSize) { - return Db.saveBatch(entityList, batchSize); - } - - /** - * 批量更新(包含限制条数) - */ - default boolean updateBatchById(Collection entityList, int batchSize) { - return Db.updateBatchById(entityList, batchSize); - } - - /** - * 批量插入或更新(包含限制条数) - */ - default boolean insertOrUpdateBatch(Collection entityList, int batchSize) { - return Db.saveOrUpdateBatch(entityList, batchSize); - } - - /** - * 插入或更新(包含限制条数) - */ - default boolean insertOrUpdate(T entity) { - return Db.saveOrUpdate(entity); - } - - default V selectVoById(Serializable id) { - return selectVoById(id, this.currentVoClass()); - } - - /** - * 根据 ID 查询 - */ - default C selectVoById(Serializable id, Class voClass) { - T obj = this.selectById(id); - if (ObjectUtil.isNull(obj)) { - return null; - } - return MapstructUtils.convert(obj, voClass); - } - - default List selectVoBatchIds(Collection idList) { - return selectVoBatchIds(idList, this.currentVoClass()); - } - - /** - * 查询(根据ID 批量查询) - */ - default List selectVoBatchIds(Collection idList, Class voClass) { - List list = this.selectBatchIds(idList); - if (CollUtil.isEmpty(list)) { - return CollUtil.newArrayList(); - } - return MapstructUtils.convert(list, voClass); - } - - default List selectVoByMap(Map map) { - return selectVoByMap(map, this.currentVoClass()); - } - - /** - * 查询(根据 columnMap 条件) - */ - default List selectVoByMap(Map map, Class voClass) { - List list = this.selectByMap(map); - if (CollUtil.isEmpty(list)) { - return CollUtil.newArrayList(); - } - return MapstructUtils.convert(list, voClass); - } - - default V selectVoOne(Wrapper wrapper) { - return selectVoOne(wrapper, this.currentVoClass()); - } - - /** - * 根据 entity 条件,查询一条记录 - */ - default C selectVoOne(Wrapper wrapper, Class voClass) { - T obj = this.selectOne(wrapper); - if (ObjectUtil.isNull(obj)) { - return null; - } - return MapstructUtils.convert(obj, voClass); - } - - default List selectVoList() { - return selectVoList(new QueryWrapper<>(), this.currentVoClass()); - } - - default List selectVoList(Wrapper wrapper) { - return selectVoList(wrapper, this.currentVoClass()); - } - - /** - * 根据 entity 条件,查询全部记录 - */ - default List selectVoList(Wrapper wrapper, Class voClass) { - List list = this.selectList(wrapper); - if (CollUtil.isEmpty(list)) { - return CollUtil.newArrayList(); - } - return MapstructUtils.convert(list, voClass); - } - - default

> P selectVoPage(IPage page, Wrapper wrapper) { - return selectVoPage(page, wrapper, this.currentVoClass()); - } - - /** - * 分页查询VO - */ - default > P selectVoPage(IPage page, Wrapper wrapper, Class voClass) { - IPage pageData = this.selectPage(page, wrapper); - IPage voPage = new Page<>(pageData.getCurrent(), pageData.getSize(), pageData.getTotal()); - if (CollUtil.isEmpty(pageData.getRecords())) { - return (P) voPage; - } - voPage.setRecords(MapstructUtils.convert(pageData.getRecords(), voClass)); - return (P) voPage; - } - - default List selectObjs(Wrapper wrapper, Function mapper) { - return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList()); - } - -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataScopeType.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataScopeType.java deleted file mode 100644 index 65362ad0..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataScopeType.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.ruoyi.common.mybatis.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.ruoyi.common.core.utils.StringUtils; -import org.ruoyi.common.mybatis.helper.DataPermissionHelper; - -/** - * 数据权限类型 - *

- * 语法支持 spel 模板表达式 - *

- * 内置数据 user 当前用户 内容参考 LoginUser - * 如需扩展数据 可使用 {@link DataPermissionHelper} 操作 - * 内置服务 sdss 系统数据权限服务 内容参考 SysDataScopeService - * 如需扩展更多自定义服务 可以参考 sdss 自行编写 - * - * @author Lion Li - * @version 3.5.0 - */ -@Getter -@AllArgsConstructor -public enum DataScopeType { - - /** - * 全部数据权限 - */ - ALL("1", "", ""), - - /** - * 自定数据权限 - */ - CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", ""), - - /** - * 部门数据权限 - */ - DEPT("3", " #{#deptName} = #{#user.deptId} ", ""), - - /** - * 部门及以下数据权限 - */ - DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", ""), - - /** - * 仅本人数据权限 - */ - SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "); - - private final String code; - - /** - * 语法 采用 spel 模板表达式 - */ - private final String sqlTemplate; - - /** - * 不满足 sqlTemplate 则填充 - */ - private final String elseSql; - - public static DataScopeType findCode(String code) { - if (StringUtils.isBlank(code)) { - return null; - } - for (DataScopeType type : values()) { - if (type.getCode().equals(code)) { - return type; - } - } - return null; - } -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/InjectionMetaObjectHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/InjectionMetaObjectHandler.java deleted file mode 100644 index dfa24f0b..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/InjectionMetaObjectHandler.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.ruoyi.common.mybatis.handler; - -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.http.HttpStatus; -import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; -import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.reflection.MetaObject; -import org.ruoyi.common.core.domain.model.LoginUser; -import org.ruoyi.common.core.exception.ServiceException; -import org.ruoyi.common.mybatis.core.domain.BaseEntity; -import org.ruoyi.common.satoken.utils.LoginHelper; - -import java.util.Date; - -/** - * MP注入处理器 - * - * @author Lion Li - * @date 2021/4/25 - */ -@Slf4j -public class InjectionMetaObjectHandler implements MetaObjectHandler { - - @Override - public void insertFill(MetaObject metaObject) { - try { - if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) { - Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime()) - ? baseEntity.getCreateTime() : new Date(); - baseEntity.setCreateTime(current); - baseEntity.setUpdateTime(current); - LoginUser loginUser = getLoginUser(); - if (ObjectUtil.isNotNull(loginUser)) { - Long userId = ObjectUtil.isNotNull(baseEntity.getCreateBy()) - ? baseEntity.getCreateBy() : loginUser.getUserId(); - // 当前已登录 且 创建人为空 则填充 - baseEntity.setCreateBy(userId); - // 当前已登录 且 更新人为空 则填充 - baseEntity.setUpdateBy(userId); - baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept()) - ? baseEntity.getCreateDept() : loginUser.getDeptId()); - } - } - } catch (Exception e) { - throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED); - } - } - - @Override - public void updateFill(MetaObject metaObject) { - try { - if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) { - Date current = new Date(); - // 更新时间填充(不管为不为空) - baseEntity.setUpdateTime(current); - LoginUser loginUser = getLoginUser(); - // 当前已登录 更新人填充(不管为不为空) - if (ObjectUtil.isNotNull(loginUser)) { - baseEntity.setUpdateBy(loginUser.getUserId()); - } - } - } catch (Exception e) { - throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED); - } - } - - /** - * 获取登录用户名 - */ - private LoginUser getLoginUser() { - LoginUser loginUser; - try { - loginUser = LoginHelper.getLoginUser(); - } catch (Exception e) { - log.warn("自动注入警告 => 用户未登录"); - return null; - } - return loginUser; - } - -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java deleted file mode 100644 index f928f42a..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.ruoyi.common.mybatis.handler; - -import cn.hutool.core.annotation.AnnotationUtil; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.ConcurrentHashSet; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ClassUtil; -import cn.hutool.core.util.ObjectUtil; -import lombok.extern.slf4j.Slf4j; -import net.sf.jsqlparser.JSQLParserException; -import net.sf.jsqlparser.expression.Expression; -import net.sf.jsqlparser.expression.Parenthesis; -import net.sf.jsqlparser.expression.operators.conditional.AndExpression; -import net.sf.jsqlparser.parser.CCJSqlParserUtil; -import org.ruoyi.common.core.domain.dto.RoleDTO; -import org.ruoyi.common.core.domain.model.LoginUser; -import org.ruoyi.common.core.exception.ServiceException; -import org.ruoyi.common.core.utils.SpringUtils; -import org.ruoyi.common.core.utils.StreamUtils; -import org.ruoyi.common.core.utils.StringUtils; -import org.ruoyi.common.mybatis.annotation.DataColumn; -import org.ruoyi.common.mybatis.annotation.DataPermission; -import org.ruoyi.common.mybatis.enums.DataScopeType; -import org.ruoyi.common.mybatis.helper.DataPermissionHelper; -import org.ruoyi.common.satoken.utils.LoginHelper; -import org.springframework.context.expression.BeanFactoryResolver; -import org.springframework.expression.BeanResolver; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.ParserContext; -import org.springframework.expression.common.TemplateParserContext; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; - -import java.lang.reflect.Method; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -/** - * 数据权限过滤 - * - * @author Lion Li - * @version 3.5.0 - */ -@Slf4j -public class PlusDataPermissionHandler { - - /** - * 方法或类(名称) 与 注解的映射关系缓存 - */ - private final Map dataPermissionCacheMap = new ConcurrentHashMap<>(); - - /** - * 无效注解方法缓存用于快速返回 - */ - private final Set invalidCacheSet = new ConcurrentHashSet<>(); - - /** - * spel 解析器 - */ - private final ExpressionParser parser = new SpelExpressionParser(); - private final ParserContext parserContext = new TemplateParserContext(); - /** - * bean解析器 用于处理 spel 表达式中对 bean 的调用 - */ - private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory()); - - - public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) { - DataColumn[] dataColumns = findAnnotation(mappedStatementId); - if (ArrayUtil.isEmpty(dataColumns)) { - invalidCacheSet.add(mappedStatementId); - return where; - } - LoginUser currentUser = DataPermissionHelper.getVariable("user"); - if (ObjectUtil.isNull(currentUser)) { - currentUser = LoginHelper.getLoginUser(); - DataPermissionHelper.setVariable("user", currentUser); - } - // 如果是超级管理员或租户管理员,则不过滤数据 - if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) { - return where; - } - String dataFilterSql = buildDataFilter(dataColumns, isSelect); - if (StringUtils.isBlank(dataFilterSql)) { - return where; - } - try { - Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql); - // 数据权限使用单独的括号 防止与其他条件冲突 - Parenthesis parenthesis = new Parenthesis(expression); - if (ObjectUtil.isNotNull(where)) { - return new AndExpression(where, parenthesis); - } else { - return parenthesis; - } - } catch (JSQLParserException e) { - throw new ServiceException("数据权限解析异常 => " + e.getMessage()); - } - } - - /** - * 构造数据过滤sql - */ - private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) { - // 更新或删除需满足所有条件 - String joinStr = isSelect ? " OR " : " AND "; - LoginUser user = DataPermissionHelper.getVariable("user"); - StandardEvaluationContext context = new StandardEvaluationContext(); - context.setBeanResolver(beanResolver); - DataPermissionHelper.getContext().forEach(context::setVariable); - Set conditions = new HashSet<>(); - for (RoleDTO role : user.getRoles()) { - user.setRoleId(role.getRoleId()); - // 获取角色权限泛型 - DataScopeType type = DataScopeType.findCode(role.getDataScope()); - if (ObjectUtil.isNull(type)) { - throw new ServiceException("角色数据范围异常 => " + role.getDataScope()); - } - // 全部数据权限直接返回 - if (type == DataScopeType.ALL) { - return ""; - } - boolean isSuccess = false; - for (DataColumn dataColumn : dataColumns) { - if (dataColumn.key().length != dataColumn.value().length) { - throw new ServiceException("角色数据范围异常 => key与value长度不匹配"); - } - // 不包含 key 变量 则不处理 - if (!StringUtils.containsAny(type.getSqlTemplate(), - Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new) - )) { - continue; - } - // 设置注解变量 key 为表达式变量 value 为变量值 - for (int i = 0; i < dataColumn.key().length; i++) { - context.setVariable(dataColumn.key()[i], dataColumn.value()[i]); - } - - // 解析sql模板并填充 - String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class); - conditions.add(joinStr + sql); - isSuccess = true; - } - // 未处理成功则填充兜底方案 - if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) { - conditions.add(joinStr + type.getElseSql()); - } - } - - if (CollUtil.isNotEmpty(conditions)) { - String sql = StreamUtils.join(conditions, Function.identity(), ""); - return sql.substring(joinStr.length()); - } - return ""; - } - - private DataColumn[] findAnnotation(String mappedStatementId) { - StringBuilder sb = new StringBuilder(mappedStatementId); - int index = sb.lastIndexOf("."); - String clazzName = sb.substring(0, index); - String methodName = sb.substring(index + 1, sb.length()); - Class clazz = ClassUtil.loadClass(clazzName); - List methods = Arrays.stream(ClassUtil.getDeclaredMethods(clazz)) - .filter(method -> method.getName().equals(methodName)).toList(); - DataPermission dataPermission; - // 获取方法注解 - for (Method method : methods) { - dataPermission = dataPermissionCacheMap.get(mappedStatementId); - if (ObjectUtil.isNotNull(dataPermission)) { - return dataPermission.value(); - } - if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) { - dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class); - dataPermissionCacheMap.put(mappedStatementId, dataPermission); - return dataPermission.value(); - } - } - dataPermission = dataPermissionCacheMap.get(clazz.getName()); - if (ObjectUtil.isNotNull(dataPermission)) { - return dataPermission.value(); - } - // 获取类注解 - if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) { - dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class); - dataPermissionCacheMap.put(clazz.getName(), dataPermission); - return dataPermission.value(); - } - return null; - } - - /** - * 是否为无效方法 无数据权限 - */ - public boolean isInvalid(String mappedStatementId) { - return invalidCacheSet.contains(mappedStatementId); - } -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataPermissionHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataPermissionHelper.java deleted file mode 100644 index 8e3de7a3..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataPermissionHelper.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.ruoyi.common.mybatis.helper; - -import cn.dev33.satoken.context.SaHolder; -import cn.dev33.satoken.context.model.SaStorage; -import cn.hutool.core.util.ObjectUtil; -import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; -import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; - -/** - * 数据权限助手 - * - * @author Lion Li - * @version 3.5.0 - */ -@NoArgsConstructor(access = AccessLevel.PRIVATE) -@SuppressWarnings("unchecked cast") -public class DataPermissionHelper { - - private static final String DATA_PERMISSION_KEY = "data:permission"; - - public static T getVariable(String key) { - Map context = getContext(); - return (T) context.get(key); - } - - - public static void setVariable(String key, Object value) { - Map context = getContext(); - context.put(key, value); - } - - public static Map getContext() { - SaStorage saStorage = SaHolder.getStorage(); - Object attribute = saStorage.get(DATA_PERMISSION_KEY); - if (ObjectUtil.isNull(attribute)) { - saStorage.set(DATA_PERMISSION_KEY, new HashMap<>()); - attribute = saStorage.get(DATA_PERMISSION_KEY); - } - if (attribute instanceof Map map) { - return map; - } - throw new NullPointerException("data permission context type exception"); - } - - /** - * 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭) - */ - public static void enableIgnore() { - InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build()); - } - - /** - * 关闭忽略数据权限 - */ - public static void disableIgnore() { - InterceptorIgnoreHelper.clearIgnoreStrategy(); - } - - /** - * 在忽略数据权限中执行 - * - * @param handle 处理执行方法 - */ - public static void ignore(Runnable handle) { - enableIgnore(); - try { - handle.run(); - } finally { - disableIgnore(); - } - } - - /** - * 在忽略数据权限中执行 - * - * @param handle 处理执行方法 - */ - public static T ignore(Supplier handle) { - enableIgnore(); - try { - return handle.get(); - } finally { - disableIgnore(); - } - } - -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/interceptor/PlusDataPermissionInterceptor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/interceptor/PlusDataPermissionInterceptor.java deleted file mode 100644 index 98895d0a..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/interceptor/PlusDataPermissionInterceptor.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.ruoyi.common.mybatis.interceptor; - -import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; -import com.baomidou.mybatisplus.core.toolkit.PluginUtils; -import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; -import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; -import net.sf.jsqlparser.expression.Expression; -import net.sf.jsqlparser.statement.delete.Delete; -import net.sf.jsqlparser.statement.select.PlainSelect; -import net.sf.jsqlparser.statement.select.Select; -import net.sf.jsqlparser.statement.select.SelectBody; -import net.sf.jsqlparser.statement.select.SetOperationList; -import net.sf.jsqlparser.statement.update.Update; -import org.apache.ibatis.executor.Executor; -import org.apache.ibatis.executor.statement.StatementHandler; -import org.apache.ibatis.mapping.BoundSql; -import org.apache.ibatis.mapping.MappedStatement; -import org.apache.ibatis.mapping.SqlCommandType; -import org.apache.ibatis.session.ResultHandler; -import org.apache.ibatis.session.RowBounds; -import org.ruoyi.common.mybatis.handler.PlusDataPermissionHandler; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.List; - -/** - * 数据权限拦截器 - * - * @author Lion Li - * @version 3.5.0 - */ -public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor { - - private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler(); - - @Override - public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { - // 检查忽略注解 - if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { - return; - } - // 检查是否无效 无数据权限注解 - if (dataPermissionHandler.isInvalid(ms.getId())) { - return; - } - // 解析 sql 分配对应方法 - PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); - mpBs.sql(parserSingle(mpBs.sql(), ms.getId())); - } - - @Override - public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { - PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); - MappedStatement ms = mpSh.mappedStatement(); - SqlCommandType sct = ms.getSqlCommandType(); - if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { - if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { - return; - } - PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); - mpBs.sql(parserMulti(mpBs.sql(), ms.getId())); - } - } - - @Override - protected void processSelect(Select select, int index, String sql, Object obj) { - SelectBody selectBody = select.getSelectBody(); - if (selectBody instanceof PlainSelect plainSelect) { - this.setWhere(plainSelect, (String) obj); - } else if (selectBody instanceof SetOperationList setOperationList) { - List selectBodyList = setOperationList.getSelects(); - selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj)); - } - } - - @Override - protected void processUpdate(Update update, int index, String sql, Object obj) { - Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false); - if (null != sqlSegment) { - update.setWhere(sqlSegment); - } - } - - @Override - protected void processDelete(Delete delete, int index, String sql, Object obj) { - Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false); - if (null != sqlSegment) { - delete.setWhere(sqlSegment); - } - } - - /** - * 设置 where 条件 - * - * @param plainSelect 查询对象 - * @param mappedStatementId 执行方法id - */ - protected void setWhere(PlainSelect plainSelect, String mappedStatementId) { - Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true); - if (null != sqlSegment) { - plainSelect.setWhere(sqlSegment); - } - } - -} - diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaHeaderProcessor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaHeaderProcessor.java deleted file mode 100644 index fd67e07c..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaHeaderProcessor.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright © 2018 organization baomidou - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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 org.ruoyi.common.mybatis.jakarta; - -import com.baomidou.dynamic.datasource.processor.DsProcessor; -import jakarta.servlet.http.HttpServletRequest; -import org.aopalliance.intercept.MethodInvocation; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -/** - * @author TaoYu - * @since 3.6.0 - */ -public class DsJakartaHeaderProcessor extends DsProcessor { - - /** - * header prefix - */ - private static final String HEADER_PREFIX = "#header"; - - @Override - public boolean matches(String key) { - return key.startsWith(HEADER_PREFIX); - } - - @Override - public String doDetermineDatasource(MethodInvocation invocation, String key) { - HttpServletRequest request = (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); - return request.getHeader(key.substring(8)); - } -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaSessionProcessor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaSessionProcessor.java deleted file mode 100644 index f055b463..00000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaSessionProcessor.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright © 2018 organization baomidou - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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 org.ruoyi.common.mybatis.jakarta; - -import com.baomidou.dynamic.datasource.processor.DsProcessor; -import jakarta.servlet.http.HttpServletRequest; -import org.aopalliance.intercept.MethodInvocation; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - - -/** - * @author TaoYu - * @since 3.6.0 - */ -public class DsJakartaSessionProcessor extends DsProcessor { - - /** - * session开头 - */ - private static final String SESSION_PREFIX = "#session"; - - @Override - public boolean matches(String key) { - return key.startsWith(SESSION_PREFIX); - } - - @Override - public String doDetermineDatasource(MethodInvocation invocation, String key) { - HttpServletRequest request = (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); - return request.getSession().getAttribute(key.substring(9)).toString(); - } -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/config/MybatisPlusConfig.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/config/MybatisPlusConfig.java similarity index 66% rename from ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/config/MybatisPlusConfig.java rename to ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/config/MybatisPlusConfig.java index 7f28b1f6..f5942b2e 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/config/MybatisPlusConfig.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/config/MybatisPlusConfig.java @@ -1,17 +1,25 @@ -package org.ruoyi.common.mybatis.config; +package org.ruoyi.config; import cn.hutool.core.net.NetUtil; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler; import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator; import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; -import org.ruoyi.common.mybatis.handler.InjectionMetaObjectHandler; -import org.ruoyi.common.mybatis.interceptor.PlusDataPermissionInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; +import org.ruoyi.aspect.DataPermissionAspect; +import org.ruoyi.common.core.factory.YmlPropertySourceFactory; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.handler.InjectionMetaObjectHandler; +import org.ruoyi.handler.MybatisExceptionHandler; +import org.ruoyi.handler.PlusPostInitTableInfoHandler; +import org.ruoyi.interceptor.PlusDataPermissionInterceptor; import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.beans.BeansException; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.PropertySource; import org.springframework.transaction.annotation.EnableTransactionManagement; /** @@ -20,13 +28,19 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; * @author Lion Li */ @EnableTransactionManagement(proxyTargetClass = true) -@AutoConfiguration @MapperScan("${mybatis-plus.mapperPackage}") +@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class) public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 多租户插件 必须放到第一位 + try { + TenantLineInnerInterceptor tenant = SpringUtils.getBean(TenantLineInnerInterceptor.class); + interceptor.addInnerInterceptor(tenant); + } catch (BeansException ignore) { + } // 数据权限处理 interceptor.addInnerInterceptor(dataPermissionInterceptor()); // 分页插件 @@ -40,7 +54,15 @@ public class MybatisPlusConfig { * 数据权限拦截器 */ public PlusDataPermissionInterceptor dataPermissionInterceptor() { - return new PlusDataPermissionInterceptor(); + return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage")); + } + + /** + * 数据权限切面处理器 + */ + @Bean + public DataPermissionAspect dataPermissionAspect() { + return new DataPermissionAspect(); } /** @@ -48,8 +70,6 @@ public class MybatisPlusConfig { */ public PaginationInnerInterceptor paginationInnerInterceptor() { PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); - // 设置最大单页限制数量,默认 500 条,-1 不受限制 - paginationInnerInterceptor.setMaxLimit(-1L); // 分页合理化 paginationInnerInterceptor.setOverflow(true); return paginationInnerInterceptor; @@ -79,6 +99,22 @@ public class MybatisPlusConfig { return new DefaultIdentifierGenerator(NetUtil.getLocalhost()); } + /** + * 异常处理器 + */ + @Bean + public MybatisExceptionHandler mybatisExceptionHandler() { + return new MybatisExceptionHandler(); + } + + /** + * 初始化表对象处理器 + */ + @Bean + public PostInitTableInfoHandler postInitTableInfoHandler() { + return new PlusPostInitTableInfoHandler(); + } + /** * PaginationInnerInterceptor 分页插件,自动识别数据库类型 * https://baomidou.com/pages/97710a/ diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/domain/BaseEntity.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/domain/BaseEntity.java similarity index 96% rename from ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/domain/BaseEntity.java rename to ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/domain/BaseEntity.java index bfc43c54..5e362d60 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/domain/BaseEntity.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/domain/BaseEntity.java @@ -1,4 +1,4 @@ -package org.ruoyi.common.mybatis.core.domain; +package org.ruoyi.core.domain; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; @@ -17,7 +17,6 @@ import java.util.Map; * * @author Lion Li */ - @Data public class BaseEntity implements Serializable { diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/mapper/BaseMapperPlus.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/mapper/BaseMapperPlus.java new file mode 100644 index 00000000..097c7210 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/mapper/BaseMapperPlus.java @@ -0,0 +1,335 @@ +package org.ruoyi.core.mapper; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.toolkit.Db; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.LogFactory; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StreamUtils; + + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * 自定义 Mapper 接口, 实现 自定义扩展 + * + * @param table 泛型 + * @param vo 泛型 + * @author Lion Li + * @since 2021-05-13 + */ +@SuppressWarnings("unchecked") +public interface BaseMapperPlus extends BaseMapper { + + Log log = LogFactory.getLog(BaseMapperPlus.class); + + /** + * 获取当前实例对象关联的泛型类型 V 的 Class 对象 + * + * @return 返回当前实例对象关联的泛型类型 V 的 Class 对象 + */ + default Class currentVoClass() { + return (Class) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[1]; + } + + /** + * 获取当前实例对象关联的泛型类型 T 的 Class 对象 + * + * @return 返回当前实例对象关联的泛型类型 T 的 Class 对象 + */ + default Class currentModelClass() { + return (Class) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[0]; + } + + /** + * 使用默认的查询条件查询并返回结果列表 + * + * @return 返回查询结果的列表 + */ + default List selectList() { + return this.selectList(new QueryWrapper<>()); + } + + /** + * 批量插入实体对象集合 + * + * @param entityList 实体对象集合 + * @return 插入操作是否成功的布尔值 + */ + default boolean insertBatch(Collection entityList) { + return Db.saveBatch(entityList); + } + + /** + * 批量根据ID更新实体对象集合 + * + * @param entityList 实体对象集合 + * @return 更新操作是否成功的布尔值 + */ + default boolean updateBatchById(Collection entityList) { + return Db.updateBatchById(entityList); + } + + /** + * 批量插入或更新实体对象集合 + * + * @param entityList 实体对象集合 + * @return 插入或更新操作是否成功的布尔值 + */ + default boolean insertOrUpdateBatch(Collection entityList) { + return Db.saveOrUpdateBatch(entityList); + } + + /** + * 批量插入实体对象集合并指定批处理大小 + * + * @param entityList 实体对象集合 + * @param batchSize 批处理大小 + * @return 插入操作是否成功的布尔值 + */ + default boolean insertBatch(Collection entityList, int batchSize) { + return Db.saveBatch(entityList, batchSize); + } + + /** + * 批量根据ID更新实体对象集合并指定批处理大小 + * + * @param entityList 实体对象集合 + * @param batchSize 批处理大小 + * @return 更新操作是否成功的布尔值 + */ + default boolean updateBatchById(Collection entityList, int batchSize) { + return Db.updateBatchById(entityList, batchSize); + } + + /** + * 批量插入或更新实体对象集合并指定批处理大小 + * + * @param entityList 实体对象集合 + * @param batchSize 批处理大小 + * @return 插入或更新操作是否成功的布尔值 + */ + default boolean insertOrUpdateBatch(Collection entityList, int batchSize) { + return Db.saveOrUpdateBatch(entityList, batchSize); + } + + /** + * 根据ID查询单个VO对象 + * + * @param id 主键ID + * @return 查询到的单个VO对象 + */ + default V selectVoById(Serializable id) { + return selectVoById(id, this.currentVoClass()); + } + + /** + * 根据ID查询单个VO对象并将其转换为指定的VO类 + * + * @param id 主键ID + * @param voClass 要转换的VO类的Class对象 + * @param VO类的类型 + * @return 查询到的单个VO对象,经过转换为指定的VO类后返回 + */ + default C selectVoById(Serializable id, Class voClass) { + T obj = this.selectById(id); + if (ObjectUtil.isNull(obj)) { + return null; + } + return MapstructUtils.convert(obj, voClass); + } + + /** + * 根据ID集合批量查询VO对象列表 + * + * @param idList 主键ID集合 + * @return 查询到的VO对象列表 + */ + default List selectVoByIds(Collection idList) { + return selectVoByIds(idList, this.currentVoClass()); + } + + /** + * 根据ID集合批量查询实体对象列表,并将其转换为指定的VO对象列表 + * + * @param idList 主键ID集合 + * @param voClass 要转换的VO类的Class对象 + * @param VO类的类型 + * @return 查询到的VO对象列表,经过转换为指定的VO类后返回 + */ + default List selectVoByIds(Collection idList, Class voClass) { + List list = this.selectByIds(idList); + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + return MapstructUtils.convert(list, voClass); + } + + /** + * 根据查询条件Map查询VO对象列表 + * + * @param map 查询条件Map + * @return 查询到的VO对象列表 + */ + default List selectVoByMap(Map map) { + return selectVoByMap(map, this.currentVoClass()); + } + + /** + * 根据查询条件Map查询实体对象列表,并将其转换为指定的VO对象列表 + * + * @param map 查询条件Map + * @param voClass 要转换的VO类的Class对象 + * @param VO类的类型 + * @return 查询到的VO对象列表,经过转换为指定的VO类后返回 + */ + default List selectVoByMap(Map map, Class voClass) { + List list = this.selectByMap(map); + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + return MapstructUtils.convert(list, voClass); + } + + /** + * 根据条件查询单个VO对象 + * + * @param wrapper 查询条件Wrapper + * @return 查询到的单个VO对象 + */ + default V selectVoOne(Wrapper wrapper) { + return selectVoOne(wrapper, this.currentVoClass()); + } + + /** + * 根据条件查询单个VO对象,并根据需要决定是否抛出异常 + * + * @param wrapper 查询条件Wrapper + * @param throwEx 是否抛出异常的标志 + * @return 查询到的单个VO对象 + */ + default V selectVoOne(Wrapper wrapper, boolean throwEx) { + return selectVoOne(wrapper, this.currentVoClass(), throwEx); + } + + /** + * 根据条件查询单个VO对象,并指定返回的VO对象的类型 + * + * @param wrapper 查询条件Wrapper + * @param voClass 返回的VO对象的Class对象 + * @param 返回的VO对象的类型 + * @return 查询到的单个VO对象,经过类型转换为指定的VO类后返回 + */ + default C selectVoOne(Wrapper wrapper, Class voClass) { + return selectVoOne(wrapper, voClass, true); + } + + /** + * 根据条件查询单个实体对象,并将其转换为指定的VO对象 + * + * @param wrapper 查询条件Wrapper + * @param voClass 要转换的VO类的Class对象 + * @param throwEx 是否抛出异常的标志 + * @param VO类的类型 + * @return 查询到的单个VO对象,经过转换为指定的VO类后返回 + */ + default C selectVoOne(Wrapper wrapper, Class voClass, boolean throwEx) { + T obj = this.selectOne(wrapper, throwEx); + if (ObjectUtil.isNull(obj)) { + return null; + } + return MapstructUtils.convert(obj, voClass); + } + + /** + * 查询所有VO对象列表 + * + * @return 查询到的VO对象列表 + */ + default List selectVoList() { + return selectVoList(new QueryWrapper<>(), this.currentVoClass()); + } + + /** + * 根据条件查询VO对象列表 + * + * @param wrapper 查询条件Wrapper + * @return 查询到的VO对象列表 + */ + default List selectVoList(Wrapper wrapper) { + return selectVoList(wrapper, this.currentVoClass()); + } + + /** + * 根据条件查询实体对象列表,并将其转换为指定的VO对象列表 + * + * @param wrapper 查询条件Wrapper + * @param voClass 要转换的VO类的Class对象 + * @param VO类的类型 + * @return 查询到的VO对象列表,经过转换为指定的VO类后返回 + */ + default List selectVoList(Wrapper wrapper, Class voClass) { + List list = this.selectList(wrapper); + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + return MapstructUtils.convert(list, voClass); + } + + /** + * 根据条件分页查询VO对象列表 + * + * @param page 分页信息 + * @param wrapper 查询条件Wrapper + * @return 查询到的VO对象分页列表 + */ + default

> P selectVoPage(IPage page, Wrapper wrapper) { + return selectVoPage(page, wrapper, this.currentVoClass()); + } + + /** + * 根据条件分页查询实体对象列表,并将其转换为指定的VO对象分页列表 + * + * @param page 分页信息 + * @param wrapper 查询条件Wrapper + * @param voClass 要转换的VO类的Class对象 + * @param VO类的类型 + * @param

VO对象分页列表的类型 + * @return 查询到的VO对象分页列表,经过转换为指定的VO类后返回 + */ + default > P selectVoPage(IPage page, Wrapper wrapper, Class voClass) { + // 根据条件分页查询实体对象列表 + List list = this.selectList(page, wrapper); + // 创建一个新的VO对象分页列表,并设置分页信息 + IPage voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); + if (CollUtil.isEmpty(list)) { + return (P) voPage; + } + voPage.setRecords(MapstructUtils.convert(list, voClass)); + return (P) voPage; + } + + /** + * 根据条件查询符合条件的对象,并将其转换为指定类型的对象列表 + * + * @param wrapper 查询条件Wrapper + * @param mapper 转换函数,用于将查询到的对象转换为指定类型的对象 + * @param 要转换的对象的类型 + * @return 查询到的符合条件的对象列表,经过转换为指定类型的对象后返回 + */ + default List selectObjs(Wrapper wrapper, Function mapper) { + return StreamUtils.toList(this.selectObjs(wrapper), mapper); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/PageQuery.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/page/PageQuery.java similarity index 90% rename from ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/PageQuery.java rename to ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/page/PageQuery.java index 33e0190d..a388cb4a 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/PageQuery.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/page/PageQuery.java @@ -1,9 +1,10 @@ -package org.ruoyi.common.mybatis.core.page; +package org.ruoyi.core.page; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.ruoyi.common.core.exception.ServiceException; import org.ruoyi.common.core.utils.StringUtils; @@ -19,7 +20,6 @@ import java.util.List; * * @author Lion Li */ - @Data public class PageQuery implements Serializable { @@ -56,6 +56,9 @@ public class PageQuery implements Serializable { */ public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE; + /** + * 构建分页对象 + */ public Page build() { Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM); Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE); @@ -111,4 +114,14 @@ public class PageQuery implements Serializable { return list; } + @JsonIgnore + public Integer getFirstNum() { + return (pageNum - 1) * pageSize; + } + + public PageQuery(Integer pageSize, Integer pageNum) { + this.pageSize = pageSize; + this.pageNum = pageNum; + } + } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/TableDataInfo.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/page/TableDataInfo.java similarity index 85% rename from ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/TableDataInfo.java rename to ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/page/TableDataInfo.java index b4f7cd70..eb321ae9 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/TableDataInfo.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/page/TableDataInfo.java @@ -1,4 +1,4 @@ -package org.ruoyi.common.mybatis.core.page; +package org.ruoyi.core.page; import cn.hutool.http.HttpStatus; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -14,7 +14,6 @@ import java.util.List; * * @author Lion Li */ - @Data @NoArgsConstructor public class TableDataInfo implements Serializable { @@ -51,8 +50,13 @@ public class TableDataInfo implements Serializable { public TableDataInfo(List list, long total) { this.rows = list; this.total = total; + this.code = HttpStatus.HTTP_OK; + this.msg = "查询成功"; } + /** + * 根据分页对象构建表格分页数据对象 + */ public static TableDataInfo build(IPage page) { TableDataInfo rspData = new TableDataInfo<>(); rspData.setCode(HttpStatus.HTTP_OK); @@ -62,6 +66,9 @@ public class TableDataInfo implements Serializable { return rspData; } + /** + * 根据数据列表构建表格分页数据对象 + */ public static TableDataInfo build(List list) { TableDataInfo rspData = new TableDataInfo<>(); rspData.setCode(HttpStatus.HTTP_OK); @@ -71,6 +78,9 @@ public class TableDataInfo implements Serializable { return rspData; } + /** + * 构建表格分页数据对象 + */ public static TableDataInfo build() { TableDataInfo rspData = new TableDataInfo<>(); rspData.setCode(HttpStatus.HTTP_OK); diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataBaseType.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/enums/DataBaseType.java similarity index 74% rename from ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataBaseType.java rename to ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/enums/DataBaseType.java index fba59340..1b32cda4 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataBaseType.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/enums/DataBaseType.java @@ -1,9 +1,10 @@ -package org.ruoyi.common.mybatis.enums; +package org.ruoyi.enums; import lombok.AllArgsConstructor; import lombok.Getter; import org.ruoyi.common.core.utils.StringUtils; + /** * 数据库类型 * @@ -33,8 +34,17 @@ public enum DataBaseType { */ SQL_SERVER("Microsoft SQL Server"); + /** + * 数据库类型 + */ private final String type; + /** + * 根据数据库产品名称查找对应的数据库类型 + * + * @param databaseProductName 数据库产品名称 + * @return 对应的数据库类型枚举值,如果未找到则返回 null + */ public static DataBaseType find(String databaseProductName) { if (StringUtils.isBlank(databaseProductName)) { return null; diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/enums/DataScopeType.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/enums/DataScopeType.java new file mode 100644 index 00000000..f478adbd --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/enums/DataScopeType.java @@ -0,0 +1,87 @@ +package org.ruoyi.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.helper.DataPermissionHelper; + +/** + * 数据权限类型枚举 + *

+ * 支持使用 SpEL 模板表达式定义 SQL 查询条件 + * 内置数据: + * - {@code user}: 当前登录用户信息,参考 {@link LoginUser} + * 内置服务: + * - {@code sdss}: 系统数据权限服务,参考 ISysDataScopeService + * 如需扩展数据,可以通过 {@link DataPermissionHelper} 进行操作 + * 如需扩展服务,可以通过 ISysDataScopeService 自行编写 + *

+ * + * @author Lion Li + * @version 3.5.0 + */ +@Getter +@AllArgsConstructor +public enum DataScopeType { + + /** + * 全部数据权限 + */ + ALL("1", "", ""), + + /** + * 自定数据权限 + */ + CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "), + + /** + * 部门数据权限 + */ + DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "), + + /** + * 部门及以下数据权限 + */ + DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "), + + /** + * 仅本人数据权限 + */ + SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "), + + /** + * 部门及以下或本人数据权限 + */ + DEPT_AND_CHILD_OR_SELF("6", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} ) OR #{#userName} = #{#user.userId} ", " 1 = 0 "); + + private final String code; + + /** + * SpEL 模板表达式,用于构建 SQL 查询条件 + */ + private final String sqlTemplate; + + /** + * 如果不满足 {@code sqlTemplate} 的条件,则使用此默认 SQL 表达式 + */ + private final String elseSql; + + /** + * 根据枚举代码查找对应的枚举值 + * + * @param code 枚举代码 + * @return 对应的枚举值,如果未找到则返回 null + */ + public static DataScopeType findCode(String code) { + if (StringUtils.isBlank(code)) { + return null; + } + for (DataScopeType type : values()) { + if (type.getCode().equals(code)) { + return type; + } + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/InjectionMetaObjectHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/InjectionMetaObjectHandler.java new file mode 100644 index 00000000..57843d35 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/InjectionMetaObjectHandler.java @@ -0,0 +1,103 @@ +package org.ruoyi.handler; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.HttpStatus; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.ObjectUtils; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.core.domain.BaseEntity; + + +import java.util.Date; + +/** + * MP注入处理器 + * + * @author Lion Li + * @date 2021/4/25 + */ +@Slf4j +public class InjectionMetaObjectHandler implements MetaObjectHandler { + + /** + * 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息 + * + * @param metaObject 元对象,用于获取原始对象并进行填充 + */ + @Override + public void insertFill(MetaObject metaObject) { + try { + if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) { + // 获取当前时间作为创建时间和更新时间,如果创建时间不为空,则使用创建时间,否则使用当前时间 + Date current = ObjectUtils.notNull(baseEntity.getCreateTime(), new Date()); + baseEntity.setCreateTime(current); + baseEntity.setUpdateTime(current); + + // 如果创建人为空,则填充当前登录用户的信息 + if (ObjectUtil.isNull(baseEntity.getCreateBy())) { + LoginUser loginUser = getLoginUser(); + if (ObjectUtil.isNotNull(loginUser)) { + Long userId = loginUser.getUserId(); + // 填充创建人、更新人和创建部门信息 + baseEntity.setCreateBy(userId); + baseEntity.setUpdateBy(userId); + baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId())); + } + } + } else { + Date date = new Date(); + this.strictInsertFill(metaObject, "createTime", Date.class, date); + this.strictInsertFill(metaObject, "updateTime", Date.class, date); + } + } catch (Exception e) { + throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED); + } + } + + /** + * 更新填充方法,用于在更新数据时自动填充实体对象中的更新时间和更新人信息 + * + * @param metaObject 元对象,用于获取原始对象并进行填充 + */ + @Override + public void updateFill(MetaObject metaObject) { + try { + if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) { + // 获取当前时间作为更新时间,无论原始对象中的更新时间是否为空都填充 + Date current = new Date(); + baseEntity.setUpdateTime(current); + + // 获取当前登录用户的ID,并填充更新人信息 + Long userId = LoginHelper.getUserId(); + if (ObjectUtil.isNotNull(userId)) { + baseEntity.setUpdateBy(userId); + } + } else { + this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); + } + } catch (Exception e) { + throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED); + } + } + + /** + * 获取当前登录用户信息 + * + * @return 当前登录用户的信息,如果用户未登录则返回 null + */ + private LoginUser getLoginUser() { + LoginUser loginUser; + try { + loginUser = LoginHelper.getLoginUser(); + } catch (Exception e) { + log.warn("自动注入警告 => 用户未登录"); + return null; + } + return loginUser; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/MybatisExceptionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/MybatisExceptionHandler.java similarity index 90% rename from ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/MybatisExceptionHandler.java rename to ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/MybatisExceptionHandler.java index 281d172d..0cb338c2 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/MybatisExceptionHandler.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/MybatisExceptionHandler.java @@ -1,9 +1,11 @@ -package org.ruoyi.common.mybatis.handler; +package org.ruoyi.handler; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.core.domain.R; + import org.mybatis.spring.MyBatisSystemException; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.utils.StringUtils; import org.springframework.dao.DuplicateKeyException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -34,7 +36,7 @@ public class MybatisExceptionHandler { public R handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); String message = e.getMessage(); - if (message.contains("CannotFindDataSourceException")) { + if (StringUtils.contains("CannotFindDataSourceException", message)) { log.error("请求地址'{}', 未找到数据源", requestURI); return R.fail("未找到数据源,请联系管理员确认"); } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/PlusDataPermissionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/PlusDataPermissionHandler.java new file mode 100644 index 00000000..46e0b2e9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/PlusDataPermissionHandler.java @@ -0,0 +1,359 @@ +package org.ruoyi.handler; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import org.apache.ibatis.io.Resources; + +import org.ruoyi.annotation.DataColumn; +import org.ruoyi.annotation.DataPermission; +import org.ruoyi.common.core.domain.dto.RoleDTO; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.enums.DataScopeType; +import org.ruoyi.helper.DataPermissionHelper; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.ClassMetadata; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.expression.*; +import org.springframework.expression.common.TemplateParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.ClassUtils; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * 数据权限过滤 + * + * @author Lion Li + * @version 3.5.0 + */ +@Slf4j +public class PlusDataPermissionHandler { + + /** + * 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行) + */ + private final Map dataPermissionCacheMap = new ConcurrentHashMap<>(); + + /** + * spel 解析器 + */ + private final ExpressionParser parser = new SpelExpressionParser(); + private final ParserContext parserContext = new TemplateParserContext(); + /** + * bean解析器 用于处理 spel 表达式中对 bean 的调用 + */ + private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory()); + + /** + * 构造方法,扫描指定包下的 Mapper 类并初始化缓存 + * + * @param mapperPackage Mapper 类所在的包路径 + */ + public PlusDataPermissionHandler(String mapperPackage) { + scanMapperClasses(mapperPackage); + } + + /** + * 获取数据过滤条件的 SQL 片段 + * + * @param where 原始的查询条件表达式 + * @param mappedStatementId Mapper 方法的 ID + * @param isSelect 是否为查询语句 + * @return 数据过滤条件的 SQL 片段 + */ + public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) { + try { + // 获取数据权限配置 + DataPermission dataPermission = getDataPermission(mappedStatementId); + // 获取当前登录用户信息 + LoginUser currentUser = DataPermissionHelper.getVariable("user"); + if (ObjectUtil.isNull(currentUser)) { + currentUser = LoginHelper.getLoginUser(); + DataPermissionHelper.setVariable("user", currentUser); + } + // 如果是超级管理员或租户管理员,则不过滤数据 + if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) { + return where; + } + // 构造数据过滤条件的 SQL 片段 + String dataFilterSql = buildDataFilter(dataPermission, isSelect); + if (StringUtils.isBlank(dataFilterSql)) { + return where; + } + Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql); + // 数据权限使用单独的括号 防止与其他条件冲突 + ParenthesedExpressionList parenthesis = new ParenthesedExpressionList<>(expression); + if (ObjectUtil.isNotNull(where)) { + return new AndExpression(where, parenthesis); + } else { + return parenthesis; + } + } catch (JSQLParserException e) { + throw new ServiceException("数据权限解析异常 => " + e.getMessage()); + } finally { + DataPermissionHelper.removePermission(); + } + } + + /** + * 构建数据过滤条件的 SQL 语句 + * + * @param dataPermission 数据权限注解 + * @param isSelect 标志当前操作是否为查询操作,查询操作和更新或删除操作在处理过滤条件时会有不同的处理方式 + * @return 构建的数据过滤条件的 SQL 语句 + * @throws ServiceException 如果角色的数据范围异常或者 key 与 value 的长度不匹配,则抛出 ServiceException 异常 + */ + private String buildDataFilter(DataPermission dataPermission, boolean isSelect) { + // 更新或删除需满足所有条件 + String joinStr = isSelect ? " OR " : " AND "; + if (StringUtils.isNotBlank(dataPermission.joinStr())) { + joinStr = " " + dataPermission.joinStr() + " "; + } + LoginUser user = DataPermissionHelper.getVariable("user"); + Object defaultValue = "-1"; + NullSafeStandardEvaluationContext context = new NullSafeStandardEvaluationContext(defaultValue); + context.addPropertyAccessor(new NullSafePropertyAccessor(context.getPropertyAccessors().get(0), defaultValue)); + context.setBeanResolver(beanResolver); + DataPermissionHelper.getContext().forEach(context::setVariable); + Set conditions = new HashSet<>(); + // 优先设置变量 + List keys = new ArrayList<>(); + Map ignoreMap = new HashMap<>(); + for (DataColumn dataColumn : dataPermission.value()) { + if (dataColumn.key().length != dataColumn.value().length) { + throw new ServiceException("角色数据范围异常 => key与value长度不匹配"); + } + // 包含权限标识符 这直接跳过 + if (StringUtils.isNotBlank(dataColumn.permission()) && + CollUtil.contains(user.getMenuPermission(), dataColumn.permission()) + ) { + ignoreMap.put(dataColumn, Boolean.TRUE); + continue; + } + // 设置注解变量 key 为表达式变量 value 为变量值 + for (int i = 0; i < dataColumn.key().length; i++) { + context.setVariable(dataColumn.key()[i], dataColumn.value()[i]); + } + keys.addAll(Arrays.stream(dataColumn.key()).map(key -> "#" + key).toList()); + } + + for (RoleDTO role : user.getRoles()) { + user.setRoleId(role.getRoleId()); + // 获取角色权限泛型 + DataScopeType type = DataScopeType.findCode(role.getDataScope()); + if (ObjectUtil.isNull(type)) { + throw new ServiceException("角色数据范围异常 => " + role.getDataScope()); + } + // 全部数据权限直接返回 + if (type == DataScopeType.ALL) { + return StringUtils.EMPTY; + } + boolean isSuccess = false; + for (DataColumn dataColumn : dataPermission.value()) { + // 包含权限标识符 这直接跳过 + if (ignoreMap.containsKey(dataColumn)) { + // 修复多角色与权限标识符共用问题 https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IB4CS4 + conditions.add(joinStr + " 1 = 1 "); + isSuccess = true; + continue; + } + // 不包含 key 变量 则不处理 + if (!StringUtils.containsAny(type.getSqlTemplate(), keys.toArray(String[]::new))) { + continue; + } + // 当前注解不满足模板 不处理 + if (!StringUtils.containsAny(type.getSqlTemplate(), dataColumn.key())) { + continue; + } + // 忽略数据权限 防止spel表达式内有其他sql查询导致死循环调用 + String sql = DataPermissionHelper.ignore(() -> + parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class) + ); + // 解析sql模板并填充 + conditions.add(joinStr + sql); + isSuccess = true; + } + // 未处理成功则填充兜底方案 + if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) { + conditions.add(joinStr + type.getElseSql()); + } + } + + if (CollUtil.isNotEmpty(conditions)) { + String sql = StreamUtils.join(conditions, Function.identity(), ""); + return sql.substring(joinStr.length()); + } + return StringUtils.EMPTY; + } + + /** + * 扫描指定包下的 Mapper 类,并查找其中带有特定注解的方法或类 + * + * @param mapperPackage Mapper 类所在的包路径 + */ + private void scanMapperClasses(String mapperPackage) { + // 创建资源解析器和元数据读取工厂 + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); + // 将 Mapper 包路径按分隔符拆分为数组 + String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); + String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX; + try { + for (String packagePattern : packagePatternArray) { + // 将包路径转换为资源路径 + String path = ClassUtils.convertClassNameToResourcePath(packagePattern); + // 获取指定路径下的所有 .class 文件资源 + Resource[] resources = resolver.getResources(classpath + path + "/*.class"); + for (Resource resource : resources) { + // 获取资源的类元数据 + ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata(); + // 获取资源对应的类对象 + Class clazz = Resources.classForName(classMetadata.getClassName()); + // 查找类中的特定注解 + findAnnotation(clazz); + } + } + } catch (Exception e) { + log.error("初始化数据安全缓存时出错:{}", e.getMessage()); + } + } + + /** + * 在指定的类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中 + * + * @param clazz 要查找的类 + */ + private void findAnnotation(Class clazz) { + DataPermission dataPermission; + for (Method method : clazz.getMethods()) { + if (method.isDefault() || method.isVarArgs()) { + continue; + } + String mappedStatementId = clazz.getName() + "." + method.getName(); + if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) { + dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class); + dataPermissionCacheMap.put(mappedStatementId, dataPermission); + } + } + if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) { + dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class); + dataPermissionCacheMap.put(clazz.getName(), dataPermission); + } + } + + /** + * 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象 + * + * @param mapperId 映射语句 ID + * @return DataPermission 注解对象,如果不存在则返回 null + */ + public DataPermission getDataPermission(String mapperId) { + // 检查上下文中是否包含映射语句 ID 对应的 DataPermission 注解对象 + if (DataPermissionHelper.getPermission() != null) { + return DataPermissionHelper.getPermission(); + } + // 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象 + if (dataPermissionCacheMap.containsKey(mapperId)) { + return dataPermissionCacheMap.get(mapperId); + } + // 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找 + String clazzName = mapperId.substring(0, mapperId.lastIndexOf(".")); + if (dataPermissionCacheMap.containsKey(clazzName)) { + return dataPermissionCacheMap.get(clazzName); + } + return null; + } + + /** + * 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象 + * + * @param mapperId 映射语句 ID + * @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true + */ + public boolean invalid(String mapperId) { + return getDataPermission(mapperId) == null; + } + + /** + * 对所有null变量找不到的变量返回默认值 + */ + @AllArgsConstructor + private static class NullSafeStandardEvaluationContext extends StandardEvaluationContext { + + private final Object defaultValue; + + @Override + public Object lookupVariable(String name) { + Object obj = super.lookupVariable(name); + // 如果读取到的值是 null,则返回默认值 + if (obj == null) { + return defaultValue; + } + return obj; + } + + } + + /** + * 对所有null变量找不到的变量返回默认值 委托模式 将不需要处理的方法委托给原处理器 + */ + @AllArgsConstructor + private static class NullSafePropertyAccessor implements PropertyAccessor { + + private final PropertyAccessor delegate; + private final Object defaultValue; + + @Override + public Class[] getSpecificTargetClasses() { + return delegate.getSpecificTargetClasses(); + } + + @Override + public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { + return delegate.canRead(context, target, name); + } + + @Override + public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { + TypedValue value = delegate.read(context, target, name); + // 如果读取到的值是 null,则返回默认值 + if (value.getValue() == null) { + return new TypedValue(defaultValue); + } + return value; + } + + @Override + public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { + return delegate.canWrite(context, target, name); + } + + @Override + public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException { + delegate.write(context, target, name, newValue); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/PlusPostInitTableInfoHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/PlusPostInitTableInfoHandler.java new file mode 100644 index 00000000..aac6bb55 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/PlusPostInitTableInfoHandler.java @@ -0,0 +1,28 @@ +package org.ruoyi.handler; + +import cn.hutool.core.convert.Convert; +import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import org.apache.ibatis.session.Configuration; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.reflect.ReflectUtils; + + +/** + * 修改表信息初始化方式 + * 目前用于全局修改是否使用逻辑删除 + * + * @author Lion Li + */ +public class PlusPostInitTableInfoHandler implements PostInitTableInfoHandler { + + @Override + public void postTableInfo(TableInfo tableInfo, Configuration configuration) { + String flag = SpringUtils.getProperty("mybatis-plus.enableLogicDelete", "true"); + // 只有关闭时 统一设置false 为true时mp自动判断不处理 + if (!Convert.toBool(flag)) { + ReflectUtils.setFieldValue(tableInfo, "withLogicDelete", false); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataBaseHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/helper/DataBaseHelper.java similarity index 91% rename from ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataBaseHelper.java rename to ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/helper/DataBaseHelper.java index f060e17a..16a16375 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataBaseHelper.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/helper/DataBaseHelper.java @@ -1,12 +1,13 @@ -package org.ruoyi.common.mybatis.helper; +package org.ruoyi.helper; import cn.hutool.core.convert.Convert; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import lombok.AccessLevel; import lombok.NoArgsConstructor; + import org.ruoyi.common.core.exception.ServiceException; import org.ruoyi.common.core.utils.SpringUtils; -import org.ruoyi.common.mybatis.enums.DataBaseType; +import org.ruoyi.enums.DataBaseType; import javax.sql.DataSource; import java.sql.Connection; @@ -62,8 +63,8 @@ public class DataBaseHelper { // charindex(',100,' , ',0,100,101,') <> 0 return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2); } else if (dataBasyType == DataBaseType.POSTGRE_SQL) { - // (select position(',100,' in ',0,100,101,')) <> 0 - return "(select position(',%s,' in ','||%s||',')) <> 0".formatted(var, var2); + // (select strpos(',0,100,101,' , ',100,')) <> 0 + return "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var); } else if (dataBasyType == DataBaseType.ORACLE) { // instr(',0,100,101,' , ',100,') <> 0 return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var); @@ -71,6 +72,7 @@ public class DataBaseHelper { // find_in_set(100 , '0,100,101') return "find_in_set('%s' , %s) <> 0".formatted(var, var2); } + /** * 获取当前加载的数据库名 */ diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/helper/DataPermissionHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/helper/DataPermissionHelper.java new file mode 100644 index 00000000..3d8f99c8 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/helper/DataPermissionHelper.java @@ -0,0 +1,176 @@ +package org.ruoyi.helper; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaStorage; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.ruoyi.annotation.DataPermission; +import org.ruoyi.common.core.utils.reflect.ReflectUtils; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; +import java.util.function.Supplier; + +/** + * 数据权限助手 + * + * @author Lion Li + * @version 3.5.0 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings("unchecked cast") +public class DataPermissionHelper { + + private static final String DATA_PERMISSION_KEY = "data:permission"; + + private static final ThreadLocal> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new); + + private static final ThreadLocal PERMISSION_CACHE = new ThreadLocal<>(); + + /** + * 获取当前执行mapper权限注解 + * + * @return 返回当前执行mapper权限注解 + */ + public static DataPermission getPermission() { + return PERMISSION_CACHE.get(); + } + + /** + * 设置当前执行mapper权限注解 + * + * @param dataPermission 数据权限注解 + */ + public static void setPermission(DataPermission dataPermission) { + PERMISSION_CACHE.set(dataPermission); + } + + /** + * 删除当前执行mapper权限注解 + */ + public static void removePermission() { + PERMISSION_CACHE.remove(); + } + + /** + * 从上下文中获取指定键的变量值,并将其转换为指定的类型 + * + * @param key 变量的键 + * @param 变量值的类型 + * @return 指定键的变量值,如果不存在则返回 null + */ + public static T getVariable(String key) { + Map context = getContext(); + return (T) context.get(key); + } + + /** + * 向上下文中设置指定键的变量值 + * + * @param key 要设置的变量的键 + * @param value 要设置的变量值 + */ + public static void setVariable(String key, Object value) { + Map context = getContext(); + context.put(key, value); + } + + /** + * 获取数据权限上下文 + * + * @return 存储在SaStorage中的Map对象,用于存储数据权限相关的上下文信息 + * @throws NullPointerException 如果数据权限上下文类型异常,则抛出NullPointerException + */ + public static Map getContext() { + SaStorage saStorage = SaHolder.getStorage(); + Object attribute = saStorage.get(DATA_PERMISSION_KEY); + if (ObjectUtil.isNull(attribute)) { + saStorage.set(DATA_PERMISSION_KEY, new HashMap<>()); + attribute = saStorage.get(DATA_PERMISSION_KEY); + } + if (attribute instanceof Map map) { + return map; + } + throw new NullPointerException("data permission context type exception"); + } + + private static IgnoreStrategy getIgnoreStrategy() { + Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL")); + if (ignoreStrategyLocal instanceof ThreadLocal IGNORE_STRATEGY_LOCAL) { + if (IGNORE_STRATEGY_LOCAL.get() instanceof IgnoreStrategy ignoreStrategy) { + return ignoreStrategy; + } + } + return null; + } + + /** + * 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭) + */ + public static void enableIgnore() { + IgnoreStrategy ignoreStrategy = getIgnoreStrategy(); + if (ObjectUtil.isNull(ignoreStrategy)) { + InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build()); + } else { + ignoreStrategy.setDataPermission(true); + } + Stack reentrantStack = REENTRANT_IGNORE.get(); + reentrantStack.push(reentrantStack.size() + 1); + } + + /** + * 关闭忽略数据权限 + */ + public static void disableIgnore() { + IgnoreStrategy ignoreStrategy = getIgnoreStrategy(); + if (ObjectUtil.isNotNull(ignoreStrategy)) { + boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName()) + && !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack()) + && !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql()) + && !Boolean.TRUE.equals(ignoreStrategy.getTenantLine()) + && CollectionUtil.isEmpty(ignoreStrategy.getOthers()); + Stack reentrantStack = REENTRANT_IGNORE.get(); + boolean empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1; + if (noOtherIgnoreStrategy && empty) { + InterceptorIgnoreHelper.clearIgnoreStrategy(); + } else if (empty) { + ignoreStrategy.setDataPermission(false); + } + + } + } + + /** + * 在忽略数据权限中执行 + * + * @param handle 处理执行方法 + */ + public static void ignore(Runnable handle) { + enableIgnore(); + try { + handle.run(); + } finally { + disableIgnore(); + } + } + + /** + * 在忽略数据权限中执行 + * + * @param handle 处理执行方法 + */ + public static T ignore(Supplier handle) { + enableIgnore(); + try { + return handle.get(); + } finally { + disableIgnore(); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/interceptor/PlusDataPermissionInterceptor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/interceptor/PlusDataPermissionInterceptor.java new file mode 100644 index 00000000..10a58e5a --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/interceptor/PlusDataPermissionInterceptor.java @@ -0,0 +1,181 @@ +package org.ruoyi.interceptor; + +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; +import com.baomidou.mybatisplus.core.toolkit.PluginUtils; +import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler; +import com.baomidou.mybatisplus.extension.plugins.inner.BaseMultiTableInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SetOperationList; +import net.sf.jsqlparser.statement.update.Update; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.ruoyi.handler.PlusDataPermissionHandler; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +/** + * 数据权限拦截器 + * + * @author Lion Li + * @version 3.5.0 + */ +@Slf4j +public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor { + + private final PlusDataPermissionHandler dataPermissionHandler; + + /** + * 构造函数,初始化 PlusDataPermissionHandler 实例 + * + * @param mapperPackage 扫描的映射器包 + */ + public PlusDataPermissionInterceptor(String mapperPackage) { + this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage); + } + + /** + * 在执行查询之前,检查并处理数据权限相关逻辑 + * + * @param executor MyBatis 执行器对象 + * @param ms 映射语句对象 + * @param parameter 方法参数 + * @param rowBounds 分页对象 + * @param resultHandler 结果处理器 + * @param boundSql 绑定的 SQL 对象 + * @throws SQLException 如果发生 SQL 异常 + */ + @Override + public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { + // 检查是否需要忽略数据权限处理 + if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { + return; + } + // 检查是否缺少有效的数据权限注解 + if (dataPermissionHandler.invalid(ms.getId())) { + return; + } + // 解析 sql 分配对应方法 + PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); + mpBs.sql(parserSingle(mpBs.sql(), ms.getId())); + } + + /** + * 在准备 SQL 语句之前,检查并处理更新和删除操作的数据权限相关逻辑 + * + * @param sh MyBatis StatementHandler 对象 + * @param connection 数据库连接对象 + * @param transactionTimeout 事务超时时间 + */ + @Override + public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { + PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); + MappedStatement ms = mpSh.mappedStatement(); + // 获取 SQL 命令类型(增、删、改、查) + SqlCommandType sct = ms.getSqlCommandType(); + + // 只处理更新和删除操作的 SQL 语句 + if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { + if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { + return; + } + // 检查是否缺少有效的数据权限注解 + if (dataPermissionHandler.invalid(ms.getId())) { + return; + } + PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); + mpBs.sql(parserMulti(mpBs.sql(), ms.getId())); + } + } + + /** + * 处理 SELECT 查询语句中的 WHERE 条件 + * + * @param select SELECT 查询对象 + * @param index 查询语句的索引 + * @param sql 查询语句 + * @param obj WHERE 条件参数 + */ + @Override + protected void processSelect(Select select, int index, String sql, Object obj) { + if (select instanceof PlainSelect) { + this.setWhere((PlainSelect) select, (String) obj); + } else if (select instanceof SetOperationList setOperationList) { + List - SELECT * FROM test_demo ${ew.customSqlSegment} - - - diff --git a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/package-info.md b/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/package-info.md deleted file mode 100644 index c938b1e5..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/package-info.md +++ /dev/null @@ -1,3 +0,0 @@ -java包使用 `.` 分割 resource 目录使用 `/` 分割 -
-此文件目的 防止文件夹粘连找不到 `xml` 文件 \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/controller/GenController.java b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/controller/GenController.java index f74c0aaf..c0410899 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/controller/GenController.java +++ b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/controller/GenController.java @@ -3,15 +3,15 @@ package org.ruoyi.generator.controller; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; -import org.ruoyi.common.mybatis.helper.DataBaseHelper; +import org.ruoyi.helper.DataBaseHelper; import org.ruoyi.generator.domain.GenTable; import org.ruoyi.generator.domain.GenTableColumn; import org.ruoyi.generator.service.IGenTableService; import org.ruoyi.common.core.domain.R; import org.ruoyi.common.log.annotation.Log; import org.ruoyi.common.log.enums.BusinessType; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.core.page.PageQuery; +import org.ruoyi.core.page.TableDataInfo; import org.ruoyi.common.web.core.BaseController; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; diff --git a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/domain/GenTable.java b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/domain/GenTable.java index 901d124f..1dfa9bca 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/domain/GenTable.java +++ b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/domain/GenTable.java @@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import org.ruoyi.generator.constant.GenConstants; import org.ruoyi.common.core.utils.StringUtils; -import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.core.domain.BaseEntity; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import lombok.Data; diff --git a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/domain/GenTableColumn.java b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/domain/GenTableColumn.java index d2e46114..e96dec61 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/domain/GenTableColumn.java +++ b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/domain/GenTableColumn.java @@ -5,7 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import org.ruoyi.common.core.utils.StringUtils; -import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.core.domain.BaseEntity; import jakarta.validation.constraints.NotBlank; import lombok.Data; import lombok.EqualsAndHashCode; @@ -41,7 +41,7 @@ public class GenTableColumn extends BaseEntity { /** * 列描述 */ - @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR) + @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR) private String columnComment; /** @@ -63,43 +63,43 @@ public class GenTableColumn extends BaseEntity { /** * 是否主键(1是) */ - @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR) + @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR) private String isPk; /** * 是否自增(1是) */ - @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR) + @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR) private String isIncrement; /** * 是否必填(1是) */ - @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR) + @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR) private String isRequired; /** * 是否为插入字段(1是) */ - @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR) + @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR) private String isInsert; /** * 是否编辑字段(1是) */ - @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR) + @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR) private String isEdit; /** * 是否列表字段(1是) */ - @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR) + @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR) private String isList; /** * 是否查询字段(1是) */ - @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR) + @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR) private String isQuery; /** diff --git a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/mapper/GenTableColumnMapper.java b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/mapper/GenTableColumnMapper.java index 888e4cf3..6f431870 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/mapper/GenTableColumnMapper.java +++ b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/mapper/GenTableColumnMapper.java @@ -2,7 +2,7 @@ package org.ruoyi.generator.mapper; import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import org.ruoyi.generator.domain.GenTableColumn; -import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.core.mapper.BaseMapperPlus; import java.util.List; diff --git a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/mapper/GenTableMapper.java b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/mapper/GenTableMapper.java index b03da863..ce7c3cc9 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/mapper/GenTableMapper.java +++ b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/mapper/GenTableMapper.java @@ -2,7 +2,7 @@ package org.ruoyi.generator.mapper; import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.core.mapper.BaseMapperPlus; import org.ruoyi.generator.domain.GenTable; import org.apache.ibatis.annotations.Param; diff --git a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/service/GenTableServiceImpl.java b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/service/GenTableServiceImpl.java index f04f3cda..124dd115 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/service/GenTableServiceImpl.java +++ b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/service/GenTableServiceImpl.java @@ -17,8 +17,8 @@ import org.ruoyi.common.core.utils.StreamUtils; import org.ruoyi.common.core.utils.StringUtils; import org.ruoyi.common.core.utils.file.FileUtils; import org.ruoyi.common.json.utils.JsonUtils; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.core.page.PageQuery; +import org.ruoyi.core.page.TableDataInfo; import org.ruoyi.common.satoken.utils.LoginHelper; import org.ruoyi.generator.constant.GenConstants; import org.ruoyi.generator.domain.GenTable; diff --git a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/service/IGenTableService.java b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/service/IGenTableService.java index 5f885489..7ff208f4 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/service/IGenTableService.java +++ b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/service/IGenTableService.java @@ -1,7 +1,7 @@ package org.ruoyi.generator.service; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.core.page.PageQuery; +import org.ruoyi.core.page.TableDataInfo; import org.ruoyi.generator.domain.GenTable; import org.ruoyi.generator.domain.GenTableColumn; diff --git a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/util/VelocityUtils.java b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/util/VelocityUtils.java index eb52c85e..6d0e68a0 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/util/VelocityUtils.java +++ b/ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/util/VelocityUtils.java @@ -10,7 +10,7 @@ import org.ruoyi.generator.domain.GenTableColumn; import org.ruoyi.common.core.utils.DateUtils; import org.ruoyi.common.core.utils.StringUtils; import org.ruoyi.common.json.utils.JsonUtils; -import org.ruoyi.common.mybatis.helper.DataBaseHelper; +import org.ruoyi.helper.DataBaseHelper; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.apache.velocity.VelocityContext; diff --git a/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml index 3dfd97c5..f4e49357 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml +++ b/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml @@ -8,7 +8,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + select table_name, table_comment, create_time, update_time from information_schema.tables where table_schema = (select database()) @@ -29,7 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by create_time desc - + select lower(dt.table_name) as table_name, dtc.comments as table_comment, uo.created as create_time, uo.last_ddl_time as update_time from user_tables dt, user_tab_comments dtc, user_objects uo where dt.table_name = dtc.table_name @@ -45,7 +45,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by create_time desc - + select table_name, table_comment, create_time, update_time from ( SELECT c.relname AS table_name, @@ -69,7 +69,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by create_time desc - + SELECT cast(D.NAME as nvarchar) as table_name, cast(F.VALUE as nvarchar) as table_comment, crdate as create_time, @@ -90,7 +90,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - SELECT - user_id, - cover_id, - prompt_audio_url - FROM - chat_cover_prompt_audio - WHERE - cover_id = ( SELECT cover_id FROM chat_cover_prompt_audio WHERE user_id = #{userId} ORDER BY create_time DESC LIMIT 1 ) - - diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/PaymentOrdersMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/PaymentOrdersMapper.xml deleted file mode 100644 index d1ffcf60..00000000 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/PaymentOrdersMapper.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml deleted file mode 100644 index 712c9a87..00000000 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPackagePlanMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPackagePlanMapper.xml deleted file mode 100644 index 1b7c4a1f..00000000 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPackagePlanMapper.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobConfigMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobConfigMapper.xml deleted file mode 100644 index c7af5bf3..00000000 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobConfigMapper.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobKeywordMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobKeywordMapper.xml deleted file mode 100644 index 32582d17..00000000 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobKeywordMapper.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobRelationMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobRelationMapper.xml deleted file mode 100644 index aab96635..00000000 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobRelationMapper.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/script/docker/database.yml b/script/docker/database.yml deleted file mode 100644 index 6d37c803..00000000 --- a/script/docker/database.yml +++ /dev/null @@ -1,61 +0,0 @@ -version: '3' - -services: - # 此镜像仅用于测试 正式环境需自行安装数据库 - # SID: XE user: system password: oracle - oracle: - image: tekintian/oracle12c:latest - container_name: oracle - environment: - # 时区上海 - TZ: Asia/Shanghai - DBCA_TOTAL_MEMORY: 16192 - ports: - - "18080:8080" - - "1521:1521" - volumes: - # 数据挂载 - - "/docker/oracle/data:/u01/app/oracle" - network_mode: "host" - - # 此镜像仅用于测试 正式环境需自行安装数据库 - sqlserver: - image: mcr.microsoft.com/mssql/server:2017-latest - container_name: sqlserver - environment: - # 时区上海 - TZ: Asia/Shanghai - ACCEPT_EULA: "Y" - SA_PASSWORD: "ruoyi@123" - ports: - - "1433:1433" - volumes: - # 数据挂载 - - "/docker/sqlserver/data:/var/opt/mssql" - network_mode: "host" - - postgres: - image: postgres:14.2 - container_name: postgres - environment: - POSTGRES_USER: root - POSTGRES_PASSWORD: root - POSTGRES_DB: postgres - ports: - - "5432:5432" - volumes: - - /docker/postgres/data:/var/lib/postgresql/data - network_mode: "host" - - postgres13: - image: postgres:13.6 - container_name: postgres13 - environment: - POSTGRES_USER: root - POSTGRES_PASSWORD: root - POSTGRES_DB: postgres - ports: - - "5433:5432" - volumes: - - /docker/postgres13/data:/var/lib/postgresql/data - network_mode: "host" diff --git a/script/docker/docker-compose.yml b/script/docker/docker-compose.yml deleted file mode 100644 index 0e1d01df..00000000 --- a/script/docker/docker-compose.yml +++ /dev/null @@ -1,64 +0,0 @@ -version: '3' - -services: - mysql: - image: mysql:8.0.33 - container_name: mysql - environment: - TZ: Asia/Shanghai - MYSQL_ROOT_PASSWORD: root - MYSQL_DATABASE: ry-vue - ports: - - "3307:3306" - volumes: - - ./ry-vue.sql:/docker-entrypoint-initdb.d/ry-vue.sql - command: - --default-authentication-plugin=mysql_native_password - --character-set-server=utf8mb4 - --collation-server=utf8mb4_general_ci - --explicit_defaults_for_timestamp=true - --lower_case_table_names=1 - - ruoyi-server: - image: registry.cn-shanghai.aliyuncs.com/ruoyi-ai/ai:1.2.1 - ports: - - "6039:6039" - container_name: ruoyi-server - environment: - TZ: Asia/Shanghai - # 运行端口号 - SERVER_PORT: 6039 - # 数据库连接地址 - DB_URL: jdbc:mysql://mysql:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true - # 数据库用户名 - DB_USERNAME: root - # 数据库用户密码 - DB_PASSWORD: root - # Redis地址 - REDIS_HOST: redis - # Redis端口 - REDIS_PORT: 6379 - # 数据库索引 - REDIS_DATABASE: 0 - # Redis密码 - REDIS_PASSWORD: - # 连接超时时间 - REDIS_TIMEOUT: 10s - volumes: - - /docker/server2/logs/:/ruoyi/server/logs/ - - ruoyi-web: - image: registry.cn-shanghai.aliyuncs.com/ruoyi-ai/web:1.2.1 - ports: - - "8081:8081" - container_name: ruoyi-web - - - ruoyi-admin: - image: registry.cn-shanghai.aliyuncs.com/ruoyi-ai/admin:1.2.1 - ports: - - "8082:8082" - container_name: ruoyi-admin - - - diff --git a/script/docker/localModels/Dockerfile b/script/docker/localModels/Dockerfile deleted file mode 100644 index 0f1e6063..00000000 --- a/script/docker/localModels/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# 使用 Python 3.8 slim 作为基础镜像(轻量稳定) -FROM python:3.10 - -# 设置工作目录 -WORKDIR /app - -# 复制所有文件到容器 -COPY . /app - -# 更新 pip 并安装 wheel(避免依赖问题) -RUN pip install --upgrade pip wheel - -# 使用阿里云 PyPI 镜像安装依赖,并添加 trusted-host -RUN pip install -i https://mirrors.aliyun.com/pypi/simple \ - --trusted-host mirrors.aliyun.com \ - --no-cache-dir -r requirements.txt - -# 暴露 Flask 端口 -EXPOSE 5000 - -# 设置环境变量 -ENV FLASK_APP=app.py -ENV FLASK_RUN_HOST=0.0.0.0 - -# 运行 Flask -CMD ["flask", "run", "--host=0.0.0.0"] diff --git a/script/docker/localModels/app.py b/script/docker/localModels/app.py deleted file mode 100644 index 645a9b43..00000000 --- a/script/docker/localModels/app.py +++ /dev/null @@ -1,116 +0,0 @@ -from flask import Flask, request, jsonify -from sentence_transformers import SentenceTransformer -from sklearn.metrics.pairwise import cosine_similarity -import json - -app = Flask(__name__) - -# 创建一个全局的模型缓存字典 -model_cache = {} - -# 分割文本块 -def split_text(text, block_size, overlap_chars, delimiter): - chunks = text.split(delimiter) - text_blocks = [] - current_block = "" - - for chunk in chunks: - if len(current_block) + len(chunk) + 1 <= block_size: - if current_block: - current_block += " " + chunk - else: - current_block = chunk - else: - text_blocks.append(current_block) - current_block = chunk - if current_block: - text_blocks.append(current_block) - - overlap_blocks = [] - for i in range(len(text_blocks)): - if i > 0: - overlap_block = text_blocks[i - 1][-overlap_chars:] + text_blocks[i] - overlap_blocks.append(overlap_block) - overlap_blocks.append(text_blocks[i]) - - return overlap_blocks - -# 文本向量化 -def vectorize_text_blocks(text_blocks, model): - return model.encode(text_blocks) - -# 文本检索 -def retrieve_top_k(query, knowledge_base, k, block_size, overlap_chars, delimiter, model): - # 将知识库拆分为文本块 - text_blocks = split_text(knowledge_base, block_size, overlap_chars, delimiter) - # 向量化文本块 - knowledge_vectors = vectorize_text_blocks(text_blocks, model) - # 向量化查询文本 - query_vector = model.encode([query]).reshape(1, -1) - # 计算相似度 - similarities = cosine_similarity(query_vector, knowledge_vectors) - # 获取相似度最高的 k 个文本块的索引 - top_k_indices = similarities[0].argsort()[-k:][::-1] - - # 返回文本块和它们的向量 - top_k_texts = [text_blocks[i] for i in top_k_indices] - top_k_embeddings = [knowledge_vectors[i] for i in top_k_indices] - - return top_k_texts, top_k_embeddings - -@app.route('/vectorize', methods=['POST']) -def vectorize_text(): - # 从请求中获取 JSON 数据 - data = request.json - print(f"Received request data: {data}") # 调试输出请求数据 - - text_list = data.get("text", []) - model_name = data.get("model_name", "msmarco-distilbert-base-tas-b") # 默认模型 - - delimiter = data.get("delimiter", "\n") # 默认分隔符 - k = int(data.get("k", 3)) # 默认检索条数 - block_size = int(data.get("block_size", 500)) # 默认文本块大小 - overlap_chars = int(data.get("overlap_chars", 50)) # 默认重叠字符数 - - if not text_list: - return jsonify({"error": "Text is required."}), 400 - - # 检查模型是否已经加载 - if model_name not in model_cache: - try: - model = SentenceTransformer(model_name) - model_cache[model_name] = model # 缓存模型 - except Exception as e: - return jsonify({"error": f"Failed to load model: {e}"}), 500 - - model = model_cache[model_name] - - top_k_texts_all = [] - top_k_embeddings_all = [] - - # 如果只有一个查询文本 - if len(text_list) == 1: - top_k_texts, top_k_embeddings = retrieve_top_k(text_list[0], text_list[0], k, block_size, overlap_chars, delimiter, model) - top_k_texts_all.append(top_k_texts) - top_k_embeddings_all.append(top_k_embeddings) - elif len(text_list) > 1: - # 如果多个查询文本,依次处理 - for query in text_list: - top_k_texts, top_k_embeddings = retrieve_top_k(query, text_list[0], k, block_size, overlap_chars, delimiter, model) - top_k_texts_all.append(top_k_texts) - top_k_embeddings_all.append(top_k_embeddings) - - # 将嵌入向量(ndarray)转换为可序列化的列表 - top_k_embeddings_all = [[embedding.tolist() for embedding in embeddings] for embeddings in top_k_embeddings_all] - - print(f"Top K texts: {top_k_texts_all}") # 打印检索到的文本 - print(f"Top K embeddings: {top_k_embeddings_all}") # 打印检索到的向量 - - # 返回 JSON 格式的数据 - return jsonify({ - - "topKEmbeddings": top_k_embeddings_all # 返回嵌入向量 - }) - -if __name__ == '__main__': - app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/script/docker/localModels/remade.md b/script/docker/localModels/remade.md deleted file mode 100644 index d6af3d2b..00000000 --- a/script/docker/localModels/remade.md +++ /dev/null @@ -1,4 +0,0 @@ -1.下载镜像 -docker pull registry.cn-hangzhou.aliyuncs.com/hejh-docker/localmodel:0.1.1 -2. 启动 -docker run -p 5000:5000 \ No newline at end of file diff --git a/script/docker/localModels/requirements.txt b/script/docker/localModels/requirements.txt deleted file mode 100644 index 2d62483a..00000000 --- a/script/docker/localModels/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -Flask==3.1.0 -sentence-transformers==3.4.1 -scikit-learn==1.6.1 -werkzeug>=3.1 - diff --git a/script/docker/nginx/conf/nginx.conf b/script/docker/nginx/conf/nginx.conf deleted file mode 100644 index e9630aaf..00000000 --- a/script/docker/nginx/conf/nginx.conf +++ /dev/null @@ -1,111 +0,0 @@ -worker_processes 1; - -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - -events { - worker_connections 1024; -} - -http { - include mime.types; - default_type application/octet-stream; - sendfile on; - keepalive_timeout 65; - # 限制body大小 - client_max_body_size 100m; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - upstream server { - ip_hash; - server 127.0.0.1:8080; - server 127.0.0.1:8081; - } - - upstream monitor-admin { - server 127.0.0.1:9090; - } - - upstream xxljob-admin { - server 127.0.0.1:9100; - } - - server { - listen 80; - server_name localhost; - - # https配置参考 start - #listen 443 ssl; - - # 证书直接存放 /docker/nginx/cert/ 目录下即可 更改证书名称即可 无需更改证书路径 - #ssl on; - #ssl_certificate /etc/nginx/cert/xxx.local.crt; # /etc/nginx/cert/ 为docker映射路径 不允许更改 - #ssl_certificate_key /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 为docker映射路径 不允许更改 - #ssl_session_timeout 5m; - #ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; - #ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - #ssl_prefer_server_ciphers on; - # https配置参考 end - - # 演示环境配置 拦截除 GET POST 之外的所有请求 - # if ($request_method !~* GET|POST) { - # rewrite ^/(.*)$ /403; - # } - - # location = /403 { - # default_type application/json; - # return 200 '{"msg":"演示模式,不允许操作","code":500}'; - # } - - # 限制外网访问内网 actuator 相关路径 - location ~ ^(/[^/]*)?/actuator(/.*)?$ { - return 403; - } - - location / { - root /usr/share/nginx/html; - try_files $uri $uri/ /index.html; - index index.html index.htm; - } - - location /prod-api/ { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header REMOTE-HOST $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://server/; - } - - # https 会拦截内链所有的 http 请求 造成功能无法使用 - # 解决方案1 将 admin 服务 也配置成 https - # 解决方案2 将菜单配置为外链访问 走独立页面 http 访问 - location /admin/ { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header REMOTE-HOST $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://monitor-admin/admin/; - } - - # https 会拦截内链所有的 http 请求 造成功能无法使用 - # 解决方案1 将 xxljob 服务 也配置成 https - # 解决方案2 将菜单配置为外链访问 走独立页面 http 访问 - location /xxl-job-admin/ { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header REMOTE-HOST $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://xxljob-admin/xxl-job-admin/; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - } -} diff --git a/script/docker/redis/conf/redis.conf b/script/docker/redis/conf/redis.conf deleted file mode 100644 index 72255c61..00000000 --- a/script/docker/redis/conf/redis.conf +++ /dev/null @@ -1,28 +0,0 @@ -# redis 密码 -requirepass ruoyi123 - -# key 监听器配置 -# notify-keyspace-events Ex - -# 配置持久化文件存储路径 -dir /redis/data -# 配置rdb -# 15分钟内有至少1个key被更改则进行快照 -save 900 1 -# 5分钟内有至少10个key被更改则进行快照 -save 300 10 -# 1分钟内有至少10000个key被更改则进行快照 -save 60 10000 -# 开启压缩 -rdbcompression yes -# rdb文件名 用默认的即可 -dbfilename dump.rdb - -# 开启aof -appendonly yes -# 文件名 -appendfilename "appendonly.aof" -# 持久化策略,no:不同步,everysec:每秒一次,always:总是同步,速度比较慢 -# appendfsync always -appendfsync everysec -# appendfsync no diff --git a/script/docker/redis/data/README.md b/script/docker/redis/data/README.md deleted file mode 100644 index fbc5474b..00000000 --- a/script/docker/redis/data/README.md +++ /dev/null @@ -1 +0,0 @@ -数据目录 请执行 `chmod 777 /docker/redis/data` 赋予读写权限 否则将无法写入数据 \ No newline at end of file diff --git a/script/sql/update/updatdata20250402.sql b/script/sql/update/updatdata20250402.sql index 1a8f4b3d..940ace59 100644 --- a/script/sql/update/updatdata20250402.sql +++ b/script/sql/update/updatdata20250402.sql @@ -1 +1 @@ -INSERT INTO `ry-vue`.`chat_app_store` (`id`, `name`, `description`, `avatar`, `app_url`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (7, '微信机器人', '微信机器人', 'https://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2025/04/02/0557a7d68fa842bba952ce0d6ef38a2e.png', '/wxbot', NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `chat_app_store` (`id`, `name`, `description`, `avatar`, `app_url`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (7, '微信机器人', '微信机器人', 'https://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2025/04/02/0557a7d68fa842bba952ce0d6ef38a2e.png', '/wxbot', NULL, NULL, NULL, NULL, NULL, NULL); diff --git a/script/sql/update/updatdata20250407-2.sql b/script/sql/update/updatdata20250407-2.sql deleted file mode 100644 index 8ba99113..00000000 --- a/script/sql/update/updatdata20250407-2.sql +++ /dev/null @@ -1,14 +0,0 @@ -SET FOREIGN_KEY_CHECKS=0; - -ALTER TABLE `ruoyi-org`.`chat_gpts` ADD COLUMN `model_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型名称' AFTER `tenant_id`; - -ALTER TABLE `ruoyi-org`.`chat_gpts` ADD COLUMN `system_prompt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '系统提示词' AFTER `model_name`; - -INSERT INTO `ruoyi-ai`.`chat_gpts` (`id`, `gid`, `name`, `logo`, `info`, `author_id`, `author_name`, `use_cnt`, `bad`, `type`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`, `model_name`, `system_prompt`) VALUES (1810602934286237698, 'gpt-4-gizmo-g-RQAWjtI6u', '翻译助手', 'https://external-content.duckduckgo.com/ip3/chat.openai.com.ico', '中英和英中翻译专家', 'winkey', 'winkey', 0, 0, 'vector', 103, '2024-07-09 17:12:34', '1', '1', '2025-04-07 21:44:11', 'Ms. Smith, the AI-powered Language Teacher, is a revolutionary GPT-based bot that offers personalized language learning experiences in over 20 languages, including Spanish, German, French, English, Chinese, Korean, Japanese, and more\n', NULL, '0', '127.0.0.1', 0, 'deepseek-r1:1.5b', '你是一位精通各国语言的翻译大师\r\n\r\n请将用户输入词语翻译成英文或中文\r\n\r\n==示例输出==\r\n**原文** : <这里显示要翻译的原文信息>\r\n**翻译** : <这里显示翻译成英语的结果>\r\n==示例结束==\r\n\r\n注意:请严格按示例进行输出,返回markdown格式'); -INSERT INTO `ruoyi-ai`.`chat_gpts` (`id`, `gid`, `name`, `logo`, `info`, `author_id`, `author_name`, `use_cnt`, `bad`, `type`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`, `model_name`, `system_prompt`) VALUES (1811668415990931458, 'gpt-4-gizmo-g-XbReEL4Uq', '清北全科医生', 'https://external-content.duckduckgo.com/ip3/chat.openai.com.ico', '富有同情心的全科医生提供健康指导', NULL, NULL, 0, 0, NULL, 103, '2024-07-12 15:46:24', '1', '1', '2024-07-12 15:46:24', NULL, NULL, '0', NULL, 0, 'deepseek-r1:1.5b', '富有同情心的全科医生提供健康指导'); -INSERT INTO `ruoyi-ai`.`chat_gpts` (`id`, `gid`, `name`, `logo`, `info`, `author_id`, `author_name`, `use_cnt`, `bad`, `type`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`, `model_name`, `system_prompt`) VALUES (1811670922074988545, 'gpt-4-gizmo-g-AphhNRLxt', '提示词优化', 'https://external-content.duckduckgo.com/ip3/chat.openai.com.ico', '擅长为Prompt 提升清晰度和创造力的大师', NULL, NULL, 0, 0, NULL, 103, '2024-07-12 15:56:22', '1', '1', '2024-07-12 15:56:22', NULL, NULL, '0', NULL, 0, 'deepseek-r1:1.5b', '擅长为Prompt 提升清晰度和创造力的大师'); -INSERT INTO `ruoyi-ai`.`chat_gpts` (`id`, `gid`, `name`, `logo`, `info`, `author_id`, `author_name`, `use_cnt`, `bad`, `type`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`, `model_name`, `system_prompt`) VALUES (1811815442062188545, 'gpt-4-gizmo-g-ThuHxKi7e', '小红书文案生成器', 'https://external-content.duckduckgo.com/ip3/chat.openai.com.ico', '小红书文案生成器', NULL, NULL, 0, 0, NULL, 103, '2024-07-13 01:30:38', '1', '1', '2024-07-13 01:30:38', NULL, NULL, '0', NULL, 0, 'deepseek-r1:1.5b', '小红书文案生成器'); -INSERT INTO `ruoyi-ai`.`chat_gpts` (`id`, `gid`, `name`, `logo`, `info`, `author_id`, `author_name`, `use_cnt`, `bad`, `type`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`, `model_name`, `system_prompt`) VALUES (1811817605668741121, 'gpt-4-gizmo-g-AsQCd3k8', '中国法律助手', 'https://external-content.duckduckgo.com/ip3/chat.openai.com.ico', '全面掌握中国法律的智能助手,可帮助起草文书,分析案件,进行法律咨询', NULL, NULL, 0, 0, NULL, 103, '2024-07-13 01:39:14', '1', '1', '2024-07-13 01:39:14', NULL, NULL, '2', NULL, 0, 'deepseek-r1:1.5b', '全面掌握中国法律的智能助手,可帮助起草文书,分析案件,进行法律咨询'); -INSERT INTO `ruoyi-ai`.`chat_gpts` (`id`, `gid`, `name`, `logo`, `info`, `author_id`, `author_name`, `use_cnt`, `bad`, `type`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`, `model_name`, `system_prompt`) VALUES (1811817605668741122, 'gpt-4-gizmo-g-IXwub6dJu', '英语老师', 'https://external-content.duckduckgo.com/ip3/chat.openai.com.ico', '英语学习GPT是一个专门设计来帮助用户提高他们的英语技能的人工智能助手', NULL, NULL, 0, 0, NULL, NULL, NULL, '', '', NULL, NULL, NULL, '0', NULL, 0, 'deepseek-r1:1.5b', '英语学习GPT是一个专门设计来帮助用户提高他们的英语技能的人工智能助手'); - -SET FOREIGN_KEY_CHECKS=1; \ No newline at end of file diff --git a/script/sql/update/updatdata20250407.sql b/script/sql/update/updatdata20250407.sql index a40a8396..151fddce 100644 --- a/script/sql/update/updatdata20250407.sql +++ b/script/sql/update/updatdata20250407.sql @@ -1,4 +1,4 @@ -INSERT INTO `ruoyi-ai`.`chat_model` (`id`, `tenant_id`, `category`, `model_name`, `model_describe`, `model_price`, `model_type`, `model_show`, `system_prompt`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1907575746601119746, '000000', 'vector', 'text-embedding-3-small', 'text-embedding-3-small', 0, '2', '0', NULL, 'https://api.pandarobot.chat/', 'sk-cdBlIaZcufccm2RaDe547cBd054d49C7B0782eCa72A0052b', 103, 1, '2025-04-03 07:27:54', 1, '2025-04-03 07:27:54', 'text-embedding-3-small'); -INSERT INTO `ruoyi-ai`.`chat_model` (`id`, `tenant_id`, `category`, `model_name`, `model_describe`, `model_price`, `model_type`, `model_show`, `system_prompt`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1907576007017066497, '000000', 'vector', 'quentinz/bge-large-zh-v1.5', 'bge-large-zh-v1.5', 0, '2', '0', NULL, 'https://api.pandarobot.chat/', 'cdBlIaZcufccm2RaDe547cBd054d49C7B0782eCa72A0052b', 103, 1, '2025-04-03 07:28:56', 1, '2025-04-03 07:28:56', 'bge-large-zh-v1.5'); -INSERT INTO `ruoyi-ai`.`chat_model` (`id`, `tenant_id`, `category`, `model_name`, `model_describe`, `model_price`, `model_type`, `model_show`, `system_prompt`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1907576806191362049, '000000', 'vector', 'nomic-embed-text', 'nomic-embed-text', 0, '2', '0', NULL, 'http://127.0.0.1:11434/', 'nomic-embed-text', 103, 1, '2025-04-03 07:32:06', 1, '2025-04-03 07:32:06', 'nomic-embed-text'); -INSERT INTO `ruoyi-ai`.`chat_model` (`id`, `tenant_id`, `category`, `model_name`, `model_describe`, `model_price`, `model_type`, `model_show`, `system_prompt`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1907577073490161665, '000000', 'vector', 'snowflake-arctic-embed', 'snowflake-arctic-embed', 0, '2', '0', NULL, 'http://127.0.0.1:11434/', 'snowflake-arctic-embed', 103, 1, '2025-04-03 07:33:10', 1, '2025-04-03 07:33:10', 'snowflake-arctic-embed'); +INSERT INTO `chat_model` (`id`, `tenant_id`, `category`, `model_name`, `model_describe`, `model_price`, `model_type`, `model_show`, `system_prompt`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1907575746601119746, '000000', 'vector', 'text-embedding-3-small', 'text-embedding-3-small', 0, '2', '0', NULL, 'https://api.pandarobot.chat/', 'sk-cdBlIaZcufccm2RaDe547cBd054d49C7B0782eCa72A0052b', 103, 1, '2025-04-03 07:27:54', 1, '2025-04-03 07:27:54', 'text-embedding-3-small'); +INSERT INTO `chat_model` (`id`, `tenant_id`, `category`, `model_name`, `model_describe`, `model_price`, `model_type`, `model_show`, `system_prompt`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1907576007017066497, '000000', 'vector', 'quentinz/bge-large-zh-v1.5', 'bge-large-zh-v1.5', 0, '2', '0', NULL, 'https://api.pandarobot.chat/', 'cdBlIaZcufccm2RaDe547cBd054d49C7B0782eCa72A0052b', 103, 1, '2025-04-03 07:28:56', 1, '2025-04-03 07:28:56', 'bge-large-zh-v1.5'); +INSERT INTO `chat_model` (`id`, `tenant_id`, `category`, `model_name`, `model_describe`, `model_price`, `model_type`, `model_show`, `system_prompt`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1907576806191362049, '000000', 'vector', 'nomic-embed-text', 'nomic-embed-text', 0, '2', '0', NULL, 'http://127.0.0.1:11434/', 'nomic-embed-text', 103, 1, '2025-04-03 07:32:06', 1, '2025-04-03 07:32:06', 'nomic-embed-text'); +INSERT INTO `chat_model` (`id`, `tenant_id`, `category`, `model_name`, `model_describe`, `model_price`, `model_type`, `model_show`, `system_prompt`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1907577073490161665, '000000', 'vector', 'snowflake-arctic-embed', 'snowflake-arctic-embed', 0, '2', '0', NULL, 'http://127.0.0.1:11434/', 'snowflake-arctic-embed', 103, 1, '2025-04-03 07:33:10', 1, '2025-04-03 07:33:10', 'snowflake-arctic-embed');