first commit

This commit is contained in:
ye1023
2026-04-09 15:12:08 +08:00
commit d94e9c9bc4
1980 changed files with 709719 additions and 0 deletions
@@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-module</artifactId>
<version>3.8.0</version>
</parent>
<artifactId>jeecg-boot-module-airag</artifactId>
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jeecg</id>
<name>jeecg Repository</name>
<url>https://maven.jeecg.org/nexus/content/repositories/jeecg</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<properties>
<kotlin.version>1.6.21</kotlin.version>
<liteflow.version>2.12.4.1</liteflow.version>
<langchain4j.version>0.35.0</langchain4j.version>
<apache-tika.version>2.9.1</apache-tika.version>
</properties>
<dependencies>
<!-- system单体 api-->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-system-local-api</artifactId>
</dependency>
<!-- 微服务starter和system微服务 api
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-starter-cloud</artifactId>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-system-cloud-api</artifactId>
</dependency>-->
<!-- aiflow核心引擎 -->
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>ai-flow</artifactId>
<version>1.1.0</version>
</dependency>
<!-- liteflow -->
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>${liteflow.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-lang</artifactId>
<groupId>commons-lang</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-rule-sql</artifactId>
<version>${liteflow.version}</version>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-graaljs</artifactId>
<version>${liteflow.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-groovy</artifactId>
<version>${liteflow.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-kotlin</artifactId>
<version>${liteflow.version}</version>
<exclusions>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-scripting-jsr223</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-scripting-jsr223</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-aviator</artifactId>
<version>${liteflow.version}</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<artifactId>aviator</artifactId>
<groupId>com.googlecode.aviator</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- langChain4j model support -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-zhipu-ai</artifactId>
<version>${langchain4j.version}</version>
<exclusions>
<exclusion>
<artifactId>checker-qual</artifactId>
<groupId>org.checkerframework</groupId>
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-qianfan</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-dashscope</artifactId>
<version>${langchain4j.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<artifactId>okio</artifactId>
<groupId>com.squareup.okio</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- langChain4j vextor support -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-pgvector</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- langChain4j Document Parser -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>${apache-tika.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parser-html-module</artifactId>
<version>${apache-tika.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parser-pdf-module</artifactId>
<version>${apache-tika.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parser-text-module</artifactId>
<version>${apache-tika.version}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,12 @@
//package org.jeecg;
//
//import org.springframework.boot.SpringApplication;
//import org.springframework.boot.autoconfigure.SpringBootApplication;
//
//@SpringBootApplication
//public class JeecgAiRagApplication {
//
// public static void main(String[] args) {
// SpringApplication.run(JeecgAiRagApplication.class, args);
// }
//}
@@ -0,0 +1,41 @@
package org.jeecg.modules.airag.app.consts;
/**
* AI应用常量类
*
* @author chenrui
* @date 2025/2/25 14:52
*/
public class AiAppConsts {
/**
* 状态:启用
*/
public static final String STATUS_ENABLE = "enable";
/**
* 状态:禁用
*/
public static final String STATUS_DISABLE = "disable";
/**
* 状态:发布
*/
public static final String STATUS_RELEASE = "release";
/**
* 默认应用id
*/
public static final String DEFAULT_APP_ID = "default";
/**
* 应用类型:简单聊天
*/
public static final String APP_TYPE_CHAT_SIMPLE = "chatSimple";
/**
* 应用类型:聊天流(高级编排)
*/
public static final String APP_TYPE_CHAT_FLOW = "chatFLow";
}
@@ -0,0 +1,107 @@
package org.jeecg.modules.airag.app.consts;
/**
* @Description: 提示词常量
* @Author: chenrui
* @Date: 2025/3/12 15:03
*/
public class Prompts {
/**
* 根据提示生成智能体提示词
*/
public static final String GENERATE_LLM_PROMPT = "# 角色\n" +
"你是一位专业且高效的AI提示词工程师,擅长根据用户多样化需求自动生成高质量的结构化提示词模板,具备全面而敏锐的分析能力和出色的创造力。\n" +
"## 要求:\n" +
"1. \"\"\"只输出提示词,不要输出多余解释\"\"\"\n" +
"2. \"\"\"不要在前后增加代码块的md语法.\"\"\"\n" +
"2. 贴合用户需求,描述智能助手的定位、能力、知识储备\n" +
"3. 提示词应清晰、精确、易于理解,在保持质量的同时,尽可能简洁\n" +
"4. 严格按照给定的流程和格式执行任务,确保输出规范准确。\n" +
"\n" +
"## 流程\n" +
"### 1: 需求分析\n" +
"1. 当用户描述需求时,严格运用SCQA框架确认核心要素,精准分析和联想:\"当前场景(Situation)是什么?主要矛盾(Complication)有哪些?需要解决的关键问题(Question)是?预期达成什么效果(Answer)?\"\n" +
"2. 通过5W1H细致分析和联想细节:\"目标受众(Who)?使用场景(Where/When)?具体要实现什么(What)?为什么需要这些特征(Why)?如何量化效果(How)?\"\n" +
"\n" +
"### 2: 框架选择\n" +
"根据需求从给定模板库中匹配最佳提示词类型:\n" +
"* 角色扮演型:\n" +
"```\n" +
"你将扮演一个人物角色<角色名称>,以下是关于这个角色的详细设定,请根据这些信息来构建你的回答。 \n" +
"\n" +
"**人物基本信息:**\n" +
"- 你是:<角色的名称、身份等基本介绍>\n" +
"- 人称:第一人称\n" +
"- 出身背景与上下文:<交代角色背景信息和上下文>\n" +
"**性格特点:**\n" +
"- <性格特点描述>\n" +
"**语言风格:**\n" +
"- <语言风格描述> \n" +
"**人际关系:**\n" +
"- <人际关系描述>\n" +
"**过往经历:**\n" +
"- <过往经历描述>\n" +
"**经典台词或口头禅:**\n" +
"补充信息: 即你可以将动作、神情语气、心理活动、故事背景放在()中来表示,为对话提供补充信息。\n" +
"- 台词1<角色台词示例1> \n" +
"- 台词2<角色台词示例2>\n" +
"- ...\n" +
"\n" +
"要求: \n" +
"- 要求1\n" +
"- 要求2\n" +
"- ... \n" +
"```\n" +
"* 多步骤型:\n" +
"```\n" +
"# 角色 \n" +
"你是<角色设定(比如:xx领域的专家)>\n" +
"你的目标是<希望模型执行什么任务,达成什么目标>\n" +
"\n" +
"{#以下可以采用先总括,再展开详细说明的方式,描述你希望智能体在每一个步骤如何进行工作,具体的工作步骤数量可以根据实际需求增删#}\n" +
"## 工作步骤 \n" +
"1. <工作流程1的一句话概括> \n" +
"2. <工作流程2的一句话概括> \n" +
"3. <工作流程3的一句话概括>\n" +
"\n" +
"### 第一步 <工作流程1标题> \n" +
"<工作流程步骤1的具体工作要求和举例说明,可以分点列出希望在本步骤做哪些事情,需要完成什么阶段性的工作目标>\n" +
"### 第二步 <工作流程2标题> \n" +
"<工作流程步骤2的具体工作要求和举例说明,可以分点列出希望在本步骤做哪些事情,需要完成什么阶段性的工作目标>\n" +
"### 第三步 <工作流程3标题>\n" +
"<工作流程步骤3的具体工作要求和举例说明,可以分点列出希望在本步骤做哪些事情,需要完成什么阶段性的工作目标>\n" +
"```\n" +
"* 限制性模板:\n" +
"```\n" +
"# 角色:<角色名称>\n" +
"<角色概述和主要职责的一句话描述>\n" +
"\n" +
"## 目标:\n" +
"<角色的工作目标,如果有多目标可以分点列出,但建议更聚焦1-2个目标>\n" +
"\n" +
"## 技能:\n" +
"1. <为了实现目标,角色需要具备的技能1>\n" +
"2. <为了实现目标,角色需要具备的技能2>\n" +
"3. <为了实现目标,角色需要具备的技能3>\n" +
"\n" +
"## 工作流:\n" +
"1. <描述角色工作流程的第一步>\n" +
"2. <描述角色工作流程的第二步>\n" +
"3. <描述角色工作流程的第三步>\n" +
"\n" +
"## 输出格式:\n" +
"<如果对角色的输出格式有特定要求,可以在这里强调并举例说明想要的输出格式>\n" +
"\n" +
"## 限制:\n" +
"- <描述角色在互动过程中需要遵循的限制条件1>\n" +
"- <描述角色在互动过程中需要遵循的限制条件2>\n" +
"- <描述角色在互动过程中需要遵循的限制条件3>\n" +
"```\n" +
"\n" +
"### 3: 生成优化\n" +
"1. 输出时自动添加三重保障机制:\n" +
" - 反幻觉校验:\"所有数据需标注来源,不确定信息用[需核实]标记\"\n" +
" - 风格校准器:\"对比[目标风格]与生成内容的余弦相似度,低于0.7时启动重写\"\n" +
" - 伦理审查模块:\"自动过滤涉及隐私/偏见/违法内容,替换为[合规表达]\"";
}
@@ -0,0 +1,181 @@
package org.jeecg.modules.airag.app.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.AssertUtils;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
import org.jeecg.config.shiro.IgnoreAuth;
import org.jeecg.modules.airag.app.consts.AiAppConsts;
import org.jeecg.modules.airag.app.entity.AiragApp;
import org.jeecg.modules.airag.app.service.IAiragAppService;
import org.jeecg.modules.airag.app.service.IAiragChatService;
import org.jeecg.modules.airag.app.vo.AppDebugParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.http.HttpServletRequest;
/**
* @Description: AI应用
* @Author: jeecg-boot
* @Date: 2025-02-26
* @Version: V1.0
*/
@RestController
@RequestMapping("/airag/app")
@Slf4j
public class AiragAppController extends JeecgController<AiragApp, IAiragAppService> {
@Autowired
private IAiragAppService airagAppService;
@Autowired
private IAiragChatService airagChatService;
/**
* 分页列表查询
*
* @param airagApp
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<AiragApp>> queryPageList(AiragApp airagApp,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<AiragApp> queryWrapper = QueryGenerator.initQueryWrapper(airagApp, req.getParameterMap());
Page<AiragApp> page = new Page<AiragApp>(pageNo, pageSize);
IPage<AiragApp> pageList = airagAppService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 新增或编辑
*
* @param airagApp
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
@RequiresPermissions("airag:app:edit")
public Result<String> edit(@RequestBody AiragApp airagApp) {
AssertUtils.assertNotEmpty("参数异常", airagApp);
AssertUtils.assertNotEmpty("请输入应用名称", airagApp.getName());
AssertUtils.assertNotEmpty("请选择应用类型", airagApp.getType());
airagApp.setStatus(AiAppConsts.STATUS_ENABLE);
airagAppService.saveOrUpdate(airagApp);
return Result.OK("保存完成!", airagApp.getId());
}
/**
* 发布应用
*
* @return
*/
@RequestMapping(value = "/release", method = RequestMethod.POST)
public Result<String> release(@RequestParam(name = "id") String id, @RequestParam(name = "release") Boolean release) {
AssertUtils.assertNotEmpty("id必须填写", id);
if (release == null) {
release = true;
}
AiragApp airagApp = new AiragApp();
airagApp.setId(id);
if (release) {
airagApp.setStatus(AiAppConsts.STATUS_RELEASE);
} else {
airagApp.setStatus(AiAppConsts.STATUS_ENABLE);
}
airagAppService.updateById(airagApp);
return Result.OK(release ? "发布成功" : "取消发布成功");
}
/**
* 通过id删除
*
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
@RequiresPermissions("airag:app:delete")
public Result<String> delete(HttpServletRequest request,@RequestParam(name = "id", required = true) String id) {
//update-begin---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
//如果是saas隔离的情况下,判断当前租户id是否是当前租户下的
if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
AiragApp app = airagAppService.getById(id);
//获取当前租户
String currentTenantId = TokenUtils.getTenantIdByRequest(request);
if (null == app || !app.getTenantId().equals(currentTenantId)) {
return Result.error("删除AI应用失败,不能删除其他租户的AI应用!");
}
}
//update-end---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
airagAppService.removeById(id);
return Result.OK("删除成功!");
}
/**
* 通过id查询
*
* @param id
* @return
*/
@IgnoreAuth
@GetMapping(value = "/queryById")
public Result<AiragApp> queryById(@RequestParam(name = "id", required = true) String id) {
AiragApp airagApp = airagAppService.getById(id);
if (airagApp == null) {
return Result.error("未找到对应数据");
}
return Result.OK(airagApp);
}
/**
* 调试应用
*
* @param appDebugParams
* @return
* @author chenrui
* @date 2025/2/28 10:49
*/
@PostMapping(value = "/debug")
public SseEmitter debugApp(@RequestBody AppDebugParams appDebugParams) {
return airagChatService.debugApp(appDebugParams);
}
/**
* 根据需求生成提示词
*
* @param prompt
* @return
* @author chenrui
* @date 2025/3/12 15:30
*/
@GetMapping(value = "/prompt/generate")
public Result<?> generatePrompt(@RequestParam(name = "prompt", required = true) String prompt) {
return (Result<?>) airagAppService.generatePrompt(prompt,true);
}
/**
* 根据需求生成提示词
*
* @param prompt
* @return
* @author chenrui
* @date 2025/3/12 15:30
*/
@PostMapping(value = "/prompt/generate")
public SseEmitter generatePromptSse(@RequestParam(name = "prompt", required = true) String prompt) {
return (SseEmitter) airagAppService.generatePrompt(prompt,false);
}
}
@@ -0,0 +1,206 @@
package org.jeecg.modules.airag.app.controller;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.CommonUtils;
import org.jeecg.config.shiro.IgnoreAuth;
import org.jeecg.modules.airag.app.service.IAiragChatService;
import org.jeecg.modules.airag.app.vo.ChatConversation;
import org.jeecg.modules.airag.app.vo.ChatSendParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* airag应用-chat
*
* @Author: chenrui
* @Date: 2025-02-25 11:40
*/
@Slf4j
@RestController
@RequestMapping("/airag/chat")
public class AiragChatController {
@Autowired
IAiragChatService chatService;
@Value(value = "${jeecg.path.upload}")
private String uploadpath;
/**
* 本地:local miniominio 阿里:alioss
*/
@Value(value="${jeecg.uploadType}")
private String uploadType;
/**
* 发送消息
*
* @return 返回一个Result对象,表示发送消息的结果
* @author chenrui
* @date 2025/2/25 11:42
*/
@IgnoreAuth
@PostMapping(value = "/send")
public SseEmitter send(@RequestBody ChatSendParams chatSendParams) {
return chatService.send(chatSendParams);
}
/**
* 发送消息 <br/>
* 兼容旧版浏览器
* @param content
* @param conversationId
* @param topicId
* @param appId
* @return
* @author chenrui
* @date 2025/2/25 18:13
*/
@GetMapping(value = "/send")
public SseEmitter sendByGet(@RequestParam("content") String content,
@RequestParam(value = "conversationId", required = false) String conversationId,
@RequestParam(value = "topicId", required = false) String topicId,
@RequestParam(value = "appId", required = false) String appId) {
ChatSendParams chatSendParams = new ChatSendParams(content, conversationId, topicId, appId);
return chatService.send(chatSendParams);
}
/**
* 获取所有对话
*
* @return 返回一个Result对象,包含所有对话的信息
* @author chenrui
* @date 2025/2/25 11:42
*/
@IgnoreAuth
@GetMapping(value = "/init")
public Result<?> initChat(@RequestParam(name = "id", required = true) String id) {
return chatService.initChat(id);
}
/**
* 获取所有对话
*
* @return 返回一个Result对象,包含所有对话的信息
* @author chenrui
* @date 2025/2/25 11:42
*/
@IgnoreAuth
@GetMapping(value = "/conversations")
public Result<?> getConversations(@RequestParam(value = "appId", required = false) String appId) {
return chatService.getConversations(appId);
}
/**
* 删除会话
*
* @param id
* @return
* @author chenrui
* @date 2025/3/3 16:55
*/
@IgnoreAuth
@DeleteMapping(value = "/conversation/{id}")
public Result<?> deleteConversation(@PathVariable("id") String id) {
return chatService.deleteConversation(id);
}
/**
* 更新会话标题
*
* @param updateTitleParams
* @return
* @author chenrui
* @date 2025/3/3 16:55
*/
@IgnoreAuth
@PutMapping(value = "/conversation/update/title")
public Result<?> updateConversationTitle(@RequestBody ChatConversation updateTitleParams) {
return chatService.updateConversationTitle(updateTitleParams);
}
/**
* 获取消息
*
* @return 返回一个Result对象,包含消息的信息
* @author chenrui
* @date 2025/2/25 11:42
*/
@IgnoreAuth
@GetMapping(value = "/messages")
public Result<?> getMessages(@RequestParam(value = "conversationId", required = true) String conversationId) {
return chatService.getMessages(conversationId);
}
/**
* 清空消息
*
* @return
* @author chenrui
* @date 2025/2/25 11:42
*/
@IgnoreAuth
@GetMapping(value = "/messages/clear/{conversationId}")
public Result<?> clearMessage(@PathVariable(value = "conversationId") String conversationId) {
return chatService.clearMessage(conversationId);
}
/**
* 根据请求ID停止某个请求的处理
*
* @param requestId 请求的唯一标识符,用于识别和停止特定的请求
* @return 返回一个Result对象,表示停止请求的结果
* @author chenrui
* @date 2025/2/25 11:42
*/
@IgnoreAuth
@GetMapping(value = "/stop/{requestId}")
public Result<?> stop(@PathVariable(name = "requestId", required = true) String requestId) {
return chatService.stop(requestId);
}
/**
* 上传文件
* for [QQYUN-12135]AI聊天,上传图片提示非法token
*
* @param request
* @param response
* @return
* @throws Exception
* @author chenrui
* @date 2025/4/25 11:04
*/
@IgnoreAuth
@PostMapping(value = "/upload")
public Result<?> upload(HttpServletRequest request, HttpServletResponse response) throws Exception {
String bizPath = "airag";
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
// 获取上传文件对象
MultipartFile file = multipartRequest.getFile("file");
String savePath;
if (CommonConstant.UPLOAD_TYPE_LOCAL.equals(uploadType)) {
savePath = CommonUtils.uploadLocal(file, bizPath, uploadpath);
} else {
savePath = CommonUtils.upload(file, bizPath, uploadType);
}
Result<?> result = new Result<>();
result.setMessage(savePath);
result.setSuccess(true);
return result;
}
}
@@ -0,0 +1,191 @@
package org.jeecg.modules.airag.app.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecg.common.aspect.annotation.Dict;
import org.jeecg.common.util.oConvertUtils;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Description: AI应用
* @Author: jeecg-boot
* @Date: 2025-02-26
* @Version: V1.0
*/
@Data
@TableName("airag_app")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description="AI应用")
public class AiragApp implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private java.lang.String id;
/**
* 创建人
*/
@Schema(description = "创建人")
@Dict(dictTable = "sys_user",dicCode = "username",dicText = "realname")
private java.lang.String createBy;
/**
* 创建日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建日期")
private java.util.Date createTime;
/**
* 更新人
*/
@Schema(description = "更新人")
private java.lang.String updateBy;
/**
* 更新日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新日期")
private java.util.Date updateTime;
/**
* 所属部门
*/
@Schema(description = "所属部门")
private java.lang.String sysOrgCode;
/**
* 租户id
*/
@Excel(name = "租户id", width = 15)
@Schema(description = "租户id")
private java.lang.String tenantId;
/**
* 应用名称
*/
@Excel(name = "应用名称", width = 15)
@Schema(description = "应用名称")
private java.lang.String name;
/**
* 应用描述
*/
@Excel(name = "应用描述", width = 15)
@Schema(description = "应用描述")
private java.lang.String descr;
/**
* 应用图标
*/
@Excel(name = "应用图标", width = 15)
@Schema(description = "应用图标")
private java.lang.String icon;
/**
* 应用类型
*/
@Excel(name = "应用类型", width = 15, dicCode = "ai_app_type")
@Dict(dicCode = "ai_app_type")
@Schema(description = "应用类型")
private java.lang.String type;
/**
* 开场白
*/
@Excel(name = "开场白", width = 15)
@Schema(description = "开场白")
private java.lang.String prologue;
/**
* 预设问题
*/
@Excel(name = "预设问题", width = 15)
@Schema(description = "预设问题")
private java.lang.String presetQuestion;
/**
* 提示词
*/
@Excel(name = "提示词", width = 15)
@Schema(description = "提示词")
private java.lang.String prompt;
/**
* 模型配置
*/
@Excel(name = "模型配置", width = 15, dictTable = "airag_model where model_type = 'LLM' ", dicText = "name", dicCode = "id")
@Dict(dictTable = "airag_model where model_type = 'LLM' ", dicText = "name", dicCode = "id")
@Schema(description = "模型配置")
private java.lang.String modelId;
/**
* 历史消息数
*/
@Excel(name = "历史消息数", width = 15)
@Schema(description = "历史消息数")
private java.lang.Integer msgNum;
/**
* 知识库
*/
@Excel(name = "知识库", width = 15, dictTable = "airag_knowledge where status = 'enable'", dicText = "name", dicCode = "id")
@Dict(dictTable = "airag_knowledge where status = 'enable'", dicText = "name", dicCode = "id")
@Schema(description = "知识库")
private java.lang.String knowledgeIds;
/**
* 流程
*/
@Excel(name = "流程", width = 15, dictTable = "airag_flow where status = 'enable' ", dicText = "name", dicCode = "id")
@Dict(dictTable = "airag_flow where status = 'enable' ", dicText = "name", dicCode = "id")
@Schema(description = "流程")
private java.lang.String flowId;
/**
* 快捷指令
*/
@Excel(name = "快捷指令", width = 15)
@Schema(description = "快捷指令")
private java.lang.String quickCommand;
/**
* 状态(enable=启用、disable=禁用、release=发布)
*/
@Excel(name = "状态", width = 15)
@Schema(description = "状态")
private java.lang.String status;
/**
* 元数据
*/
@Excel(name = "元数据", width = 15)
@Schema(description = "元数据")
private java.lang.String metadata;
/**
* 知识库ids
*/
@TableField(exist = false)
private List<String> knowIds;
/**
* 获取知识库id
*
* @return
* @author chenrui
* @date 2025/2/28 11:45
*/
public List<String> getKnowIds() {
if (oConvertUtils.isNotEmpty(knowledgeIds)) {
String[] knowIds = knowledgeIds.split(",");
return Arrays.asList(knowIds);
} else {
return new ArrayList<>(0);
}
}
}
@@ -0,0 +1,25 @@
package org.jeecg.modules.airag.app.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.airag.app.entity.AiragApp;
/**
* @Description: AI应用
* @Author: jeecg-boot
* @Date: 2025-02-26
* @Version: V1.0
*/
public interface AiragAppMapper extends BaseMapper<AiragApp> {
/**
* 根据ID查询app信息(忽略租户)
* @param id
* @return
* @author chenrui
* @date 2025/4/21 16:03
*/
@InterceptorIgnore(tenantLine = "true")
AiragApp getByIdIgnoreTenant(String id);
}
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.airag.app.mapper.AiragAppMapper">
<select id="getByIdIgnoreTenant" resultType="org.jeecg.modules.airag.app.entity.AiragApp">
SELECT * FROM airag_app WHERE id = #{id}
</select>
</mapper>
@@ -0,0 +1,24 @@
package org.jeecg.modules.airag.app.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.airag.app.entity.AiragApp;
/**
* @Description: AI应用
* @Author: jeecg-boot
* @Date: 2025-02-26
* @Version: V1.0
*/
public interface IAiragAppService extends IService<AiragApp> {
/**
* 生成提示词
* @param prompt
* @return blocking 是否阻塞
* @return
* @author chenrui
* @date 2025/3/12 14:45
*/
Object generatePrompt(String prompt,boolean blocking);
}
@@ -0,0 +1,105 @@
package org.jeecg.modules.airag.app.service;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.airag.app.vo.AppDebugParams;
import org.jeecg.modules.airag.app.vo.ChatConversation;
import org.jeecg.modules.airag.app.vo.ChatSendParams;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* ai聊天
*
* @author chenrui
* @date 2025/2/25 13:36
*/
public interface IAiragChatService {
/**
* 发送消息
*
* @param chatSendParams
* @return
* @author chenrui
* @date 2025/2/25 13:39
*/
SseEmitter send(ChatSendParams chatSendParams);
/**
* 调试应用
*
* @param appDebugParams
* @return
* @author chenrui
* @date 2025/2/28 10:49
*/
SseEmitter debugApp(AppDebugParams appDebugParams);
/**
* 停止响应
*
* @param requestId
* @return
* @author chenrui
* @date 2025/2/25 17:17
*/
Result<?> stop(String requestId);
/**
* 获取所有对话
*
* @param appId
* @return
* @author chenrui
* @date 2025/2/26 14:48
*/
Result<?> getConversations(String appId);
/**
* 获取对话聊天记录
*
* @param conversationId
* @return
* @author chenrui
* @date 2025/2/26 15:16
*/
Result<?> getMessages(String conversationId);
/**
* 删除会话
*
* @param conversationId
* @return
* @author chenrui
* @date 2025/3/3 16:55
*/
Result<?> deleteConversation(String conversationId);
/**
* 更新会话标题
* @param updateTitleParams
* @return
* @author chenrui
* @date 2025/3/3 17:02
*/
Result<?> updateConversationTitle(ChatConversation updateTitleParams);
/**
* 清空消息
* @param conversationId
* @return
* @author chenrui
* @date 2025/3/3 19:49
*/
Result<?> clearMessage(String conversationId);
/**
* 初始化聊天(忽略租户)
* [QQYUN-12113]分享之后的聊天,应用、模型、知识库不根据租户查询
* @param appId
* @return
* @author chenrui
* @date 2025/4/21 14:17
*/
Result<?> initChat(String appId);
}
@@ -0,0 +1,157 @@
package org.jeecg.modules.airag.app.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.output.FinishReason;
import dev.langchain4j.service.TokenStream;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.util.AssertUtils;
import org.jeecg.common.util.UUIDGenerator;
import org.jeecg.modules.airag.app.consts.Prompts;
import org.jeecg.modules.airag.app.entity.AiragApp;
import org.jeecg.modules.airag.app.mapper.AiragAppMapper;
import org.jeecg.modules.airag.app.service.IAiragAppService;
import org.jeecg.modules.airag.common.consts.AiragConsts;
import org.jeecg.modules.airag.common.handler.AIChatParams;
import org.jeecg.modules.airag.common.handler.IAIChatHandler;
import org.jeecg.modules.airag.common.utils.AiragLocalCache;
import org.jeecg.modules.airag.common.vo.event.EventData;
import org.jeecg.modules.airag.common.vo.event.EventFlowData;
import org.jeecg.modules.airag.common.vo.event.EventMessageData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @Description: AI应用
* @Author: jeecg-boot
* @Date: 2025-02-26
* @Version: V1.0
*/
@Slf4j
@Service
public class AiragAppServiceImpl extends ServiceImpl<AiragAppMapper, AiragApp> implements IAiragAppService {
@Autowired
IAIChatHandler aiChatHandler;
@Override
public Object generatePrompt(String prompt, boolean blocking) {
AssertUtils.assertNotEmpty("请输入提示词", prompt);
List<ChatMessage> messages = Arrays.asList(new SystemMessage(Prompts.GENERATE_LLM_PROMPT), new UserMessage(prompt));
AIChatParams params = new AIChatParams();
params.setTemperature(0.8);
params.setTopP(0.9);
params.setPresencePenalty(0.1);
params.setFrequencyPenalty(0.1);
if(blocking){
String promptValue = aiChatHandler.completionsByDefaultModel(messages, params);
if (promptValue == null || promptValue.isEmpty()) {
return Result.error("生成失败");
}
return Result.OK("success", promptValue);
}else{
SseEmitter emitter = new SseEmitter(-0L);
// 异步运行(流式)
TokenStream tokenStream = aiChatHandler.chatByDefaultModel(messages, params);
/**
* 是否正在思考
*/
AtomicBoolean isThinking = new AtomicBoolean(false);
String requestId = UUIDGenerator.generate();
// ai聊天响应逻辑
tokenStream.onNext((String resMessage) -> {
// 兼容推理模型
if ("<think>".equals(resMessage)) {
isThinking.set(true);
resMessage = "> ";
}
if ("</think>".equals(resMessage)) {
isThinking.set(false);
resMessage = "\n\n";
}
if (isThinking.get()) {
if (null != resMessage && resMessage.contains("\n")) {
resMessage = "\n> ";
}
}
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE);
EventMessageData messageEventData = EventMessageData.builder()
.message(resMessage)
.build();
eventData.setData(messageEventData);
try {
String eventStr = JSONObject.toJSONString(eventData);
log.debug("[AI应用]接收LLM返回消息:{}", eventStr);
emitter.send(SseEmitter.event().data(eventStr));
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.onComplete((responseMessage) -> {
// 记录ai的回复
AiMessage aiMessage = responseMessage.content();
FinishReason finishReason = responseMessage.finishReason();
String respText = aiMessage.text();
if (FinishReason.STOP.equals(finishReason) || null == finishReason) {
// 正常结束
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END);
try {
log.debug("[AI应用]接收LLM返回消息完成:{}", respText);
emitter.send(SseEmitter.event().data(eventData));
} catch (IOException e) {
throw new RuntimeException(e);
}
closeSSE(emitter, eventData);
} else if (FinishReason.TOOL_EXECUTION.equals(finishReason)) {
// 需要执行工具
// TODO author: chenrui for: date:2025/3/7
} else {
// 异常结束
log.error("调用模型异常:" + respText);
if (respText.contains("insufficient Balance")) {
respText = "大预言模型账号余额不足!";
}
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR);
eventData.setData(EventFlowData.builder().success(false).message(respText).build());
closeSSE(emitter, eventData);
}
})
.onError((Throwable error) -> {
// sse
String errMsg = "调用大模型接口失败:" + error.getMessage();
log.error(errMsg, error);
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR);
eventData.setData(EventFlowData.builder().success(false).message(errMsg).build());
closeSSE(emitter, eventData);
})
.start();
return emitter;
}
}
private static void closeSSE(SseEmitter emitter, EventData eventData) {
try {
// 发送完成事件
emitter.send(SseEmitter.event().data(eventData));
} catch (IOException e) {
log.error("终止会话时发生错误", e);
} finally {
// 从缓存中移除emitter
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE, eventData.getRequestId());
// 关闭emitter
emitter.complete();
}
}
}
@@ -0,0 +1,979 @@
package org.jeecg.modules.airag.app.service.impl;
import com.alibaba.fastjson.JSONObject;
import dev.langchain4j.data.image.Image;
import dev.langchain4j.data.message.*;
import dev.langchain4j.model.output.FinishReason;
import dev.langchain4j.service.TokenStream;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.exception.JeecgBootBizTipException;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.*;
import org.jeecg.modules.airag.app.consts.AiAppConsts;
import org.jeecg.modules.airag.app.entity.AiragApp;
import org.jeecg.modules.airag.app.mapper.AiragAppMapper;
import org.jeecg.modules.airag.app.service.IAiragAppService;
import org.jeecg.modules.airag.app.service.IAiragChatService;
import org.jeecg.modules.airag.app.vo.AppDebugParams;
import org.jeecg.modules.airag.app.vo.ChatConversation;
import org.jeecg.modules.airag.app.vo.ChatSendParams;
import org.jeecg.modules.airag.common.consts.AiragConsts;
import org.jeecg.modules.airag.common.handler.AIChatParams;
import org.jeecg.modules.airag.common.handler.IAIChatHandler;
import org.jeecg.modules.airag.common.utils.AiragLocalCache;
import org.jeecg.modules.airag.common.vo.MessageHistory;
import org.jeecg.modules.airag.common.vo.event.EventData;
import org.jeecg.modules.airag.common.vo.event.EventFlowData;
import org.jeecg.modules.airag.common.vo.event.EventMessageData;
import org.jeecg.modules.airag.flow.consts.FlowConsts;
import org.jeecg.modules.airag.flow.service.IAiragFlowService;
import org.jeecg.modules.airag.flow.vo.api.FlowRunParams;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
* AI助手聊天Service
*
* @author chenrui
* @date 2024/1/26 20:07
*/
@Service
@Slf4j
public class AiragChatServiceImpl implements IAiragChatService {
@Autowired
IAIChatHandler aiChatHandler;
@Autowired
RedisTemplate redisTemplate;
@Autowired
AiragAppMapper airagAppMapper;
@Autowired
IAiragFlowService airagFlowService;
@Autowired
private ISysBaseAPI sysBaseApi;
@Autowired
private RedisUtil redisUtil;
@Override
public SseEmitter send(ChatSendParams chatSendParams) {
AssertUtils.assertNotEmpty("参数异常", chatSendParams);
String userMessage = chatSendParams.getContent();
AssertUtils.assertNotEmpty("至少发送一条消息", userMessage);
// 获取会话信息
String conversationId = chatSendParams.getConversationId();
String topicId = oConvertUtils.getString(chatSendParams.getTopicId(), UUIDGenerator.generate());
// 获取app信息
AiragApp app = null;
if (oConvertUtils.isNotEmpty(chatSendParams.getAppId())) {
app = airagAppMapper.getByIdIgnoreTenant(chatSendParams.getAppId());
}
ChatConversation chatConversation = getOrCreateChatConversation(app, conversationId);
// 更新标题
if (oConvertUtils.isEmpty(chatConversation.getTitle())) {
chatConversation.setTitle(userMessage.length() > 5 ? userMessage.substring(0, 5) : userMessage);
}
// 发送消息
return doChat(chatConversation, topicId, chatSendParams);
}
@Override
public SseEmitter debugApp(AppDebugParams appDebugParams) {
AssertUtils.assertNotEmpty("参数异常", appDebugParams);
String userMessage = appDebugParams.getContent();
AssertUtils.assertNotEmpty("至少发送一条消息", userMessage);
AssertUtils.assertNotEmpty("应用信息不能为空", appDebugParams.getApp());
// 获取会话信息
String topicId = oConvertUtils.getString(appDebugParams.getTopicId(), UUIDGenerator.generate());
AiragApp app = appDebugParams.getApp();
app.setId("__DEBUG_APP");
ChatConversation chatConversation = getOrCreateChatConversation(app, topicId);
// 发送消息
SseEmitter emitter = doChat(chatConversation, topicId, appDebugParams);
//保存会话
saveChatConversation(chatConversation, true, null);
return emitter;
}
@Override
public Result<?> stop(String requestId) {
AssertUtils.assertNotEmpty("requestId不能为空", requestId);
// 从缓存中获取对应的SseEmitter
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
if (emitter != null) {
closeSSE(emitter, new EventData(requestId, null, EventData.EVENT_MESSAGE_END));
return Result.ok("会话已成功终止");
} else {
return Result.error("未找到对应的会话");
}
}
/**
* 关闭sse
*
* @param emitter
* @param eventData
* @throws IOException
* @author chenrui
* @date 2025/2/27 15:56
*/
private static void closeSSE(SseEmitter emitter, EventData eventData) {
AssertUtils.assertNotEmpty("请求id不能为空", eventData);
if (null == emitter) {
log.warn("会话已关闭");
return;
}
try {
// 发送完成事件
emitter.send(SseEmitter.event().data(eventData));
} catch (Exception e) {
log.error("终止会话时发生错误", e);
try {
// 防止异常冒泡
emitter.completeWithError(e);
} catch (Exception ignore) {}
} finally {
// 从缓存中移除emitter
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE, eventData.getRequestId());
// 关闭emitter
try {
emitter.complete();
} catch (Exception ignore) {}
}
}
@Override
public Result<?> getConversations(String appId) {
if (oConvertUtils.isEmpty(appId)) {
appId = AiAppConsts.DEFAULT_APP_ID;
}
String key = getConversationDirCacheKey(null);
key = key + ":*";
List<String> keys = redisUtil.scan(key);
// 如果键集合为空,返回空列表
if (keys.isEmpty()) {
return Result.ok(Collections.emptyList());
}
// 遍历键集合,获取对应的 ChatConversation 对象
List<ChatConversation> conversations = new ArrayList<>();
for (Object k : keys) {
ChatConversation conversation = (ChatConversation) redisTemplate.boundValueOps(k).get();
if (conversation != null) {
AiragApp app = conversation.getApp();
if (null == app) {
continue;
}
String conversationAppId = app.getId();
if (appId.equals(conversationAppId)) {
conversation.setApp(null);
conversation.setMessages(null);
conversations.add(conversation);
}
}
}
// 对会话列表按创建时间降序排序
conversations.sort((o1, o2) -> {
Date date1 = o1.getCreateTime();
Date date2 = o2.getCreateTime();
if (date1 == null && date2 == null) {
return 0;
}
if (date1 == null) {
return 1;
}
if (date2 == null) {
return -1;
}
return date2.compareTo(date1);
});
// 返回结果
return Result.ok(conversations);
}
@Override
public Result<?> getMessages(String conversationId) {
AssertUtils.assertNotEmpty("请先选择会话", conversationId);
String key = getConversationCacheKey(conversationId, null);
if (oConvertUtils.isEmpty(key)) {
return Result.ok(Collections.emptyList());
}
ChatConversation chatConversation = (ChatConversation) redisTemplate.boundValueOps(key).get();
if (oConvertUtils.isObjectEmpty(chatConversation)) {
return Result.ok(Collections.emptyList());
}
return Result.ok(chatConversation.getMessages());
}
@Override
public Result<?> clearMessage(String conversationId) {
AssertUtils.assertNotEmpty("请先选择会话", conversationId);
String key = getConversationCacheKey(conversationId, null);
if (oConvertUtils.isEmpty(key)) {
return Result.ok(Collections.emptyList());
}
ChatConversation chatConversation = (ChatConversation) redisTemplate.boundValueOps(key).get();
if (null != chatConversation && oConvertUtils.isObjectNotEmpty(chatConversation.getMessages())) {
chatConversation.getMessages().clear();
saveChatConversation(chatConversation);
}
return Result.ok();
}
@Override
public Result<?> initChat(String appId) {
AiragApp app = airagAppMapper.getByIdIgnoreTenant(appId);
return Result.ok(app);
}
@Override
public Result<?> deleteConversation(String conversationId) {
AssertUtils.assertNotEmpty("请选择要删除的会话", conversationId);
String key = getConversationCacheKey(conversationId, null);
if (oConvertUtils.isNotEmpty(key)) {
Boolean delete = redisTemplate.delete(key);
if (delete) {
return Result.ok();
} else {
return Result.error("删除会话失败");
}
}
log.warn("[ai-chat]删除会话:未找到会话:{}", conversationId);
return Result.ok();
}
@Override
public Result<?> updateConversationTitle(ChatConversation updateTitleParams) {
AssertUtils.assertNotEmpty("请先选择会话", updateTitleParams);
AssertUtils.assertNotEmpty("请先选择会话", updateTitleParams.getId());
AssertUtils.assertNotEmpty("请输入会话标题", updateTitleParams.getTitle());
String key = getConversationCacheKey(updateTitleParams.getId(), null);
if (oConvertUtils.isEmpty(key)) {
log.warn("[ai-chat]删除会话:未找到会话:{}", updateTitleParams.getId());
return Result.ok();
}
ChatConversation chatConversation = (ChatConversation) redisTemplate.boundValueOps(key).get();
chatConversation.setTitle(updateTitleParams.getTitle());
saveChatConversation(chatConversation);
return Result.ok();
}
/**
* 获取会话缓存key
*
* @param conversationId
* @param httpRequest
* @return
* @author chenrui
* @date 2025/2/25 19:27
*/
private String getConversationCacheKey(String conversationId, HttpServletRequest httpRequest) {
if (oConvertUtils.isEmpty(conversationId)) {
return null;
}
String key = getConversationDirCacheKey(httpRequest);
key = key + ":" + conversationId;
return key;
}
/**
* 获取当前用户会话的缓存目录
*
* @param httpRequest
* @return
* @author chenrui
* @date 2025/2/26 15:09
*/
private String getConversationDirCacheKey(HttpServletRequest httpRequest) {
String username = getUsername(httpRequest);
// 如果用户不存在,获取当前请求的sessionid
if (oConvertUtils.isEmpty(username)) {
try {
if (null == httpRequest) {
httpRequest = SpringContextUtils.getHttpServletRequest();
}
username = httpRequest.getSession().getId();
} catch (Exception e) {
log.error("获取当前请求的sessionid失败", e);
}
}
AssertUtils.assertNotEmpty("请先登录", username);
return "airag:chat:" + username;
}
/**
* 获取会话
*
* @param app
* @param conversationId
* @return
* @author chenrui
* @date 2025/2/25 19:19
*/
@NotNull
private ChatConversation getOrCreateChatConversation(AiragApp app, String conversationId) {
if (oConvertUtils.isObjectEmpty(app)) {
app = new AiragApp();
app.setId(AiAppConsts.DEFAULT_APP_ID);
}
ChatConversation chatConversation = null;
String key = getConversationCacheKey(conversationId, null);
if (oConvertUtils.isNotEmpty(key)) {
chatConversation = (ChatConversation) redisTemplate.boundValueOps(key).get();
}
if (null == chatConversation) {
chatConversation = createConversation(conversationId);
}
chatConversation.setApp(app);
return chatConversation;
}
/**
* 创建新的会话
*
* @param conversationId
* @return
* @author chenrui
* @date 2025/2/26 15:53
*/
@NotNull
private ChatConversation createConversation(String conversationId) {
// 新会话
conversationId = oConvertUtils.getString(conversationId, UUIDGenerator.generate());
ChatConversation chatConversation = new ChatConversation();
chatConversation.setId(conversationId);
chatConversation.setCreateTime(new Date());
return chatConversation;
}
/**
* 保存会话
*
* @param chatConversation
* @author chenrui
* @date 2025/2/25 19:27
*/
private void saveChatConversation(ChatConversation chatConversation) {
saveChatConversation(chatConversation, false, null);
}
/**
* 保存会话
*
* @param chatConversation
* @param temp 是否临时会话
* @author chenrui
* @date 2025/2/25 19:27
*/
private void saveChatConversation(ChatConversation chatConversation, boolean temp, HttpServletRequest httpRequest) {
if (null == chatConversation) {
return;
}
String key = getConversationCacheKey(chatConversation.getId(), httpRequest);
if (oConvertUtils.isEmpty(key)) {
return;
}
BoundValueOperations chatRedisCacheOp = redisTemplate.boundValueOps(key);
chatRedisCacheOp.set(chatConversation);
if (temp) {
chatRedisCacheOp.expire(3, TimeUnit.HOURS);
}
}
/**
* 构造消息
*
* @param conversation
* @param topicId
* @return
* @author chenrui
* @date 2025/2/25 15:26
*/
private List<ChatMessage> collateMessage(ChatConversation conversation, String topicId) {
List<MessageHistory> messagesHistory = conversation.getMessages();
if (oConvertUtils.isObjectEmpty(messagesHistory)) {
return new LinkedList<>();
}
LinkedList<ChatMessage> chatMessages = new LinkedList<>();
for (int i = messagesHistory.size() - 1; i >= 0; i--) {
MessageHistory history = messagesHistory.get(i);
if (topicId.equals(history.getTopicId())) {
ChatMessage chatMessage = null;
switch (history.getRole()) {
case AiragConsts.MESSAGE_ROLE_USER:
List<Content> contents = new ArrayList<>();
List<MessageHistory.ImageHistory> images = history.getImages();
if (oConvertUtils.isObjectNotEmpty(images) && !images.isEmpty()) {
contents.addAll(images.stream().map(imageHistory -> {
if (oConvertUtils.isNotEmpty(imageHistory.getUrl())) {
return ImageContent.from(imageHistory.getUrl());
} else {
return ImageContent.from(imageHistory.getBase64Data(), imageHistory.getMimeType());
}
}).collect(Collectors.toList()));
}
contents.add(TextContent.from(history.getContent()));
chatMessage = UserMessage.from(contents);
break;
case AiragConsts.MESSAGE_ROLE_AI:
chatMessage = new AiMessage(history.getContent());
break;
}
if (null == chatMessage) {
continue;
}
chatMessages.addFirst(chatMessage);
}
}
return chatMessages;
}
/**
* 追加消息
*
* @param messages
* @param message
* @param chatConversation
* @param topicId
* @return
* @author chenrui
* @date 2025/2/25 19:05
*/
private void appendMessage(List<ChatMessage> messages, ChatMessage message, ChatConversation chatConversation, String topicId) {
if (message.type().equals(ChatMessageType.SYSTEM)) {
// 系统消息,放到消息列表最前面,并且不记录历史
messages.add(0, message);
return;
} else {
messages.add(message);
}
List<MessageHistory> histories = chatConversation.getMessages();
if (oConvertUtils.isObjectEmpty(histories)) {
histories = new ArrayList<>();
}
// 消息记录
MessageHistory historyMessage = MessageHistory.builder().conversationId(chatConversation.getId()).topicId(topicId).datetime(DateUtils.now()).build();
if (message.type().equals(ChatMessageType.USER)) {
historyMessage.setRole(AiragConsts.MESSAGE_ROLE_USER);
StringBuilder textContent = new StringBuilder();
List<MessageHistory.ImageHistory> images = new ArrayList<>();
List<Content> contents = ((UserMessage) message).contents();
contents.forEach(content -> {
if (content.type().equals(ContentType.IMAGE)) {
ImageContent imageContent = (ImageContent) content;
Image image = imageContent.image();
MessageHistory.ImageHistory imageMessage = MessageHistory.ImageHistory.from(image.url(), image.base64Data(), image.mimeType());
images.add(imageMessage);
} else if (content.type().equals(ContentType.TEXT)) {
textContent.append(((TextContent) content).text()).append("\n");
}
});
historyMessage.setContent(textContent.toString());
historyMessage.setImages(images);
} else if (message.type().equals(ChatMessageType.AI)) {
historyMessage.setRole(AiragConsts.MESSAGE_ROLE_AI);
historyMessage.setContent(((AiMessage) message).text());
}
histories.add(historyMessage);
chatConversation.setMessages(histories);
}
/**
* 发送聊天消息
*
* @param chatConversation
* @param topicId
* @param sendParams
* @return
* @author chenrui
* @date 2025/2/28 11:04
*/
@NotNull
private SseEmitter doChat(ChatConversation chatConversation, String topicId, ChatSendParams sendParams) {
// 从历史消息中组装本次的消息列表
List<ChatMessage> messages = collateMessage(chatConversation, topicId);
AiragApp aiApp = chatConversation.getApp();
// 每次会话都生成一个新的,用来缓存emitter
String requestId = UUIDGenerator.generate();
SseEmitter emitter = new SseEmitter(-0L);
emitter.onError(throwable -> {
log.warn("SEE向客户端发送消息失败: {}", throwable.getMessage());
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE, requestId);
try {
emitter.complete();
} catch (Exception ignore) {}
});
EventData eventRequestId = new EventData(requestId, null, EventData.EVENT_INIT_REQUEST_ID, chatConversation.getId(), topicId);
eventRequestId.setData(EventMessageData.builder().message("").build());
sendMessage2Client(emitter, eventRequestId);
// 缓存emitter
AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE, requestId, emitter);
// 缓存开始发送时间
log.info("[AI-CHAT]开始发送消息,requestId:{}", requestId);
AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId, System.currentTimeMillis());
try {
// 组装用户消息
UserMessage userMessage = aiChatHandler.buildUserMessage(sendParams.getContent(), sendParams.getImages());
// 追加消息
appendMessage(messages, userMessage, chatConversation, topicId);
/* 这里应该是有几种情况:
* 1. 非ai应用:获取默认模型->开始聊天
* 2. AI应用-聊天助手(ChatAssistant):从应用信息组装模型和提示词->开始聊天
* 3. AI应用-聊天流程(ChatFlow):从应用信息获取模型,流程,组装入参->调用工作流
*/
if (null != aiApp && !AiAppConsts.DEFAULT_APP_ID.equals(aiApp.getId())) {
// ai应用:查询应用信息(ChatAssistant,chatflow),模型信息,组装模型-提示词,知识库等
if (AiAppConsts.APP_TYPE_CHAT_FLOW.equals(aiApp.getType())) {
// ai应用:聊天流程(ChatFlow)
sendWithFlow(requestId, aiApp.getFlowId(), chatConversation, topicId, messages, sendParams);
} else {
// AI应用-聊天助手(ChatAssistant):从应用信息组装模型和提示词
sendWithAppChat(requestId, messages, chatConversation, topicId);
}
} else {
// 发消息
sendWithDefault(requestId, chatConversation, topicId, null, messages, null);
}
} catch (Throwable e) {
log.error(e.getMessage(), e);
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
eventData.setData(EventFlowData.builder().success(false).message(e.getMessage()).build());
closeSSE(emitter, eventData);
}
return emitter;
}
/**
* 运行流程
*
* @param requestId
* @param flowId
* @param chatConversation
* @param topicId
* @param messages
* @param sendParams
* @author chenrui
* @date 2025/2/27 14:55
*/
private void sendWithFlow(String requestId, String flowId, ChatConversation chatConversation, String topicId, List<ChatMessage> messages, ChatSendParams sendParams) {
FlowRunParams flowRunParams = new FlowRunParams();
flowRunParams.setRequestId(requestId);
flowRunParams.setFlowId(flowId);
flowRunParams.setConversationId(chatConversation.getId());
flowRunParams.setTopicId(topicId);
// 支持流式
flowRunParams.setResponseMode(FlowConsts.FLOW_RESPONSE_MODE_STREAMING);
Map<String, Object> flowInputParams = new HashMap<>();
List<MessageHistory> histories = new ArrayList<>();
if (oConvertUtils.isObjectNotEmpty(chatConversation.getMessages())) {
// 创建历史消息的副本(不直接操作原来的list)
histories.addAll(chatConversation.getMessages());
// 移除最后一条历史消息(最后一条是当前发出去的这一条消息)
histories.remove(histories.size() - 1);
}
flowInputParams.put(FlowConsts.FLOW_INPUT_PARAM_HISTORY, histories);
flowInputParams.put(FlowConsts.FLOW_INPUT_PARAM_QUESTION, sendParams.getContent());
flowInputParams.put(FlowConsts.FLOW_INPUT_PARAM_IMAGES, sendParams.getImages());
flowRunParams.setInputParams(flowInputParams);
HttpServletRequest httpRequest = SpringContextUtils.getHttpServletRequest();
flowRunParams.setHttpRequest(httpRequest);
// 流程结束后,记录ai返回并保存会话
// sse
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
flowRunParams.setEventCallback(eventData -> {
if (EventData.EVENT_FLOW_FINISHED.equals(eventData.getEvent())) {
// 打印耗时日志
printChatDuration(requestId, "流程执行完毕");
// 已经执行完了,删除时间缓存
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId);
EventFlowData data = (EventFlowData) eventData.getData();
if(data.isSuccess()) {
Object outputs = data.getOutputs();
if (oConvertUtils.isObjectNotEmpty(outputs)) {
AiMessage aiMessage;
if (outputs instanceof String) {
// 兼容推理模型
String messageText = String.valueOf(outputs);
messageText = messageText.replaceAll("<think>([\\s\\S]*?)</think>", "> $1");
aiMessage = new AiMessage(messageText);
} else {
aiMessage = new AiMessage(JSONObject.toJSONString(outputs));
}
EventData msgEventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
EventMessageData messageEventData = EventMessageData.builder().message(aiMessage.text()).build();
msgEventData.setData(messageEventData);
msgEventData.setRequestId(requestId);
sendMessage2Client(emitter, msgEventData);
appendMessage(messages, aiMessage, chatConversation, topicId);
// 保存会话
saveChatConversation(chatConversation, false, httpRequest);
}
}else{
//update-begin---author:chenrui ---date:20250425 for[QQYUN-12203]AI 聊天,超时或者服务器报错,给个友好提示------------
// 失败
String message = data.getMessage();
if (message != null && message.contains(FlowConsts.FLOW_ERROR_MSG_LLM_TIMEOUT)) {
message = "当前用户较多,排队中,请稍后再试!";
EventData errEventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
errEventData.setData(EventMessageData.builder().message("\n" + message).build());
sendMessage2Client(emitter, errEventData);
errEventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END, chatConversation.getId(), topicId);
// 如果是超时,主动关闭SSE,防止流程切面中返回异常消息导致前端不能正常展示上面的{普通消息}.
closeSSE(emitter, errEventData);
}
//update-end---author:chenrui ---date:20250425 for[QQYUN-12203]AI 聊天,超时或者服务器报错,给个友好提示------------
}
}
});
// 打印流程耗时日志
printChatDuration(requestId, "开始执行流程");
airagFlowService.runFlow(flowRunParams);
}
/**
* 发送app聊天
*
* @param requestId
* @param messages
* @param chatConversation
* @param topicId
* @return
* @author chenrui
* @date 2025/2/28 10:41
*/
private void sendWithAppChat(String requestId, List<ChatMessage> messages, ChatConversation chatConversation, String topicId) {
AiragApp aiApp = chatConversation.getApp();
String modelId = aiApp.getModelId();
AssertUtils.assertNotEmpty("请先选择模型", modelId);
// AI应用提示词
String prompt = aiApp.getPrompt();
if (oConvertUtils.isNotEmpty(prompt)) {
appendMessage(messages, new SystemMessage(prompt), chatConversation, topicId);
}
AIChatParams aiChatParams = new AIChatParams();
// AI应用自定义的模型参数
String metadataStr = aiApp.getMetadata();
if (oConvertUtils.isNotEmpty(metadataStr)) {
JSONObject metadata = JSONObject.parseObject(metadataStr);
if (oConvertUtils.isNotEmpty(metadata)) {
if (metadata.containsKey("temperature")) {
aiChatParams.setTemperature(metadata.getDouble("temperature"));
}
if (metadata.containsKey("topP")) {
aiChatParams.setTopP(metadata.getDouble("topP"));
}
if (metadata.containsKey("presencePenalty")) {
aiChatParams.setPresencePenalty(metadata.getDouble("presencePenalty"));
}
if (metadata.containsKey("frequencyPenalty")) {
aiChatParams.setFrequencyPenalty(metadata.getDouble("frequencyPenalty"));
}
if (metadata.containsKey("maxTokens")) {
aiChatParams.setMaxTokens(metadata.getInteger("maxTokens"));
}
}
}
// 打印流程耗时日志
printChatDuration(requestId, "构造应用自定义参数完成");
// 发消息
sendWithDefault(requestId, chatConversation, topicId, modelId, messages, aiChatParams);
}
/**
* 处理聊天
* 向大模型发送消息并接受响应
*
* @param chatConversation
* @param topicId
* @param modelId
* @param messages
* @return
* @author chenrui
* @date 2025/2/25 19:24
*/
private void sendWithDefault(String requestId, ChatConversation chatConversation, String topicId, String modelId, List<ChatMessage> messages, AIChatParams aiChatParams) {
// 调用ai聊天
if (null == aiChatParams) {
aiChatParams = new AIChatParams();
}
aiChatParams.setKnowIds(chatConversation.getApp().getKnowIds());
aiChatParams.setMaxMsgNumber(oConvertUtils.getInt(chatConversation.getApp().getMsgNum(), 5));
HttpServletRequest httpRequest = SpringContextUtils.getHttpServletRequest();
TokenStream chatStream;
try {
// 打印流程耗时日志
printChatDuration(requestId, "开始向LLM发送消息");
if (oConvertUtils.isNotEmpty(modelId)) {
chatStream = aiChatHandler.chat(modelId, messages, aiChatParams);
} else {
chatStream = aiChatHandler.chatByDefaultModel(messages, aiChatParams);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new JeecgBootBizTipException("调用大模型接口失败:" + e.getMessage());
}
/**
* 是否正在思考
*/
AtomicBoolean isThinking = new AtomicBoolean(false);
// ai聊天响应逻辑
chatStream.onNext((String resMessage) -> {
// 兼容推理模型
if ("<think>".equals(resMessage)) {
isThinking.set(true);
resMessage = "> ";
}
if ("</think>".equals(resMessage)) {
isThinking.set(false);
resMessage = "\n\n";
}
if (isThinking.get()) {
if (null != resMessage && resMessage.contains("\n")) {
resMessage = "\n> ";
}
}
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
EventMessageData messageEventData = EventMessageData.builder().message(resMessage).build();
eventData.setData(messageEventData);
eventData.setRequestId(requestId);
// sse
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
if (null == emitter) {
log.warn("[AI应用]接收LLM返回会话已关闭");
return;
}
sendMessage2Client(emitter, eventData);
}).onComplete((responseMessage) -> {
// 打印流程耗时日志
printChatDuration(requestId, "LLM输出消息完成");
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId);
// 记录ai的回复
AiMessage aiMessage = responseMessage.content();
FinishReason finishReason = responseMessage.finishReason();
String respText = aiMessage.text();
// sse
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
if (null == emitter) {
log.warn("[AI应用]接收LLM返回会话已关闭");
return;
}
if (FinishReason.STOP.equals(finishReason) || null == finishReason) {
// 正常结束
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END, chatConversation.getId(), topicId);
appendMessage(messages, aiMessage, chatConversation, topicId);
// 保存会话
saveChatConversation(chatConversation, false, httpRequest);
closeSSE(emitter, eventData);
} else if (FinishReason.TOOL_EXECUTION.equals(finishReason)) {
// 需要执行工具
// TODO author: chenrui for: date:2025/3/7
} else if (FinishReason.LENGTH.equals(finishReason)) {
// 上下文长度超过限制
log.error("调用模型异常:上下文长度超过限制:{}", responseMessage.tokenUsage());
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
eventData.setData(EventMessageData.builder().message("\n上下文长度超过限制,请调整模型最大Tokens").build());
sendMessage2Client(emitter, eventData);
eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END, chatConversation.getId(), topicId);
closeSSE(emitter, eventData);
} else {
// 异常结束
log.error("调用模型异常:" + respText);
if (respText.contains("insufficient Balance")) {
respText = "大预言模型账号余额不足!";
}
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
eventData.setData(EventFlowData.builder().success(false).message(respText).build());
closeSSE(emitter, eventData);
}
}).onError((Throwable error) -> {
// 打印流程耗时日志
printChatDuration(requestId, "LLM输出消息异常");
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId);
// sse
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
if (null == emitter) {
log.warn("[AI应用]接收LLM返回会话已关闭{}", requestId);
return;
}
log.error(error.getMessage(), error);
String errMsg = error.getMessage();
if (errMsg != null && errMsg.contains("timeout")) {
//update-begin---author:chenrui ---date:20250425 for[QQYUN-12203]AI 聊天,超时或者服务器报错,给个友好提示------------
errMsg = "当前用户较多,排队中,请稍后再试!";
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
eventData.setData(EventMessageData.builder().message("\n" + errMsg).build());
sendMessage2Client(emitter, eventData);
eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END, chatConversation.getId(), topicId);
closeSSE(emitter, eventData);
//update-end---author:chenrui ---date:20250425 for[QQYUN-12203]AI 聊天,超时或者服务器报错,给个友好提示------------
} else {
errMsg = "调用大模型接口失败:" + errMsg;
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
eventData.setData(EventFlowData.builder().success(false).message(errMsg).build());
closeSSE(emitter, eventData);
}
}).start();
}
/**
* 发送消息到客户端
*
* @param emitter
* @param eventData
* @author chenrui
* @date 2025/4/22 19:58
*/
private static void sendMessage2Client(SseEmitter emitter, EventData eventData) {
try {
log.info("发送消息:{}", eventData.getRequestId());
String eventStr = JSONObject.toJSONString(eventData);
log.debug("[AI应用]接收LLM返回消息:{}", eventStr);
emitter.send(SseEmitter.event().data(eventStr));
} catch (IOException e) {
log.error("发送消息失败", e);
}
}
/**
* 发送聊天返回结果
*
* @author chenrui
* @date 2025/2/28 11:05
*/
private static class ChatResult {
public final SseEmitter emitter;
public final AiragModel chatModel;
public ChatResult(SseEmitter emitter, AiragModel chatModel) {
this.emitter = emitter;
this.chatModel = chatModel;
}
}
/**
* 总结会话标题
* 几个问题: <br/>
* 1. 如果在发消息时同步总结会话标题,会导致接口很慢甚至超时.
* 2. 但如果异步更新会话标题会导致消息记录丢失(不全)或者标题丢失,需要写很多逻辑去保证最终一致
* so 暂时先不用AI更新会话标题. 后期如果需要单独再增加一个接口,由前端调用或者在第一次消息接收完成后再异步更新
*
* @param chatConversation
* @param question
* @param modelId
* @return
* @author chenrui
* @date 2025/2/25 17:12
*/
protected void summaryConversationTitle(ChatConversation chatConversation, String question, String modelId) {
if (oConvertUtils.isEmpty(chatConversation.getId())) {
return;
}
String key = getConversationCacheKey(chatConversation.getId(), null);
if (oConvertUtils.isEmpty(key)) {
return;
}
CompletableFuture.runAsync(() -> {
List<ChatMessage> messages = new LinkedList<>();
String systemMsgStr = "根据用户的问题,总结会话标题.\n" + "要求如下:\n" + "1. 使用中文回答.\n" + "2. 标题长度控制在5个汉字10个英文字符以内\n" + "3. 直接回复会话标题,不要有其他任何无关描述\n" + "4. 如果无法总结,回复不知道\n";
messages.add(new SystemMessage(systemMsgStr));
messages.add(new UserMessage(question));
String summaryTitle;
try {
summaryTitle = aiChatHandler.completions(modelId, messages, null);
log.info("总结会话完成{}", summaryTitle);
if (summaryTitle.equalsIgnoreCase("不知道")) {
summaryTitle = "";
}
} catch (Exception e) {
log.warn("AI总结会话失败" + e.getMessage(), e);
summaryTitle = "";
}
// 更新会话标题
ChatConversation cachedConversation = (ChatConversation) redisTemplate.boundValueOps(key).get();
if (null == cachedConversation) {
cachedConversation = chatConversation;
}
if (oConvertUtils.isEmpty(chatConversation.getTitle())) {
// 再次判断标题是否为空,只有标题为空才更新
if (oConvertUtils.isNotEmpty(summaryTitle)) {
cachedConversation.setTitle(summaryTitle);
} else {
cachedConversation.setTitle(question.length() > 5 ? question.substring(0, 5) : question);
}
//保存会话
saveChatConversation(cachedConversation);
}
});
}
/**
* 获取用户名
*
* @param httpRequest
* @return
* @author chenrui
* @date 2025/3/27 15:05
*/
private String getUsername(HttpServletRequest httpRequest) {
try {
TokenUtils.getTokenByRequest();
String token;
if (null != httpRequest) {
token = TokenUtils.getTokenByRequest(httpRequest);
} else {
token = TokenUtils.getTokenByRequest();
}
if (TokenUtils.verifyToken(token, sysBaseApi, redisUtil)) {
return JwtUtil.getUsername(token);
}
} catch (Exception e) {
return null;
}
return null;
}
/**
* 打印耗时
* @param requestId
* @param message
* @author chenrui
* @date 2025/4/28 15:15
*/
private static void printChatDuration(String requestId,String message) {
Long beginTime = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId);
if (null != beginTime) {
log.info("[AI-CHAT]{},requestId:{},耗时:{}s", message, requestId, (System.currentTimeMillis() - beginTime) / 1000);
}
}
}
@@ -0,0 +1,20 @@
package org.jeecg.modules.airag.app.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.jeecg.modules.airag.app.entity.AiragApp;
/**
* @Description: 应用调试入参
* @Author: chenrui
* @Date: 2025/2/25 11:47
*/
@Data
public class AppDebugParams extends ChatSendParams {
/**
* 应用信息
*/
AiragApp app;
}
@@ -0,0 +1,42 @@
package org.jeecg.modules.airag.app.vo;
import lombok.Data;
import org.jeecg.modules.airag.app.entity.AiragApp;
import org.jeecg.modules.airag.common.vo.MessageHistory;
import java.util.Date;
import java.util.List;
/**
* @Description: 聊天会话
* @Author: chenrui
* @Date: 2025/2/25 14:56
*/
@Data
public class ChatConversation {
/**
* 会话id
*/
private String id;
/**
* 会话标题
*/
private String title;
/**
* 消息记录
*/
private List<MessageHistory> messages;
/**
* app
*/
private AiragApp app;
/**
* 创建时间
*/
private Date createTime;
}
@@ -0,0 +1,49 @@
package org.jeecg.modules.airag.app.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @Description: 发送消息的入参
* @Author: chenrui
* @Date: 2025/2/25 11:47
*/
@NoArgsConstructor
@Data
public class ChatSendParams {
public ChatSendParams(String content, String conversationId, String topicId, String appId) {
this.content = content;
this.conversationId = conversationId;
this.topicId = topicId;
this.appId = appId;
}
/**
* 用户输入的聊天内容
*/
private String content;
/**
* 对话会话ID
*/
private String conversationId;
/**
* 对话主题ID(用于关联历史记录)
*/
private String topicId;
/**
* 应用id
*/
private String appId;
/**
* 图片列表
*/
private List<String> images;
}
@@ -0,0 +1,83 @@
package org.jeecg.modules.airag.demo;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.exception.JeecgBootBizTipException;
import org.jeecg.modules.airag.flow.component.enhance.IAiRagEnhanceJava;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Java增强Demo: Excel数据读取器
* for [QQYUN-11718]【AI】积木报表对接AI流程编排接口展示报表
* @Author: chenrui
* @Date: 2025/4/29 16:51
*/
@Component("jimuDataReader")
@Slf4j
public class JimuDataReader implements IAiRagEnhanceJava {
@Override
public Map<String, Object> process(Map<String, Object> inputParams) {
// inputParams: {"bizData":"/xxxx/xxxx/xxxx/xxxx.xls"}
try {
String filePath = (String) inputParams.get("bizData");
if (filePath == null || filePath.isEmpty()) {
throw new IllegalArgumentException("File path is empty");
}
File excelFile = new File(filePath);
if (!excelFile.exists() || !excelFile.isFile()) {
throw new IllegalArgumentException("File not found: " + filePath);
}
// Since we don't know the target entity class, we'll read the Excel generically
return readExcelData(excelFile);
} catch (Exception e) {
log.error("Error processing Excel file", e);
throw new JeecgBootBizTipException("调用java增强失败", e);
}
}
/**
* Excel导入工具方法,基于ExcelImportUtil
*
* @param file Excel文件
* @return Excel读取结果,包含字段和数据
* @throws Exception 导入过程中的异常
*/
public static Map<String, Object> readExcelData(File file) throws Exception {
Map<String, Object> result = new HashMap<>();
// 设置导入参数
ImportParams params = new ImportParams();
params.setTitleRows(0); // 没有标题
params.setHeadRows(1); // 第一行是表头
// 读取Excel数据
List<Map<String, Object>> dataList = ExcelImportUtil.importExcel(file, Map.class, params);
// 如果没有数据,返回空结果
if (dataList == null || dataList.isEmpty()) {
result.put("fields", new ArrayList<>());
result.put("datas", new ArrayList<>());
return result;
}
// 从第一行数据中获取字段名
List<String> fieldNames = new ArrayList<>(dataList.get(0).keySet());
result.put("fields", fieldNames);
result.put("datas", dataList);
return result;
}
}
@@ -0,0 +1,22 @@
package org.jeecg.modules.airag.demo;
import org.jeecg.modules.airag.flow.component.enhance.IAiRagEnhanceJava;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.Map;
/**
* @Description: Java增强节点示例类
* @Author: chenrui
* @Date: 2025/3/6 11:42
*/
@Component("testAiragEnhance")
public class TestAiragEnhance implements IAiRagEnhanceJava {
@Override
public Map<String, Object> process(Map<String, Object> inputParams) {
Object arg1 = inputParams.get("arg1");
Object arg2 = inputParams.get("arg2");
return Collections.singletonMap("result",arg1.toString()+"java拼接"+arg2.toString());
}
}
@@ -0,0 +1,47 @@
package org.jeecg.modules.airag.llm.config;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 向量存储库配置
*
* @Author: chenrui
* @Date: 2025/2/18 14:24
*/
@NoArgsConstructor
@Data
@Component
@ConfigurationProperties(prefix = EmbedStoreConfigBean.PREFIX)
public class EmbedStoreConfigBean {
public static final String PREFIX = "jeecg.airag.embed-store";
/**
* host
*/
private String host = "127.0.0.1";
/**
* 端口
*/
private int port = 5432;
/**
* 数据库
*/
private String database = "postgres";
/**
* 用户名
*/
private String user = "postgres";
/**
* 密码
*/
private String password = "postgres";
/**
* 存储向量的表
*/
private String table = "embeddings";
}
@@ -0,0 +1,31 @@
package org.jeecg.modules.airag.llm.config;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 知识库配置
*
* @Author: chenrui
* @Date: 2025-04-01 14:19
*/
@NoArgsConstructor
@Data
@Component
@ConfigurationProperties(prefix = KnowConfigBean.PREFIX)
public class KnowConfigBean {
public static final String PREFIX = "jeecg.airag.know";
/**
* 开启MinerU解析
*/
private boolean enableMinerU = false;
/**
* conda的环境(默认不使用conda)
*/
private String condaEnv = null;
}
@@ -0,0 +1,83 @@
package org.jeecg.modules.airag.llm.consts;
import java.util.regex.Pattern;
/**
* @Description: airag模型常量类
* @Author: chenrui
* @Date: 2025/2/12 17:35
*/
public class LLMConsts {
/**
* 正则表达式:是否是网页
*/
public static final Pattern WEB_PATTERN = Pattern.compile("^(http|https)://.*");
/**
* 状态:启用
*/
public static final String STATUS_ENABLE = "enable";
/**
* 状态:禁用
*/
public static final String STATUS_DISABLE = "disable";
/**
* 模型类型:向量
*/
public static final String MODEL_TYPE_EMBED = "EMBED";
/**
* 模型类型:聊天
*/
public static final String MODEL_TYPE_LLM = "LLM";
/**
* 向量模型:默认维度
*/
public static final Integer EMBED_MODEL_DEFAULT_DIMENSION = 1536;
/**
* 知识库:文档状态:草稿
*/
public static final String KNOWLEDGE_DOC_STATUS_DRAFT = "draft";
/**
* 知识库:文档状态:构建中
*/
public static final String KNOWLEDGE_DOC_STATUS_BUILDING = "building";
/**
* 知识库:文档状态:构建完成
*/
public static final String KNOWLEDGE_DOC_STATUS_COMPLETE = "complete";
/**
* 知识库:文档状态:构建失败
*/
public static final String KNOWLEDGE_DOC_STATUS_FAILED = "failed";
/**
* 知识库:文档类型:文本
*/
public static final String KNOWLEDGE_DOC_TYPE_TEXT = "text";
/**
* 知识库:文档类型:文件
*/
public static final String KNOWLEDGE_DOC_TYPE_FILE = "file";
/**
* 知识库:文档类型:网页
*/
public static final String KNOWLEDGE_DOC_TYPE_WEB = "web";
/**
* 知识库:文档元数据:文件路径
*/
public static final String KNOWLEDGE_DOC_METADATA_FILEPATH = "filePath";
/**
* 知识库:文档元数据:资源路径
*/
public static final String KNOWLEDGE_DOC_METADATA_SOURCES_PATH = "sourcesPath";
}
@@ -0,0 +1,361 @@
package org.jeecg.modules.airag.llm.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.AssertUtils;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
import org.jeecg.modules.airag.common.vo.knowledge.KnowledgeSearchResult;
import org.jeecg.modules.airag.llm.consts.LLMConsts;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
import org.jeecg.modules.airag.llm.entity.AiragKnowledgeDoc;
import org.jeecg.modules.airag.llm.handler.EmbeddingHandler;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeDocService;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* @Description: AIRag知识库
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
@RestController
@RequestMapping("/airag/knowledge")
@Slf4j
public class AiragKnowledgeController {
@Autowired
private IAiragKnowledgeService airagKnowledgeService;
@Autowired
private IAiragKnowledgeDocService airagKnowledgeDocService;
@Autowired
EmbeddingHandler embeddingHandler;
/**
* 分页列表查询知识库
*
* @param airagKnowledge
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<AiragKnowledge>> queryPageList(AiragKnowledge airagKnowledge,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<AiragKnowledge> queryWrapper = QueryGenerator.initQueryWrapper(airagKnowledge, req.getParameterMap());
Page<AiragKnowledge> page = new Page<AiragKnowledge>(pageNo, pageSize);
IPage<AiragKnowledge> pageList = airagKnowledgeService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 添加知识库
*
* @param airagKnowledge 知识库
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@PostMapping(value = "/add")
@RequiresPermissions("airag:knowledge:add")
public Result<String> add(@RequestBody AiragKnowledge airagKnowledge) {
airagKnowledge.setStatus(LLMConsts.STATUS_ENABLE);
airagKnowledgeService.save(airagKnowledge);
return Result.OK("添加成功!");
}
/**
* 编辑知识库
*
* @param airagKnowledge 知识库
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@Transactional(rollbackFor = Exception.class)
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
@RequiresPermissions("airag:knowledge:edit")
public Result<String> edit(@RequestBody AiragKnowledge airagKnowledge) {
AiragKnowledge airagKnowledgeEntity = airagKnowledgeService.getById(airagKnowledge.getId());
if (airagKnowledgeEntity == null) {
return Result.error("未找到对应数据");
}
String oldEmbedId = airagKnowledgeEntity.getEmbedId();
airagKnowledgeService.updateById(airagKnowledge);
if (!oldEmbedId.equalsIgnoreCase(airagKnowledge.getEmbedId())) {
// 更新了模型,重建文档
airagKnowledgeDocService.rebuildDocumentByKnowId(airagKnowledge.getId());
}
return Result.OK("编辑成功!");
}
/**
* 重建知识库
*
* @param knowIds
* @return
* @author chenrui
* @date 2025/3/12 17:05
*/
@PutMapping(value = "/rebuild")
@RequiresPermissions("airag:knowledge:rebuild")
public Result<?> rebuild(@RequestParam("knowIds") String knowIds) {
String[] knowIdArr = knowIds.split(",");
for (String knowId : knowIdArr) {
airagKnowledgeDocService.rebuildDocumentByKnowId(knowId);
}
return Result.OK("");
}
/**
* 通过id删除知识库
*
* @param id
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@Transactional(rollbackFor = Exception.class)
@DeleteMapping(value = "/delete")
@RequiresPermissions("airag:knowledge:delete")
public Result<String> delete(HttpServletRequest request, @RequestParam(name = "id", required = true) String id) {
//update-begin---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
//如果是saas隔离的情况下,判断当前租户id是否是当前租户下的
if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
AiragKnowledge know = airagKnowledgeService.getById(id);
//获取当前租户
String currentTenantId = TokenUtils.getTenantIdByRequest(request);
if (null == know || !know.getTenantId().equals(currentTenantId)) {
return Result.error("删除AI知识库失败,不能删除其他租户的AI知识库!");
}
}
//update-end---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
airagKnowledgeDocService.removeByKnowIds(Collections.singletonList(id));
airagKnowledgeService.removeById(id);
return Result.OK("删除成功!");
}
/**
* 通过id查询知识库
*
* @param id
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@GetMapping(value = "/queryById")
public Result<AiragKnowledge> queryById(@RequestParam(name = "id", required = true) String id) {
AiragKnowledge airagKnowledge = airagKnowledgeService.getById(id);
if (airagKnowledge == null) {
return Result.error("未找到对应数据");
}
return Result.OK(airagKnowledge);
}
/**
* 文档分页查询
*
* @param airagKnowledgeDoc
* @param pageNo
* @param pageSize
* @param req
* @return
* @author chenrui
* @date 2025/2/18 18:37
*/
@GetMapping(value = "/doc/list")
public Result<IPage<AiragKnowledgeDoc>> queryDocumentPageList(AiragKnowledgeDoc airagKnowledgeDoc,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
AssertUtils.assertNotEmpty("请先选择知识库", airagKnowledgeDoc.getKnowledgeId());
QueryWrapper<AiragKnowledgeDoc> queryWrapper = QueryGenerator.initQueryWrapper(airagKnowledgeDoc, req.getParameterMap());
Page<AiragKnowledgeDoc> page = new Page<>(pageNo, pageSize);
IPage<AiragKnowledgeDoc> pageList = airagKnowledgeDocService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 新增或编辑文档
*
* @param airagKnowledgeDoc 知识库文档
* @return
* @author chenrui
* @date 2025/2/18 15:47
*/
@PostMapping(value = "/doc/edit")
@RequiresPermissions("airag:knowledge:doc:edit")
public Result<?> addDocument(@RequestBody AiragKnowledgeDoc airagKnowledgeDoc) {
return airagKnowledgeDocService.editDocument(airagKnowledgeDoc);
}
/**
* 从压缩包导入文档
* @return
* @author chenrui
* @date 2025/3/20 11:29
*/
@PostMapping(value = "/doc/import/zip")
@RequiresPermissions("airag:knowledge:doc:zip")
public Result<?> importDocumentFromZip(@RequestParam(name = "knowId", required = true) String knowId,
@RequestParam(name = "file", required = true) MultipartFile file) {
return airagKnowledgeDocService.importDocumentFromZip(knowId,file);
}
/**
* 通过文档库查询导入任务列表
* @param knowId
* @return
* @author chenrui
* @date 2025/3/20 11:37
*/
@GetMapping(value = "/doc/import/task/list")
public Result<?> importDocumentTaskList(@RequestParam(name = "knowId", required = true) String knowId) {
return Result.OK(Collections.emptyList());
}
/**
* 重新向量化文档
*
* @param docIds 文档id集合
* @return
* @author chenrui
* @date 2025/2/18 15:47
*/
@PutMapping(value = "/doc/rebuild")
@RequiresPermissions("airag:knowledge:doc:rebuild")
public Result<?> rebuildDocument(@RequestParam("docIds") String docIds) {
return airagKnowledgeDocService.rebuildDocument(docIds);
}
/**
* 批量删除文档
*
* @param ids
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@Transactional(rollbackFor = Exception.class)
@DeleteMapping(value = "/doc/deleteBatch")
@RequiresPermissions("airag:knowledge:doc:deleteBatch")
public Result<String> deleteDocumentBatch(HttpServletRequest request, @RequestParam(name = "ids", required = true) String ids) {
List<String> idsList = Arrays.asList(ids.split(","));
//update-begin---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
//如果是saas隔离的情况下,判断当前租户id是否是当前租户下的
if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
List<AiragKnowledgeDoc> docList = airagKnowledgeDocService.listByIds(idsList);
//获取当前租户
String currentTenantId = TokenUtils.getTenantIdByRequest(request);
docList.forEach(airagKnowledgeDoc -> {
if (null == airagKnowledgeDoc || !airagKnowledgeDoc.getTenantId().equals(currentTenantId)) {
throw new IllegalArgumentException("删除AI知识库文档失败,不能删除其他租户的AI知识库文档!");
}
});
}
//update-end---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
airagKnowledgeDocService.removeDocByIds(idsList);
return Result.OK("批量删除成功!");
}
/**
* 清空知识库文档
*
* @param
* @return
*/
@Transactional(rollbackFor = Exception.class)
@DeleteMapping(value = "/doc/deleteAll")
@RequiresPermissions("airag:knowledge:doc:deleteAll")
public Result<?> deleteDocumentAll(HttpServletRequest request, @RequestParam(name = "knowId") String knowId) {
//update-begin---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
//如果是saas隔离的情况下,判断当前租户id是否是当前租户下的
if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
AiragKnowledge know = airagKnowledgeService.getById(knowId);
//获取当前租户
String currentTenantId = TokenUtils.getTenantIdByRequest(request);
if (null == know || !know.getTenantId().equals(currentTenantId)) {
return Result.error("删除AI知识库失败,不能删除其他租户的AI知识库!");
}
}
//update-end---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
return airagKnowledgeDocService.deleteAllByKnowId(knowId);
}
/**
* 命中测试
*
* @param knowId 知识库id
* @param queryText 查询内容
* @param topNumber 最多返回条数
* @param similarity 最小分数
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@GetMapping(value = "/embedding/hitTest/{knowId}")
public Result<?> hitTest(@PathVariable("knowId") String knowId,
@RequestParam(name = "queryText") String queryText,
@RequestParam(name = "topNumber") Integer topNumber,
@RequestParam(name = "similarity") Double similarity) {
List<Map<String, Object>> searchResp = embeddingHandler.searchEmbedding(knowId, queryText, topNumber, similarity);
return Result.ok(searchResp);
}
/**
* 向量查询
*
* @param knowIds 知识库ids
* @param queryText 查询内容
* @param topNumber 最多返回条数
* @param similarity 最小分数
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@GetMapping(value = "/embedding/search")
public Result<?> embeddingSearch(@RequestParam("knowIds") List<String> knowIds,
@RequestParam(name = "queryText") String queryText,
@RequestParam(name = "topNumber", required = false) Integer topNumber,
@RequestParam(name = "similarity", required = false) Double similarity) {
KnowledgeSearchResult searchResp = embeddingHandler.embeddingSearch(knowIds, queryText, topNumber, similarity);
return Result.ok(searchResp);
}
/**
* 通过ids批量查询知识库
*
* @param ids
* @return
* @author chenrui
* @date 2025/2/27 16:44
*/
@GetMapping(value = "/query/batch/byId")
public Result<?> queryBatchByIds(@RequestParam(name = "ids", required = true) String ids) {
List<String> idList = Arrays.asList(ids.split(","));
List<AiragKnowledge> airagKnowledges = airagKnowledgeService.listByIds(idList);
return Result.OK(airagKnowledges);
}
}
@@ -0,0 +1,180 @@
package org.jeecg.modules.airag.llm.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.embedding.EmbeddingModel;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.ai.factory.AiModelFactory;
import org.jeecg.ai.factory.AiModelOptions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.AssertUtils;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
import org.jeecg.modules.airag.llm.consts.LLMConsts;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import org.jeecg.modules.airag.llm.handler.AIChatHandler;
import org.jeecg.modules.airag.llm.handler.EmbeddingHandler;
import org.jeecg.modules.airag.llm.service.IAiragModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Collections;
/**
* @Description: AiRag模型配置
* @Author: jeecg-boot
* @Date: 2025-02-14
* @Version: V1.0
*/
@Tag(name = "AiRag模型配置")
@RestController
@RequestMapping("/airag/airagModel")
@Slf4j
public class AiragModelController extends JeecgController<AiragModel, IAiragModelService> {
@Autowired
private IAiragModelService airagModelService;
@Autowired
AIChatHandler aiChatHandler;
/**
* 分页列表查询
*
* @param airagModel
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<AiragModel>> queryPageList(AiragModel airagModel, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
QueryWrapper<AiragModel> queryWrapper = QueryGenerator.initQueryWrapper(airagModel, req.getParameterMap());
Page<AiragModel> page = new Page<AiragModel>(pageNo, pageSize);
IPage<AiragModel> pageList = airagModelService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 添加
*
* @param airagModel
* @return
*/
@PostMapping(value = "/add")
@RequiresPermissions("airag:model:add")
public Result<String> add(@RequestBody AiragModel airagModel) {
// 验证 模型名称/模型类型/基础模型
AssertUtils.assertNotEmpty("模型名称不能为空", airagModel.getName());
AssertUtils.assertNotEmpty("模型类型不能为空", airagModel.getModelType());
AssertUtils.assertNotEmpty("基础模型不能为空", airagModel.getModelName());
airagModelService.save(airagModel);
return Result.OK("添加成功!");
}
/**
* 编辑
*
* @param airagModel
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
@RequiresPermissions("airag:model:edit")
public Result<String> edit(@RequestBody AiragModel airagModel) {
airagModelService.updateById(airagModel);
return Result.OK("编辑成功!");
}
/**
* 通过id删除
*
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
@RequiresPermissions("airag:model:delete")
public Result<String> delete(HttpServletRequest request, @RequestParam(name = "id", required = true) String id) {
//update-begin---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
//如果是saas隔离的情况下,判断当前租户id是否是当前租户下的
if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
AiragModel model = airagModelService.getById(id);
//获取当前租户
String currentTenantId = TokenUtils.getTenantIdByRequest(request);
if (null == model || !model.getTenantId().equals(currentTenantId)) {
return Result.error("删除AI模型失败,不能删除其他租户的AI模型!");
}
}
//update-end---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
airagModelService.removeById(id);
return Result.OK("删除成功!");
}
/**
* 通过id查询
*
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<AiragModel> queryById(@RequestParam(name = "id", required = true) String id) {
AiragModel airagModel = airagModelService.getById(id);
if (airagModel == null) {
return Result.error("未找到对应数据");
}
return Result.OK(airagModel);
}
/**
* 导出excel
*
* @param request
* @param airagModel
*/
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, AiragModel airagModel) {
return super.exportXls(request, airagModel, AiragModel.class, "AiRag模型配置");
}
/**
* 通过excel导入数据
*
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, AiragModel.class);
}
@PostMapping(value = "/test")
public Result<?> test(@RequestBody AiragModel airagModel) {
// 验证 模型名称/模型类型/基础模型
AssertUtils.assertNotEmpty("模型名称不能为空", airagModel.getName());
AssertUtils.assertNotEmpty("模型类型不能为空", airagModel.getModelType());
AssertUtils.assertNotEmpty("基础模型不能为空", airagModel.getModelName());
try {
if(LLMConsts.MODEL_TYPE_LLM.equals(airagModel.getModelType())){
aiChatHandler.completions(airagModel, Collections.singletonList(UserMessage.from("test connection")), null);
}else{
AiModelOptions aiModelOptions = EmbeddingHandler.buildModelOptions(airagModel);
EmbeddingModel embeddingModel = AiModelFactory.createEmbeddingModel(aiModelOptions);
embeddingModel.embed("test text");
}
}catch (Exception e){
log.error("测试模型连接失败", e);
return Result.error("测试模型连接失败" + e.getMessage());
}
return Result.OK("测试模型连接成功");
}
}
@@ -0,0 +1,274 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.jeecg.modules.airag.llm.document;
import dev.langchain4j.data.document.BlankDocumentException;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.internal.Utils;
import org.apache.commons.io.FilenameUtils;
import org.apache.poi.hslf.usermodel.HSLFTextParagraph;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xslf.usermodel.XSLFSlide;
import org.apache.poi.xslf.usermodel.XSLFTextShape;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.tika.Tika;
import org.apache.tika.exception.ZeroByteFileException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;
import org.jeecg.common.util.AssertUtils;
import org.xml.sax.ContentHandler;
import java.io.*;
import java.nio.file.Files;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* tika文档解析器,重写langchain4j的TikaDocumentParser <br/>
* jeecgboot目前不支持poi5.x,所以langchain4j同的方法不能用,自己实现
* @author chenrui
* @date 2025/3/5 16:19
*/
public class TikaDocumentParser {
private static final Tika tika = new Tika();
private static final int NO_WRITE_LIMIT = -1;
public static final Supplier<Parser> DEFAULT_PARSER_SUPPLIER = AutoDetectParser::new;
public static final Supplier<Metadata> DEFAULT_METADATA_SUPPLIER = Metadata::new;
public static final Supplier<ParseContext> DEFAULT_PARSE_CONTEXT_SUPPLIER = ParseContext::new;
public static final Supplier<ContentHandler> DEFAULT_CONTENT_HANDLER_SUPPLIER = () -> new BodyContentHandler(-1);
private final Supplier<Parser> parserSupplier;
private final Supplier<ContentHandler> contentHandlerSupplier;
private final Supplier<Metadata> metadataSupplier;
private final Supplier<ParseContext> parseContextSupplier;
public TikaDocumentParser() {
this((Supplier) ((Supplier) null), (Supplier) null, (Supplier) null, (Supplier) null);
}
public TikaDocumentParser(Supplier<Parser> parserSupplier, Supplier<ContentHandler> contentHandlerSupplier, Supplier<Metadata> metadataSupplier, Supplier<ParseContext> parseContextSupplier) {
this.parserSupplier = (Supplier) Utils.getOrDefault(parserSupplier, () -> DEFAULT_PARSER_SUPPLIER);
this.contentHandlerSupplier = (Supplier) Utils.getOrDefault(contentHandlerSupplier, () -> DEFAULT_CONTENT_HANDLER_SUPPLIER);
this.metadataSupplier = (Supplier) Utils.getOrDefault(metadataSupplier, () -> DEFAULT_METADATA_SUPPLIER);
this.parseContextSupplier = (Supplier) Utils.getOrDefault(parseContextSupplier, () -> DEFAULT_PARSE_CONTEXT_SUPPLIER);
}
public Document parse(File file) {
AssertUtils.assertNotEmpty("请选择文件", file);
try {
// 用于解析
InputStream isForParsing = Files.newInputStream(file.toPath());
// 使用 Tika 自动检测 MIME 类型
String fileName = file.getName().toLowerCase();
if (fileName.endsWith(".txt")
|| fileName.endsWith(".md")
|| fileName.endsWith(".pdf")) {
return extractByTika(isForParsing);
} else if (fileName.endsWith(".docx")) {
return extractTextFromDocx(isForParsing);
} else if (fileName.endsWith(".doc")) {
return extractTextFromDoc(isForParsing);
} else if (fileName.endsWith(".xlsx")) {
return extractTextFromExcel(isForParsing);
} else if (fileName.endsWith(".xls")) {
return extractTextFromExcel(isForParsing);
} else if (fileName.endsWith(".pptx")) {
return extractTextFromPptx(isForParsing);
} else if (fileName.endsWith(".ppt")) {
return extractTextFromPpt(isForParsing);
} else {
throw new IllegalArgumentException("不支持的文件格式: " + FilenameUtils.getExtension(fileName));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static Document tryExtractDocOrDocx(InputStream inputStream) throws IOException {
try {
// 先尝试 DOCX(基于 OPC XML 格式)
return extractTextFromDocx(inputStream);
} catch (Exception e1) {
try {
// 如果 DOCX 解析失败,则尝试 DOC(基于二进制格式)
return extractTextFromDoc(inputStream);
} catch (Exception e2) {
throw new IOException("无法解析 DOC 或 DOCX 文件", e2);
}
}
}
/**
* 使用tika提取文件内容 <br/>
* pdf/text/md等文件使用tika提取
*
* @param inputStream
* @return
* @author chenrui
* @date 2025/3/5 14:41
*/
private Document extractByTika(InputStream inputStream) {
try {
Parser parser = (Parser) this.parserSupplier.get();
ContentHandler contentHandler = (ContentHandler) this.contentHandlerSupplier.get();
Metadata metadata = (Metadata) this.metadataSupplier.get();
ParseContext parseContext = (ParseContext) this.parseContextSupplier.get();
parser.parse(inputStream, contentHandler, metadata, parseContext);
String text = contentHandler.toString();
if (Utils.isNullOrBlank(text)) {
throw new BlankDocumentException();
} else {
return Document.from(text);
}
} catch (BlankDocumentException e) {
throw e;
} catch (ZeroByteFileException var8) {
throw new BlankDocumentException();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 提取docx文件内容
*
* @param inputStream
* @return
* @throws IOException
* @author chenrui
* @date 2025/3/5 14:42
* @deprecated 因为jeecg主项目目前不支持poi5.x, 自己实现提取功能.
*/
@Deprecated
private static Document extractTextFromDocx(InputStream inputStream) throws IOException {
try (XWPFDocument document = new XWPFDocument(inputStream)) {
StringBuilder text = new StringBuilder();
for (XWPFParagraph para : document.getParagraphs()) {
text.append(para.getText()).append("\n");
}
return Document.from(text.toString());
}
}
/**
* 提取doc文件内容
*
* @param inputStream
* @return
* @throws IOException
* @author chenrui
* @date 2025/3/5 14:42
* @deprecated 因为jeecg主项目目前不支持poi5.x, 自己实现提取功能.
*/
@Deprecated
private static Document extractTextFromDoc(InputStream inputStream) throws IOException {
try (HWPFDocument document = new HWPFDocument(inputStream);
WordExtractor extractor = new WordExtractor(document)) {
return Document.from(extractor.getText());
}
}
/**
* 提取excel文件内容
*
* @param inputStream
* @return
* @throws IOException
* @author chenrui
* @date 2025/3/5 14:43
* @deprecated 因为jeecg主项目目前不支持poi5.x, 自己实现提取功能.
*/
@Deprecated
private static Document extractTextFromExcel(InputStream inputStream) throws IOException {
try (Workbook workbook = WorkbookFactory.create(inputStream)) {
StringBuilder text = new StringBuilder();
for (Sheet sheet : workbook) {
text.append("Sheet: ").append(sheet.getSheetName()).append("\n");
for (Row row : sheet) {
for (Cell cell : row) {
text.append(cell.toString()).append("\t");
}
text.append("\n");
}
text.append("\n");
}
return Document.from(text.toString());
}
}
/**
* 提取pptx文件内容
*
* @param inputStream
* @return
* @throws IOException
* @author chenrui
* @date 2025/3/5 14:43
* @deprecated 因为jeecg主项目目前不支持poi5.x, 自己实现提取功能.
*/
@Deprecated
private static Document extractTextFromPptx(InputStream inputStream) throws IOException {
try (XMLSlideShow ppt = new XMLSlideShow(inputStream)) {
StringBuilder text = new StringBuilder();
for (XSLFSlide slide : ppt.getSlides()) {
text.append("Slide ").append(slide.getSlideNumber()).append(":\n");
List<XSLFTextShape> shapes = slide.getShapes().stream()
.filter(s -> s instanceof XSLFTextShape)
.map(s -> (XSLFTextShape) s)
.collect(Collectors.toList());
for (XSLFTextShape shape : shapes) {
text.append(shape.getText()).append("\n");
}
text.append("\n");
}
return Document.from(text.toString());
}
}
/**
* 提取ppt文件内容
*
* @param inputStream
* @return
* @throws IOException
* @author chenrui
* @date 2025/3/5 14:43
* @deprecated 因为jeecg主项目目前不支持poi5.x, 自己实现提取功能.
*/
@Deprecated
private static Document extractTextFromPpt(InputStream inputStream) throws IOException {
try (org.apache.poi.hslf.usermodel.HSLFSlideShow ppt = new org.apache.poi.hslf.usermodel.HSLFSlideShow(inputStream)) {
StringBuilder text = new StringBuilder();
for (org.apache.poi.hslf.usermodel.HSLFSlide slide : ppt.getSlides()) {
text.append("Slide ").append(slide.getSlideNumber()).append(":\n");
for (List<HSLFTextParagraph> shapes : slide.getTextParagraphs()) {
text.append(HSLFTextParagraph.getText(shapes)).append("\n");
}
text.append("\n");
}
return Document.from(text.toString());
}
}
private static byte[] toByteArray(InputStream inputStream) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[1024];
int nRead;
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
return buffer.toByteArray();
}
}
@@ -0,0 +1,105 @@
package org.jeecg.modules.airag.llm.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.jeecg.common.aspect.annotation.Dict;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
/**
* @Description: AIRag知识库
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
@Schema(description="AIRag知识库")
@Data
@TableName("airag_knowledge")
public class AiragKnowledge implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private java.lang.String id;
/**
* 创建人
*/
@Schema(description = "创建人")
@Dict(dictTable = "sys_user",dicCode = "username",dicText = "realname")
private java.lang.String createBy;
/**
* 创建日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建日期")
private java.util.Date createTime;
/**
* 更新人
*/
@Schema(description = "更新人")
private java.lang.String updateBy;
/**
* 更新日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新日期")
private java.util.Date updateTime;
/**
* 所属部门
*/
@Schema(description = "所属部门")
private java.lang.String sysOrgCode;
/**
* 租户id
*/
@Excel(name = "租户id", width = 15)
@Schema(description = "租户id")
private java.lang.String tenantId;
/**
* 知识库名称
*/
@Excel(name = "知识库名称", width = 15)
@Schema(description = "知识库名称")
private java.lang.String name;
/**
* 向量模型id
*/
@Excel(name = "向量模型id", width = 15, dictTable = "airag_model where model_type = 'EMBED'", dicText = "name", dicCode = "id")
@Dict(dictTable = "airag_model where model_type = 'EMBED'", dicText = "name", dicCode = "id")
@Schema(description = "向量模型id")
private java.lang.String embedId;
/**
* 描述
*/
@Excel(name = "描述", width = 15)
@Schema(description = "描述")
private java.lang.String descr;
/**
* 状态
*/
@Excel(name = "状态", width = 15)
@Schema(description = "状态")
private java.lang.String status;
}
@@ -0,0 +1,124 @@
package org.jeecg.modules.airag.llm.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.*;
import org.jeecg.common.aspect.annotation.Dict;
import org.jeecg.common.constant.ProvinceCityArea;
import org.jeecg.common.util.SpringContextUtils;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import java.util.Date;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.UnsupportedEncodingException;
/**
* @Description: airag知识库文档
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
@Schema(description="airag知识库文档")
@Data
@TableName("airag_knowledge_doc")
public class AiragKnowledgeDoc implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
/**
* 创建人
*/
@Schema(description = "创建人")
@Dict(dictTable = "sys_user",dicCode = "username",dicText = "realname")
private String createBy;
/**
* 创建日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建日期")
private Date createTime;
/**
* 更新人
*/
@Schema(description = "更新人")
private String updateBy;
/**
* 更新日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新日期")
private Date updateTime;
/**
* 所属部门
*/
@Schema(description = "所属部门")
private String sysOrgCode;
/**
* 租户id
*/
@Excel(name = "租户id", width = 15)
@Schema(description = "租户id")
private String tenantId;
/**
* 知识库id
*/
@Schema(description = "知识库id")
private String knowledgeId;
/**
* 标题
*/
@Excel(name = "标题", width = 15)
@Schema(description = "标题")
private String title;
/**
* 类型
*/
@Excel(name = "类型", width = 15, dicCode = "know_doc_type")
@Schema(description = "类型")
private String type;
/**
* 内容
*/
@Excel(name = "内容", width = 15)
@Schema(description = "内容")
private String content;
/**
* 元数据,存储上传文件的存储目录以及网站站点 <br/>
* eg. {"filePath":"https://xxxxxx","website":"http://hellp.jeecg.com"}
*/
@Excel(name = "元数据", width = 15)
@Schema(description = "元数据")
private String metadata;
/**
* 状态
*/
@Excel(name = "状态", width = 15)
@Schema(description = "状态")
private String status;
}
@@ -0,0 +1,124 @@
package org.jeecg.modules.airag.llm.entity;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import org.jeecg.common.constant.ProvinceCityArea;
import org.jeecg.common.util.SpringContextUtils;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.jeecg.common.aspect.annotation.Dict;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @Description: AiRag模型配置
* @Author: jeecg-boot
* @Date: 2025-02-17
* @Version: V1.0
*/
@Data
@TableName("airag_model")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description="AiRag模型配置")
public class AiragModel implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
/**
* 创建人
*/
@Schema(description = "创建人")
@Dict(dictTable = "sys_user",dicCode = "username",dicText = "realname")
private String createBy;
/**
* 创建日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建日期")
private Date createTime;
/**
* 更新人
*/
@Schema(description = "更新人")
private String updateBy;
/**
* 更新日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新日期")
private Date updateTime;
/**
* 所属部门
*/
@Schema(description = "所属部门")
private String sysOrgCode;
/**
* 租户id
*/
@Excel(name = "租户id", width = 15)
@Schema(description = "租户id")
private String tenantId;
/**
* 名称
*/
@Excel(name = "名称", width = 15)
@Schema(description = "名称")
private String name;
/**
* 供应者
*/
@Excel(name = "供应者", width = 15, dicCode = "model_provider")
@Dict(dicCode = "model_provider")
@Schema(description = "供应者")
private String provider;
/**
* 模型类型
*/
@Excel(name = "模型类型", width = 15, dicCode = "model_type")
@Dict(dicCode = "model_type")
@Schema(description = "模型类型")
private String modelType;
/**
* 模型名称
*/
@Excel(name = "模型名称", width = 15)
@Schema(description = "模型名称")
private String modelName;
/**
* API域名
*/
@Excel(name = "API域名", width = 15)
@Schema(description = "API域名")
private String baseUrl;
/**
* 凭证信息
*/
@Excel(name = "凭证信息", width = 15)
@Schema(description = "凭证信息")
private String credential;
/**
* 模型参数
*/
@Excel(name = "模型参数", width = 15)
@Schema(description = "模型参数")
private String modelParams;
}
@@ -0,0 +1,296 @@
package org.jeecg.modules.airag.llm.handler;
import com.alibaba.fastjson.JSONObject;
import dev.langchain4j.data.message.*;
import dev.langchain4j.rag.query.router.QueryRouter;
import dev.langchain4j.service.TokenStream;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.ai.handler.LLMHandler;
import org.jeecg.common.util.AssertUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.airag.common.handler.AIChatParams;
import org.jeecg.modules.airag.common.handler.IAIChatHandler;
import org.jeecg.modules.airag.llm.consts.LLMConsts;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import org.jeecg.modules.airag.llm.mapper.AiragModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.regex.Matcher;
/**
* 大模型聊天工具类
*
* @Author: chenrui
* @Date: 2025/2/18 14:31
*/
@Slf4j
@Component
public class AIChatHandler implements IAIChatHandler {
@Autowired
AiragModelMapper airagModelMapper;
@Autowired
EmbeddingHandler embeddingHandler;
@Autowired
LLMHandler llmHandler;
@Value(value = "${jeecg.path.upload:}")
private String uploadpath;
/**
* 问答
*
* @param modelId
* @param messages
* @return
* @author chenrui
* @date 2025/2/18 21:03
*/
@Override
public String completions(String modelId, List<ChatMessage> messages) {
AssertUtils.assertNotEmpty("至少发送一条消息", messages);
AssertUtils.assertNotEmpty("请选择模型", modelId);
// 整理消息
return completions(modelId, messages, null);
}
/**
* 问答
*
* @param modelId
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/2/18 21:03
*/
@Override
public String completions(String modelId, List<ChatMessage> messages, AIChatParams params) {
AssertUtils.assertNotEmpty("至少发送一条消息", messages);
AssertUtils.assertNotEmpty("请选择模型", modelId);
AiragModel airagModel = airagModelMapper.getByIdIgnoreTenant(modelId);
return completions(airagModel, messages, params);
}
/**
* 问答
*
* @param airagModel
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/2/24 17:30
*/
public String completions(AiragModel airagModel, List<ChatMessage> messages, AIChatParams params) {
params = mergeParams(airagModel, params);
String resp = llmHandler.completions(messages, params);
if (resp.contains("</think>")
&& (null == params.getNoThinking() || params.getNoThinking())) {
String[] thinkSplit = resp.split("</think>");
resp = thinkSplit[thinkSplit.length - 1];
}
return resp;
}
/**
* 使用默认模型问答
*
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/3/12 15:13
*/
@Override
public String completionsByDefaultModel(List<ChatMessage> messages, AIChatParams params) {
return completions(new AiragModel(), messages, params);
}
/**
* 聊天(流式)
*
* @param modelId
* @param messages
* @return
* @author chenrui
* @date 2025/2/20 21:06
*/
@Override
public TokenStream chat(String modelId, List<ChatMessage> messages) {
return chat(modelId, messages, null);
}
/**
* 聊天(流式)
*
* @param modelId
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/2/18 21:03
*/
@Override
public TokenStream chat(String modelId, List<ChatMessage> messages, AIChatParams params) {
AssertUtils.assertNotEmpty("至少发送一条消息", messages);
AssertUtils.assertNotEmpty("请选择模型", modelId);
AiragModel airagModel = airagModelMapper.getByIdIgnoreTenant(modelId);
return chat(airagModel, messages, params);
}
/**
* 聊天(流式)
*
* @param airagModel
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/2/24 17:29
*/
private TokenStream chat(AiragModel airagModel, List<ChatMessage> messages, AIChatParams params) {
params = mergeParams(airagModel, params);
return llmHandler.chat(messages, params);
}
/**
* 使用默认模型聊天
*
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/3/12 15:13
*/
@Override
public TokenStream chatByDefaultModel(List<ChatMessage> messages, AIChatParams params) {
return chat(new AiragModel(), messages, params);
}
/**
* 合并 airagmodel和params,params为准
*
* @param airagModel
* @param params
* @return
* @author chenrui
* @date 2025/3/11 17:45
*/
private AIChatParams mergeParams(AiragModel airagModel, AIChatParams params) {
if (null == airagModel) {
return params;
}
if (params == null) {
params = new AIChatParams();
}
params.setProvider(airagModel.getProvider());
params.setModelName(airagModel.getModelName());
params.setBaseUrl(airagModel.getBaseUrl());
if (oConvertUtils.isObjectNotEmpty(airagModel.getCredential())) {
JSONObject modelCredential = JSONObject.parseObject(airagModel.getCredential());
params.setApiKey(oConvertUtils.getString(modelCredential.getString("apiKey"), null));
params.setSecretKey(oConvertUtils.getString(modelCredential.getString("secretKey"), null));
}
if (oConvertUtils.isObjectNotEmpty(airagModel.getModelParams())) {
JSONObject modelParams = JSONObject.parseObject(airagModel.getModelParams());
if (oConvertUtils.isObjectEmpty(params.getTemperature())) {
params.setTemperature(modelParams.getDouble("temperature"));
}
if (oConvertUtils.isObjectEmpty(params.getTopP())) {
params.setTopP(modelParams.getDouble("topP"));
}
if (oConvertUtils.isObjectEmpty(params.getPresencePenalty())) {
params.setPresencePenalty(modelParams.getDouble("presencePenalty"));
}
if (oConvertUtils.isObjectEmpty(params.getFrequencyPenalty())) {
params.setFrequencyPenalty(modelParams.getDouble("frequencyPenalty"));
}
if (oConvertUtils.isObjectEmpty(params.getMaxTokens())) {
params.setMaxTokens(modelParams.getInteger("maxTokens"));
}
if (oConvertUtils.isObjectEmpty(params.getTimeout())) {
params.setTimeout(modelParams.getInteger("timeout"));
}
}
// RAG
List<String> knowIds = params.getKnowIds();
if (oConvertUtils.isObjectNotEmpty(knowIds)) {
QueryRouter queryRouter = embeddingHandler.getQueryRouter(knowIds, params.getTopNumber(), params.getSimilarity());
params.setQueryRouter(queryRouter);
}
// 设置确保maxTokens值正确
if (oConvertUtils.isObjectNotEmpty(params.getMaxTokens()) && params.getMaxTokens() <= 0) {
params.setMaxTokens(null);
}
// 默认超时时间
if(oConvertUtils.isObjectEmpty(params.getTimeout())){
params.setTimeout(60);
}
return params;
}
@Override
public UserMessage buildUserMessage(String content, List<String> images) {
AssertUtils.assertNotEmpty("请输入消息内容", content);
List<Content> contents = new ArrayList<>();
contents.add(TextContent.from(content));
if (oConvertUtils.isObjectNotEmpty(images)) {
// 获取所有图片,将他们转换为ImageContent
List<ImageContent> imageContents = buildImageContents(images);
contents.addAll(imageContents);
}
return UserMessage.from(contents);
}
@Override
public List<ImageContent> buildImageContents(List<String> images) {
List<ImageContent> imageContents = new ArrayList<>();
for (String imageUrl : images) {
Matcher matcher = LLMConsts.WEB_PATTERN.matcher(imageUrl);
if (matcher.matches()) {
// 来源于网络
imageContents.add(ImageContent.from(imageUrl));
} else {
// 本地文件
String filePath = uploadpath + File.separator + imageUrl;
// 读取文件并转换为 base64 编码字符串
try {
Path path = Paths.get(filePath);
byte[] fileContent = Files.readAllBytes(path);
String base64Data = Base64.getEncoder().encodeToString(fileContent);
// 获取文件的 MIME 类型
String mimeType = Files.probeContentType(path);
// 构建 ImageContent 对象
imageContents.add(ImageContent.from(base64Data, mimeType));
} catch (IOException e) {
log.error("读取文件失败: " + filePath, e);
throw new RuntimeException("发送消息失败,读取文件异常:" + e.getMessage(), e);
}
}
}
return imageContents;
}
}
@@ -0,0 +1,139 @@
package org.jeecg.modules.airag.llm.handler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Description: 命令行执行工具类
* @Author: chenrui
* @Date: 2024/4/8 10:11
*/
@Slf4j
public class CommandExecUtil {
/**
* 执行命令行
*
* @param command
* @param args
* @return
* @throws IOException
* @author chenrui
* @date 2024/4/9 10:59
*/
public static String execCommand(String command, String[] args) throws IOException {
if (null == command || command.isEmpty()) {
throw new IllegalArgumentException("命令不能为空");
}
return execCommand(command.split(" "), args);
}
/**
* 执行命令行
*
* @param command 脚本目录
* @param args 参数
* @author chenrui
* @date 2024/4/09 10:30
*/
public static String execCommand(String[] command, String[] args) throws IOException {
if (null == command || command.length == 0) {
throw new IllegalArgumentException("命令不能为空");
}
if (null != args && args.length > 0) {
command = (String[]) ArrayUtils.addAll(command, args);
}
// windows系统处理文件夹空格问题
if (System.getProperty("os.name").toLowerCase().startsWith("windows")) {
List<String> commandNew = new ArrayList<>(command.length + 2);
commandNew.addAll(Arrays.asList("cmd.exe", "/c"));
for (String tempCommand : command) {
if (tempCommand.contains(" ")) {
tempCommand = "\"" + tempCommand.replaceAll("\"", "'") + "\"";
}
commandNew.add(tempCommand);
}
command = commandNew.toArray(new String[0]);
}
Process process = null;
try {
log.debug(" =============================== Runtime command Script ===============================" );
log.debug(String.join(" ", command));
log.debug(" =============================== Runtime command Script =============================== " );
process = Runtime.getRuntime().exec(command);
try (ByteArrayOutputStream resultOutStream = new ByteArrayOutputStream();
InputStream processInStream = new BufferedInputStream(process.getInputStream())) {
new Thread(new InputStreamRunnable(process.getErrorStream(), "ErrorStream")).start();
int num;
byte[] bs = new byte[1024];
while ((num = processInStream.read(bs)) != -1) {
resultOutStream.write(bs, 0, num);
String stepMsg = new String(bs);
// log.debug("命令行日志:" + stepMsg);
if (stepMsg.contains("input any key to continue...")) {
process.destroy();
}
}
String result = resultOutStream.toString();
log.debug("执行命令完成:" + result);
return result;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
} catch (IOException e) {
log.error(e.getMessage(), e);
throw e;
} finally {
if (process != null) {
process.destroy();
}
}
}
/**
* exec 控制台输出获取线程类
* 使用单独的线程获取控制台输出,防止输入流阻塞
*
* @author chenrui
* @date 2024/4/09 10:30
*/
static class InputStreamRunnable implements Runnable {
BufferedReader bReader = null;
String type = null;
public InputStreamRunnable(InputStream is, String _type) {
try {
bReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(is), StandardCharsets.UTF_8));
type = _type;
} catch (Exception ex) {
}
}
@SuppressWarnings("unused")
public void run() {
String line;
int lineNum = 0;
try {
while ((line = bReader.readLine()) != null) {
lineNum++;
// Thread.sleep(200);
}
bReader.close();
} catch (Exception ignored) {
}
}
}
}
@@ -0,0 +1,617 @@
package org.jeecg.modules.airag.llm.handler;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.Metadata;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiTokenizer;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.rag.query.router.DefaultQueryRouter;
import dev.langchain4j.rag.query.router.QueryRouter;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.tika.parser.AutoDetectParser;
import org.jeecg.ai.factory.AiModelFactory;
import org.jeecg.ai.factory.AiModelOptions;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.*;
import org.jeecg.modules.airag.common.handler.IEmbeddingHandler;
import org.jeecg.modules.airag.common.vo.knowledge.KnowledgeSearchResult;
import org.jeecg.modules.airag.llm.config.EmbedStoreConfigBean;
import org.jeecg.modules.airag.llm.config.KnowConfigBean;
import org.jeecg.modules.airag.llm.consts.LLMConsts;
import org.jeecg.modules.airag.llm.document.TikaDocumentParser;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
import org.jeecg.modules.airag.llm.entity.AiragKnowledgeDoc;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import org.jeecg.modules.airag.llm.mapper.AiragKnowledgeMapper;
import org.jeecg.modules.airag.llm.mapper.AiragModelMapper;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeService;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
import static org.jeecg.modules.airag.llm.consts.LLMConsts.KNOWLEDGE_DOC_TYPE_FILE;
import static org.jeecg.modules.airag.llm.consts.LLMConsts.KNOWLEDGE_DOC_TYPE_WEB;
/**
* 向量工具类
*
* @Author: chenrui
* @Date: 2025/2/18 14:31
*/
@Slf4j
@Component
public class EmbeddingHandler implements IEmbeddingHandler {
@Autowired
EmbedStoreConfigBean embedStoreConfigBean;
@Autowired
private AiragModelMapper airagModelMapper;
@Autowired
@Lazy
private IAiragKnowledgeService airagKnowledgeService;
@Autowired
private AiragKnowledgeMapper airagKnowledgeMapper;
@Value(value = "${jeecg.path.upload:}")
private String uploadpath;
@Autowired
KnowConfigBean knowConfigBean;
/**
* 默认分段长度
*/
private static final int DEFAULT_SEGMENT_SIZE = 1000;
/**
* 默认分段重叠长度
*/
private static final int DEFAULT_OVERLAP_SIZE = 50;
/**
* 向量存储元数据:knowledgeId
*/
public static final String EMBED_STORE_METADATA_KNOWLEDGEID = "knowledgeId";
/**
* 向量存储元数据:docId
*/
public static final String EMBED_STORE_METADATA_DOCID = "docId";
/**
* 向量存储元数据:docName
*/
public static final String EMBED_STORE_METADATA_DOCNAME = "docName";
/**
* 向量存储缓存
*/
private static final ConcurrentHashMap<String, EmbeddingStore<TextSegment>> EMBED_STORE_CACHE = new ConcurrentHashMap<>();
/**
* 正则匹配: md图片
* "!\\[(.*?)]\\((.*?)(\\s*=\\d+)?\\)"
*/
private static final Pattern PATTERN_MD_IMAGE = Pattern.compile("!\\[(.*?)]\\((.*?)\\)");
/**
* 向量化文档
*
* @param knowId
* @param doc
* @return
* @author chenrui
* @date 2025/2/18 11:52
*/
public Map<String, Object> embeddingDocument(String knowId, AiragKnowledgeDoc doc) {
AiragKnowledge airagKnowledge = airagKnowledgeService.getById(knowId);
AssertUtils.assertNotEmpty("知识库不存在", airagKnowledge);
AssertUtils.assertNotEmpty("请先为知识库配置向量模型库", airagKnowledge.getEmbedId());
AssertUtils.assertNotEmpty("文档不能为空", doc);
// 读取文档
String content = doc.getContent();
// 向量化并存储
if (oConvertUtils.isEmpty(content)) {
switch (doc.getType()) {
case KNOWLEDGE_DOC_TYPE_FILE:
//解析文件
if (knowConfigBean.isEnableMinerU()) {
parseFileByMinerU(doc);
}
content = parseFile(doc);
break;
case KNOWLEDGE_DOC_TYPE_WEB:
// TODO author: chenrui for:读取网站内容 date:2025/2/18
break;
}
}
//update-begin---author:chenrui ---date:20250307 for[QQYUN-11443]【AI】是不是应该把标题也生成到向量库里,标题一般是有意义的------------
if (oConvertUtils.isNotEmpty(doc.getTitle())) {
content = doc.getTitle() + "\n\n" + content;
}
//update-end---author:chenrui ---date:20250307 for[QQYUN-11443]【AI】是不是应该把标题也生成到向量库里,标题一般是有意义的------------
// 向量化 date:2025/2/18
AiragModel model = getEmbedModelData(airagKnowledge.getEmbedId());
AiModelOptions modelOp = buildModelOptions(model);
EmbeddingModel embeddingModel = AiModelFactory.createEmbeddingModel(modelOp);
EmbeddingStore<TextSegment> embeddingStore = getEmbedStore(model);
// 删除旧数据
embeddingStore.removeAll(metadataKey(EMBED_STORE_METADATA_DOCID).isEqualTo(doc.getId()));
// 分段器
DocumentSplitter splitter = DocumentSplitters.recursive(DEFAULT_SEGMENT_SIZE, DEFAULT_OVERLAP_SIZE, new OpenAiTokenizer());
// 分段并存储
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentSplitter(splitter)
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
Metadata metadata = Metadata.metadata(EMBED_STORE_METADATA_DOCID, doc.getId())
.put(EMBED_STORE_METADATA_KNOWLEDGEID, doc.getKnowledgeId())
.put(EMBED_STORE_METADATA_DOCNAME, FilenameUtils.getName(doc.getTitle()));
Document from = Document.from(content, metadata);
ingestor.ingest(from);
return metadata.toMap();
}
/**
* 向量查询(多知识库)
*
* @param knowIds
* @param queryText
* @param topNumber
* @param similarity
* @return
* @author chenrui
* @date 2025/2/18 16:52
*/
@Override
public KnowledgeSearchResult embeddingSearch(List<String> knowIds, String queryText, Integer topNumber, Double similarity) {
AssertUtils.assertNotEmpty("请选择知识库", knowIds);
AssertUtils.assertNotEmpty("请填写查询内容", queryText);
topNumber = oConvertUtils.getInteger(topNumber, 5);
//命中的文档列表
List<Map<String, Object>> documents = new ArrayList<>(16);
for (String knowId : knowIds) {
List<Map<String, Object>> searchResp = searchEmbedding(knowId, queryText, topNumber, similarity);
if (oConvertUtils.isObjectNotEmpty(searchResp)) {
documents.addAll(searchResp);
}
}
//命中的文档内容
StringBuilder data = new StringBuilder();
// 对documents按score降序排序并取前topNumber个
List<Map<String, Object>> sortedDocuments = documents.stream()
.sorted(Comparator.comparingDouble((Map<String, Object> doc) -> (Double) doc.get("score")).reversed())
.limit(topNumber)
.peek(doc -> data.append(doc.get("content")).append("\n"))
.collect(Collectors.toList());
return new KnowledgeSearchResult(data.toString(), sortedDocuments);
}
/**
* 向量查询
*
* @param knowId
* @param queryText
* @param topNumber
* @param similarity
* @return
* @author chenrui
* @date 2025/2/18 16:52
*/
public List<Map<String, Object>> searchEmbedding(String knowId, String queryText, Integer topNumber, Double similarity) {
AssertUtils.assertNotEmpty("请选择知识库", knowId);
AiragKnowledge knowledge = airagKnowledgeMapper.getByIdIgnoreTenant(knowId);
AssertUtils.assertNotEmpty("知识库不存在", knowledge);
AssertUtils.assertNotEmpty("请填写查询内容", queryText);
AiragModel model = getEmbedModelData(knowledge.getEmbedId());
AiModelOptions modelOp = buildModelOptions(model);
EmbeddingModel embeddingModel = AiModelFactory.createEmbeddingModel(modelOp);
Embedding queryEmbedding = embeddingModel.embed(queryText).content();
topNumber = oConvertUtils.getInteger(topNumber, modelOp.getTopNumber());
similarity = oConvertUtils.getDou(similarity, modelOp.getSimilarity());
EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(topNumber)
.minScore(similarity)
.filter(metadataKey(EMBED_STORE_METADATA_KNOWLEDGEID).isEqualTo(knowId))
.build();
EmbeddingStore<TextSegment> embeddingStore = getEmbedStore(model);
List<EmbeddingMatch<TextSegment>> relevant = embeddingStore.search(embeddingSearchRequest).matches();
List<Map<String, Object>> result = new ArrayList<>();
if (oConvertUtils.isObjectNotEmpty(relevant)) {
result = relevant.stream().map(matchRes -> {
Map<String, Object> data = new HashMap<>();
data.put("score", matchRes.score());
data.put("content", matchRes.embedded().text());
Metadata metadata = matchRes.embedded().metadata();
data.put("chunk", metadata.getInteger("index"));
data.put(EMBED_STORE_METADATA_DOCNAME, metadata.getString(EMBED_STORE_METADATA_DOCNAME));
return data;
}).collect(Collectors.toList());
}
return result;
}
/**
* 获取向量查询路由
*
* @param knowIds
* @param topNumber
* @param similarity
* @return
* @author chenrui
* @date 2025/2/20 21:03
*/
@Override
public QueryRouter getQueryRouter(List<String> knowIds, Integer topNumber, Double similarity) {
AssertUtils.assertNotEmpty("请选择知识库", knowIds);
List<ContentRetriever> retrievers = Lists.newArrayList();
for (String knowId : knowIds) {
if (oConvertUtils.isEmpty(knowId)) {
continue;
}
AiragKnowledge knowledge = airagKnowledgeMapper.getByIdIgnoreTenant(knowId);
AssertUtils.assertNotEmpty("知识库不存在", knowledge);
AiragModel model = getEmbedModelData(knowledge.getEmbedId());
AiModelOptions modelOptions = buildModelOptions(model);
EmbeddingModel embeddingModel = AiModelFactory.createEmbeddingModel(modelOptions);
EmbeddingStore<TextSegment> embeddingStore = getEmbedStore(model);
topNumber = oConvertUtils.getInteger(topNumber, 5);
similarity = oConvertUtils.getDou(similarity, 0.75);
// 构建一个嵌入存储内容检索器,用于从嵌入存储中检索内容
EmbeddingStoreContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(topNumber)
.minScore(similarity)
.filter(metadataKey(EMBED_STORE_METADATA_KNOWLEDGEID).isEqualTo(knowId))
.build();
retrievers.add(contentRetriever);
}
if (retrievers.isEmpty()) {
return null;
} else {
return new DefaultQueryRouter(retrievers);
}
}
/**
* 删除向量化文档
*
* @param knowId
* @param modelId
* @author chenrui
* @date 2025/2/18 19:07
*/
public void deleteEmbedDocsByKnowId(String knowId, String modelId) {
AssertUtils.assertNotEmpty("选择知识库", knowId);
AiragModel model = getEmbedModelData(modelId);
EmbeddingStore<TextSegment> embeddingStore = getEmbedStore(model);
// 删除数据
embeddingStore.removeAll(metadataKey(EMBED_STORE_METADATA_KNOWLEDGEID).isEqualTo(knowId));
}
/**
* 删除向量化文档
*
* @param docIds
* @param modelId
* @author chenrui
* @date 2025/2/18 19:07
*/
public void deleteEmbedDocsByDocIds(List<String> docIds, String modelId) {
AssertUtils.assertNotEmpty("选择文档", docIds);
AiragModel model = getEmbedModelData(modelId);
EmbeddingStore<TextSegment> embeddingStore = getEmbedStore(model);
// 删除数据
embeddingStore.removeAll(metadataKey(EMBED_STORE_METADATA_DOCID).isIn(docIds));
}
/**
* 查询向量模型数据
*
* @param modelId
* @return
* @author chenrui
* @date 2025/2/20 20:08
*/
private AiragModel getEmbedModelData(String modelId) {
AssertUtils.assertNotEmpty("向量模型不能为空", modelId);
AiragModel model = airagModelMapper.getByIdIgnoreTenant(modelId);
AssertUtils.assertNotEmpty("向量模型不存在", model);
AssertUtils.assertEquals("仅支持向量模型", LLMConsts.MODEL_TYPE_EMBED, model.getModelType());
return model;
}
/**
* 获取向量存储
*
* @param model
* @return
* @author chenrui
* @date 2025/2/18 14:56
*/
private EmbeddingStore<TextSegment> getEmbedStore(AiragModel model) {
AssertUtils.assertNotEmpty("未配置模型", model);
String modelId = model.getId();
String connectionInfo = embedStoreConfigBean.getHost() + embedStoreConfigBean.getPort() + embedStoreConfigBean.getDatabase();
String key = modelId + connectionInfo;
if (EMBED_STORE_CACHE.containsKey(key)) {
return EMBED_STORE_CACHE.get(key);
}
AiModelOptions modelOp = buildModelOptions(model);
EmbeddingModel embeddingModel = AiModelFactory.createEmbeddingModel(modelOp);
String tableName = embedStoreConfigBean.getTable();
// update-begin---author:sunjianlei ---date:20250509 for:【QQYUN-12345】向量模型维度不一致问题
// 如果该模型不是默认的向量维度
int dimension = embeddingModel.dimension();
if (!LLMConsts.EMBED_MODEL_DEFAULT_DIMENSION.equals(dimension)) {
// 就加上维度后缀,防止因维度不一致导致保存失败
tableName += ("_" + dimension);
}
// update-end-----author:sunjianlei ---date:20250509 for:【QQYUN-12345】向量模型维度不一致问题
EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
// Connection and table parameters
.host(embedStoreConfigBean.getHost())
.port(embedStoreConfigBean.getPort())
.database(embedStoreConfigBean.getDatabase())
.user(embedStoreConfigBean.getUser())
.password(embedStoreConfigBean.getPassword())
.table(tableName)
// Embedding dimension
// Required: Must match the embedding models output dimension
.dimension(embeddingModel.dimension())
// Indexing and performance options
// Enable IVFFlat index
.useIndex(true)
// Number of lists
// for IVFFlat index
.indexListSize(100)
// Table creation options
// Automatically create the table if it doesnt exist
.createTable(true)
//Dont drop the table first (set to true if you want a fresh start)
.dropTableFirst(false)
.build();
EMBED_STORE_CACHE.put(key, embeddingStore);
return embeddingStore;
}
/**
* 构造ModelOptions
*
* @param model
* @return
* @author chenrui
* @date 2025/3/11 17:45
*/
public static AiModelOptions buildModelOptions(AiragModel model) {
AiModelOptions.AiModelOptionsBuilder modelOpBuilder = AiModelOptions.builder()
.provider(model.getProvider())
.modelName(model.getModelName())
.baseUrl(model.getBaseUrl());
if (oConvertUtils.isObjectNotEmpty(model.getCredential())) {
JSONObject modelCredential = JSONObject.parseObject(model.getCredential());
modelOpBuilder.apiKey(oConvertUtils.getString(modelCredential.getString("apiKey"), null));
modelOpBuilder.secretKey(oConvertUtils.getString(modelCredential.getString("secretKey"), null));
}
modelOpBuilder.topNumber(5);
modelOpBuilder.similarity(0.75);
return modelOpBuilder.build();
}
/**
* 解析文件
*
* @param doc
* @author chenrui
* @date 2025/3/5 11:31
*/
private String parseFile(AiragKnowledgeDoc doc) {
String metadata = doc.getMetadata();
AssertUtils.assertNotEmpty("请先上传文件", metadata);
JSONObject metadataJson = JSONObject.parseObject(metadata);
if (!metadataJson.containsKey(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH)) {
throw new JeecgBootException("请先上传文件");
}
String filePath = metadataJson.getString(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH);
AssertUtils.assertNotEmpty("请先上传文件", filePath);
// 网络资源,先下载到临时目录
filePath = ensureFile(filePath);
// 提取文档内容
File docFile = new File(filePath);
if (docFile.exists()) {
Document document = new TikaDocumentParser(AutoDetectParser::new, null, null, null).parse(docFile);
if (null != document) {
String content = document.text();
// 判断是否md文档
String fileType = FilenameUtils.getExtension(docFile.getName());
if ("md".contains(fileType)) {
// 如果是md文件,查找所有图片语法,如果是本地图片,替换成网络图片
String baseUrl = "#{domainURL}/sys/common/static/";
String sourcePath = metadataJson.getString(LLMConsts.KNOWLEDGE_DOC_METADATA_SOURCES_PATH);
if(oConvertUtils.isNotEmpty(sourcePath)) {
String escapedPath = uploadpath;
//update-begin---author:wangshuai---date:2025-06-03---for:【QQYUN-12636】【AI知识库】文档库上传 本地local 文档中的图片不展示---
/*if (File.separator.equals("\\")){
escapedPath = uploadpath.replace("//", "\\\\");
}*/
//update-end---author:wangshuai---date:2025-06-03---for:【QQYUN-12636】【AI知识库】文档库上传 本地local 文档中的图片不展示---
sourcePath = sourcePath.replaceFirst("^" + escapedPath, "").replace("\\", "/");
String docFilePath = metadataJson.getString(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH);
docFilePath = FilenameUtils.getPath(docFilePath);
docFilePath = docFilePath.replace("\\", "/");
StringBuffer sb = replaceImageUrl(content, baseUrl + sourcePath + "/", baseUrl + docFilePath);
content = sb.toString();
}
}
return content;
}
}
return null;
}
@NotNull
private static StringBuffer replaceImageUrl(String content, String abstractBaseUrl, String relativeBaseUrl) {
// 正则表达式匹配md文件中的图片语法 ![alt text](image url)
Matcher matcher = PATTERN_MD_IMAGE.matcher(content);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String imageUrl = matcher.group(2);
// 检查是否是本地图片路径
if (!imageUrl.startsWith("http")) {
// 替换成网络图片路径
String networkImageUrl = abstractBaseUrl + imageUrl;
if(imageUrl.startsWith("/")) {
// 绝对路径
networkImageUrl = abstractBaseUrl + imageUrl;
}else{
// 相对路径
networkImageUrl = relativeBaseUrl + imageUrl;
}
// 修改图片路径中//->/,但保留http://和https://
networkImageUrl = networkImageUrl.replaceAll("(?<!http:)(?<!https:)//", "/");
matcher.appendReplacement(sb, "![" + matcher.group(1) + "](" + networkImageUrl + ")");
} else {
matcher.appendReplacement(sb, "![" + matcher.group(1) + "](" + imageUrl + ")");
}
}
matcher.appendTail(sb);
return sb;
}
/**
* 通过MinerU解析文件
*
* @param doc
* @author chenrui
* @date 2025/4/1 17:37
*/
private void parseFileByMinerU(AiragKnowledgeDoc doc) {
String metadata = doc.getMetadata();
AssertUtils.assertNotEmpty("请先上传文件", metadata);
JSONObject metadataJson = JSONObject.parseObject(metadata);
if (!metadataJson.containsKey(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH)) {
throw new JeecgBootException("请先上传文件");
}
String filePath = metadataJson.getString(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH);
AssertUtils.assertNotEmpty("请先上传文件", filePath);
filePath = ensureFile(filePath);
File docFile = new File(filePath);
String fileType = FilenameUtils.getExtension(filePath);
if (!docFile.exists()
|| "txt".equalsIgnoreCase(fileType)
|| "md".equalsIgnoreCase(fileType)) {
return ;
}
String command = "magic-pdf";
if (oConvertUtils.isNotEmpty(knowConfigBean.getCondaEnv())) {
command = "conda run -n " + knowConfigBean.getCondaEnv() + " " + command;
}
String outputPath = docFile.getParentFile().getAbsolutePath();
String[] args = {
"-p", docFile.getAbsolutePath(),
"-o", outputPath,
};
try {
String execLog = CommandExecUtil.execCommand(command, args);
log.info("执行命令行:" + command + " args:" + Arrays.toString(args) + "\n log::" + execLog);
// 如果成功,替换文件路径和静态资源路径
String fileBaseName = FilenameUtils.getBaseName(docFile.getName());
String newFileDir = outputPath + File.separator + fileBaseName + File.separator + "auto" + File.separator ;
// 先检查文件是否存在,存在才替换
File convertedFile = new File(newFileDir + fileBaseName + ".md");
if (convertedFile.exists()) {
log.info("文件转换成md成功,替换文件路径和静态资源路径");
newFileDir = newFileDir.replaceFirst("^" + uploadpath, "");
metadataJson.put(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH, newFileDir + fileBaseName + ".md");
metadataJson.put(LLMConsts.KNOWLEDGE_DOC_METADATA_SOURCES_PATH, newFileDir);
doc.setMetadata(metadataJson.toJSONString());
}
} catch (IOException e) {
log.error("文件转换md失败,使用传统提取方案{}", e.getMessage(), e);
}
}
/**
* 确保文件存在
* @param filePath
* @return
* @author chenrui
* @date 2025/4/1 17:36
*/
@NotNull
private String ensureFile(String filePath) {
// 网络资源,先下载到临时目录
Matcher matcher = LLMConsts.WEB_PATTERN.matcher(filePath);
if (matcher.matches()) {
log.info("网络资源,下载到临时目录:" + filePath);
// 准备文件
String tempFilePath = uploadpath + File.separator + "tmp" + File.separator + UUIDGenerator.generate() + File.separator;
String fileName = filePath;
if (fileName.contains("?")) {
fileName = fileName.substring(0, fileName.indexOf("?"));
}
fileName = FilenameUtils.getName(fileName);
tempFilePath = tempFilePath + fileName;
FileDownloadUtils.download2DiskFromNet(filePath, tempFilePath);
filePath = tempFilePath;
} else {
//本地文件
filePath = uploadpath + File.separator + filePath;
}
return filePath;
}
}
@@ -0,0 +1,23 @@
package org.jeecg.modules.airag.llm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.airag.llm.entity.AiragKnowledgeDoc;
/**
* @Description: airag知识库文档
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
public interface AiragKnowledgeDocMapper extends BaseMapper<AiragKnowledgeDoc> {
/**
* 通过主表id删除子表数据
*
* @param mainId 主表id
* @return boolean
*/
public boolean deleteByMainId(@Param("mainId") String mainId);
}
@@ -0,0 +1,25 @@
package org.jeecg.modules.airag.llm.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
/**
* @Description: AIRag知识库
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
public interface AiragKnowledgeMapper extends BaseMapper<AiragKnowledge> {
/**
* 根据ID查询知识库信息(忽略租户)
* for [QQYUN-12113]分享之后的聊天,应用、模型、知识库不根据租户查询
* @param id
* @return
* @author chenrui
* @date 2025/4/21 15:24
*/
@InterceptorIgnore(tenantLine = "true")
AiragKnowledge getByIdIgnoreTenant(String id);
}
@@ -0,0 +1,25 @@
package org.jeecg.modules.airag.llm.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.airag.llm.entity.AiragModel;
/**
* @Description: AiRag模型配置
* @Author: jeecg-boot
* @Date: 2025-02-14
* @Version: V1.0
*/
public interface AiragModelMapper extends BaseMapper<AiragModel> {
/**
* 根据ID查询模型信息(忽略租户)
* for [QQYUN-12113]分享之后的聊天,应用、模型、知识库不根据租户查询
* @param id
* @return
* @author chenrui
* @date 2025/4/21 15:24
*/
@InterceptorIgnore(tenantLine = "true")
AiragModel getByIdIgnoreTenant(String id);
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.airag.llm.mapper.AiragKnowledgeDocMapper">
<delete id="deleteByMainId" parameterType="java.lang.String">
DELETE
FROM airag_knowledge_doc
WHERE knowledge_id = #{mainId}
</delete>
</mapper>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.airag.llm.mapper.AiragKnowledgeMapper">
<select id="getByIdIgnoreTenant" resultType="org.jeecg.modules.airag.llm.entity.AiragKnowledge">
SELECT * FROM airag_knowledge WHERE id = #{id}
</select>
</mapper>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.airag.llm.mapper.AiragModelMapper">
<select id="getByIdIgnoreTenant" resultType="org.jeecg.modules.airag.llm.entity.AiragModel">
SELECT * FROM airag_model WHERE id = #{id}
</select>
</mapper>
@@ -0,0 +1,87 @@
package org.jeecg.modules.airag.llm.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.airag.llm.entity.AiragKnowledgeDoc;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* airag知识库文档
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
public interface IAiragKnowledgeDocService extends IService<AiragKnowledgeDoc> {
/**
* 重建文档
*
* @param docIds
* @return
* @author chenrui
* @date 2025/2/18 11:14
*/
Result<?> rebuildDocument(String docIds);
/**
* 添加文档
*
* @param airagKnowledgeDoc
* @return
* @author chenrui
* @date 2025/2/18 15:30
*/
Result<?> editDocument(AiragKnowledgeDoc airagKnowledgeDoc);
/**
* 通过知识库id重建文档
*
* @param knowId
* @return
* @author chenrui
* @date 2025/2/18 18:54
*/
Result<?> rebuildDocumentByKnowId(String knowId);
/**
* 通过知识库id删除文档
*
* @param knowIds
* @return
* @author chenrui
* @date 2025/2/18 18:59
*/
Result<?> removeByKnowIds(List<String> knowIds);
/**
* 通过文档id批量删除文档
*
* @param docIds
* @return
* @author chenrui
* @date 2025/2/18 19:16
*/
Result<?> removeDocByIds(List<String> docIds);
/**
* 通过知识库id删除所以文档
*
* @param knowId
* @return
*/
Result<?> deleteAllByKnowId(String knowId);
/**
* 从zip包导入文档
* @param knowId
* @param file
* @return
* @author chenrui
* @date 2025/3/20 13:50
*/
Result<?> importDocumentFromZip(String knowId, MultipartFile file);
}
@@ -0,0 +1,14 @@
package org.jeecg.modules.airag.llm.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
/**
* AIRag知识库
*
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
public interface IAiragKnowledgeService extends IService<AiragKnowledge> {
}
@@ -0,0 +1,17 @@
package org.jeecg.modules.airag.llm.service;
import com.baomidou.mybatisplus.extension.service.IService;
import dev.langchain4j.data.message.ChatMessage;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import java.util.List;
/**
* @Description: AiRag模型配置
* @Author: jeecg-boot
* @Date: 2025-02-14
* @Version: V1.0
*/
public interface IAiragModelService extends IService<AiragModel> {
}
@@ -0,0 +1,470 @@
package org.jeecg.modules.airag.llm.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.io.FilenameUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.config.mqtoken.UserTokenContext;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.*;
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
import org.jeecg.modules.airag.llm.consts.LLMConsts;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
import org.jeecg.modules.airag.llm.entity.AiragKnowledgeDoc;
import org.jeecg.modules.airag.llm.handler.EmbeddingHandler;
import org.jeecg.modules.airag.llm.mapper.AiragKnowledgeDocMapper;
import org.jeecg.modules.airag.llm.mapper.AiragKnowledgeMapper;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeDocService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.jeecg.modules.airag.llm.consts.LLMConsts.*;
/**
* @Description: airag知识库文档
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
@Slf4j
@Service
public class AiragKnowledgeDocServiceImpl extends ServiceImpl<AiragKnowledgeDocMapper, AiragKnowledgeDoc> implements IAiragKnowledgeDocService {
@Autowired
private AiragKnowledgeDocMapper airagKnowledgeDocMapper;
@Autowired
private AiragKnowledgeMapper airagKnowledgeMapper;
@Autowired
EmbeddingHandler embeddingHandler;
@Value(value = "${jeecg.path.upload:}")
private String uploadpath;
/**
* 支持的文档类型
*/
private static final List<String> SUPPORT_DOC_TYPE = Arrays.asList("txt", "pdf", "docx", "doc", "pptx", "ppt", "xlsx", "xls", "md");
/**
* 向量化线程池大小
*/
private static final int THREAD_POOL_SIZE = 10;
/**
* 向量化文档线程池
*/
private static final ExecutorService buildDocExecutorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
// 解压文件:单个文件最大150MB
private static final long MAX_FILE_SIZE = 150 * 1024 * 1024;
// 解压文件:总解压大小1024MB
private static final long MAX_TOTAL_SIZE = 1024 * 1024 * 1024;
// 解压文件:最多解压10000个Entry
private static final int MAX_ENTRY_COUNT = 10000;
@Transactional(rollbackFor = {Exception.class})
@Override
public Result<?> editDocument(AiragKnowledgeDoc airagKnowledgeDoc) {
AssertUtils.assertNotEmpty("文档不能未空", airagKnowledgeDoc);
AssertUtils.assertNotEmpty("知识库不能未空", airagKnowledgeDoc.getKnowledgeId());
AssertUtils.assertNotEmpty("文档标题不能未空", airagKnowledgeDoc.getTitle());
AssertUtils.assertNotEmpty("文档类型不能未空", airagKnowledgeDoc.getType());
if (KNOWLEDGE_DOC_TYPE_TEXT.equals(airagKnowledgeDoc.getType())) {
AssertUtils.assertNotEmpty("文档内容不能为空", airagKnowledgeDoc.getContent());
}
airagKnowledgeDoc.setStatus(KNOWLEDGE_DOC_STATUS_DRAFT);
// 保存到数据库
if (this.saveOrUpdate(airagKnowledgeDoc)) {
// 重建向量
return this.rebuildDocument(airagKnowledgeDoc.getId());
} else {
return Result.error("保存失败");
}
}
@Override
public Result<?> rebuildDocumentByKnowId(String knowId) {
AssertUtils.assertNotEmpty("知识库id不能为空", knowId);
List<AiragKnowledgeDoc> docList = airagKnowledgeDocMapper.selectList(Wrappers.lambdaQuery(AiragKnowledgeDoc.class).eq(AiragKnowledgeDoc::getKnowledgeId, knowId));
if (oConvertUtils.isObjectEmpty(docList)) {
return Result.OK();
}
String docIds = docList.stream().map(AiragKnowledgeDoc::getId).collect(Collectors.joining(","));
return rebuildDocument(docIds);
}
@Transactional(rollbackFor = {java.lang.Exception.class})
@Override
public Result<?> rebuildDocument(String docIds) {
AssertUtils.assertNotEmpty("请选择要重建的文档", docIds);
List<String> docIdList = Arrays.asList(docIds.split(","));
// 查询数据
List<AiragKnowledgeDoc> docList = airagKnowledgeDocMapper.selectBatchIds(docIdList);
AssertUtils.assertNotEmpty("文档不存在", docList);
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
// 检查状态
List<AiragKnowledgeDoc> knowledgeDocs = docList.stream()
.filter(doc -> {
//update-begin---author:chenrui ---date:20250410 for[QQYUN-11943]【ai】ai知识库 上传完文档 一直显示构建中?------------
if(KNOWLEDGE_DOC_STATUS_BUILDING.equalsIgnoreCase(doc.getStatus())){
Date updateTime = doc.getUpdateTime();
if (updateTime != null) {
// 向量化超过了5分钟,重新向量化
long timeDifference = System.currentTimeMillis() - updateTime.getTime();
return timeDifference > 5 * 60 * 1000;
}else{
return true;
}
} else {
return true;
}
//update-end---author:chenrui ---date:20250410 for[QQYUN-11943]【ai】ai知识库 上传完文档 一直显示构建中?------------
})
.peek(doc -> {
doc.setStatus(KNOWLEDGE_DOC_STATUS_BUILDING);
})
.collect(Collectors.toList());
if (oConvertUtils.isObjectEmpty(knowledgeDocs)) {
return Result.ok("操作成功");
}
if (oConvertUtils.isObjectEmpty(knowledgeDocs)) {
return Result.ok("操作成功");
}
// 更新状态
this.updateBatchById(knowledgeDocs);
// 异步重建文档
String tenantId = TenantContext.getTenant();
String token = TokenUtils.getTokenByRequest();
knowledgeDocs.forEach((doc) -> {
CompletableFuture.runAsync(() -> {
UserTokenContext.setToken(token);
TenantContext.setTenant(tenantId);
String knowId = doc.getKnowledgeId();
log.info("开始重建文档, 知识库id: {}, 文档id: {}", knowId, doc.getId());
doc.setStatus(KNOWLEDGE_DOC_STATUS_BUILDING);
this.updateById(doc);
//update-begin---author:chenrui ---date:20250410 for[QQYUN-11943]【ai】ai知识库 上传完文档 一直显示构建中?------------
try {
Map<String, Object> metadata = embeddingHandler.embeddingDocument(knowId, doc);
// 更新数据 date:2025/2/18
if (null != metadata) {
doc.setStatus(KNOWLEDGE_DOC_STATUS_COMPLETE);
this.updateById(doc);
log.info("重建文档成功, 知识库id: {}, 文档id: {}", knowId, doc.getId());
} else {
this.handleDocBuildFailed(doc, "向量化失败");
log.info("重建文档失败, 知识库id: {}, 文档id: {}", knowId, doc.getId());
}
}catch (Throwable t){
this.handleDocBuildFailed(doc, t.getMessage());
log.error("重建文档失败:" + t.getMessage() + ", 知识库id: " + knowId + ", 文档id: " + doc.getId(), t);
}
//update-end---author:chenrui ---date:20250410 for[QQYUN-11943]【ai】ai知识库 上传完文档 一直显示构建中?------------
}, buildDocExecutorService);
});
log.info("返回操作成功");
return Result.ok("操作成功");
}
/**
* 处理文档构建失败
*/
private void handleDocBuildFailed(AiragKnowledgeDoc doc, String failedReason) {
doc.setStatus(KNOWLEDGE_DOC_STATUS_FAILED);
String metadataStr = doc.getMetadata();
JSONObject metadata;
if (oConvertUtils.isEmpty(metadataStr)) {
metadata = new JSONObject();
} else {
metadata = JSONObject.parseObject(metadataStr);
}
metadata.put("failedReason", failedReason);
doc.setMetadata(metadata.toJSONString());
this.updateById(doc);
}
@Override
public Result<?> removeByKnowIds(List<String> knowIds) {
AssertUtils.assertNotEmpty("选择知识库", knowIds);
for (String knowId : knowIds) {
AiragKnowledge airagKnowledge = airagKnowledgeMapper.selectById(knowId);
AssertUtils.assertNotEmpty("知识库不存在", airagKnowledge);
AssertUtils.assertNotEmpty("请先为知识库配置向量模型库", airagKnowledge.getEmbedId());
// 异步删除向量数据
final String embedId = airagKnowledge.getEmbedId();
final String finalKnowId = knowId;
CompletableFuture.runAsync(() -> {
try {
embeddingHandler.deleteEmbedDocsByKnowId(finalKnowId, embedId);
} catch (Throwable ignore) {
}
});
// 删除数据
airagKnowledgeDocMapper.deleteByMainId(knowId);
}
return Result.OK();
}
@Override
public Result<?> removeDocByIds(List<String> docIds) {
AssertUtils.assertNotEmpty("请选择要删除的文档", docIds);
// 查询数据
List<AiragKnowledgeDoc> docList = airagKnowledgeDocMapper.selectBatchIds(docIds);
AssertUtils.assertNotEmpty("文档不存在", docList);
// 整理数据
Map<String, List<String>> knowledgeDocs = docList.stream().collect(Collectors.groupingBy(
AiragKnowledgeDoc::getKnowledgeId,
Collectors.mapping(AiragKnowledgeDoc::getId, Collectors.toList())
));
if (oConvertUtils.isObjectEmpty(knowledgeDocs)) {
return Result.ok("success");
}
knowledgeDocs.forEach((knowId, groupedDocIds) -> {
AiragKnowledge airagKnowledge = airagKnowledgeMapper.selectById(knowId);
AssertUtils.assertNotEmpty("知识库不存在", airagKnowledge);
AssertUtils.assertNotEmpty("请先为知识库配置向量模型库", airagKnowledge.getEmbedId());
// 异步删除向量数据
final String embedId = airagKnowledge.getEmbedId();
final List<String> docIdsToDelete = new ArrayList<>(groupedDocIds);
CompletableFuture.runAsync(() -> {
try {
embeddingHandler.deleteEmbedDocsByDocIds(docIdsToDelete, embedId);
} catch (Throwable ignore) {
}
});
// 删除数据
airagKnowledgeDocMapper.deleteBatchIds(groupedDocIds);
});
return Result.ok("success");
}
@Override
public Result<?> deleteAllByKnowId(String knowId) {
if (oConvertUtils.isEmpty(knowId)) {
return Result.error("知识库id不能为空");
}
LambdaQueryWrapper<AiragKnowledgeDoc> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AiragKnowledgeDoc::getKnowledgeId, knowId);
//noinspection unchecked
wrapper.select(AiragKnowledgeDoc::getId);
List<AiragKnowledgeDoc> docList = airagKnowledgeDocMapper.selectList(wrapper);
if (docList.isEmpty()) {
return Result.ok("暂无文档");
}
List<String> docIds = docList.stream().map(AiragKnowledgeDoc::getId).collect(Collectors.toList());
this.removeDocByIds(docIds);
return Result.ok("清空完成");
}
@Transactional(rollbackFor = {java.lang.Exception.class})
@Override
public Result<?> importDocumentFromZip(String knowId, MultipartFile zipFile) {
AssertUtils.assertNotEmpty("请先选择知识库", knowId);
AssertUtils.assertNotEmpty("请上传文件", zipFile);
long startTime = System.currentTimeMillis();
log.info("开始上传知识库文档(zip), 知识库id: {}, 文件名: {}", knowId, zipFile.getOriginalFilename());
try {
String bizPath = knowId + File.separator + UUIDGenerator.generate();
String workDir = uploadpath + File.separator + bizPath + File.separator;
String sourcesPath = workDir + "files";
SsrfFileTypeFilter.checkUploadFileType(zipFile);
// 通过filePath 检查文件是不是压缩包(zip)
String zipFileName = FilenameUtils.getBaseName(zipFile.getOriginalFilename());
String fileExt = FilenameUtils.getExtension(zipFile.getOriginalFilename());
if (null == fileExt || !fileExt.equalsIgnoreCase("zip")) {
throw new JeecgBootException("请上传zip压缩包");
}
String uploadedZipPath = CommonUtils.uploadLocal(zipFile, bizPath, uploadpath);
// 解压缩文件
List<AiragKnowledgeDoc> docList = new ArrayList<>();
AtomicInteger fileCount = new AtomicInteger(0);
unzipFile(uploadpath + File.separator + uploadedZipPath, sourcesPath, uploadedFile -> {
// 仅支持txt、pdf、docx、pptx、html、md文件
String fileName = uploadedFile.getName();
if (!SUPPORT_DOC_TYPE.contains(FilenameUtils.getExtension(fileName).toLowerCase())) {
log.warn("不支持的文件类型: {}", fileName);
return;
}
String baseName = FilenameUtils.getBaseName(fileName);
AiragKnowledgeDoc doc = new AiragKnowledgeDoc();
doc.setKnowledgeId(knowId);
doc.setTitle(baseName);
doc.setType(LLMConsts.KNOWLEDGE_DOC_TYPE_FILE);
doc.setStatus(LLMConsts.KNOWLEDGE_DOC_STATUS_DRAFT);
String relativePath;
if (File.separator.equals("\\")) {
// Windows path handling
String escapedPath = uploadpath.replace("//", "\\\\");
relativePath = uploadedFile.getPath().replaceFirst("^" + escapedPath, "");
} else {
// Unix path handling
relativePath = uploadedFile.getPath().replaceFirst("^" + uploadpath, "");
}
JSONObject metadata = new JSONObject();
metadata.put(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH, relativePath);
metadata.put(LLMConsts.KNOWLEDGE_DOC_METADATA_SOURCES_PATH, sourcesPath);
doc.setMetadata(metadata.toJSONString());
docList.add(doc);
});
AssertUtils.assertNotEmpty("压缩包中没有符合要求的文档", docList);
// 保存数据
this.saveBatch(docList);
// 重建文档
String docIds = docList.stream().map(AiragKnowledgeDoc::getId).filter(oConvertUtils::isObjectNotEmpty).collect(Collectors.joining(","));
rebuildDocument(docIds);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
log.info("上传知识库文档(zip)成功, 知识库id: {}, 文件名: {}, 耗时: {}ms", knowId, zipFile.getOriginalFilename(), (System.currentTimeMillis() - startTime));
return Result.ok("上传成功");
}
/**
* 解压缩文件
*
* @param zipFilePath 压缩文件路径
* @param destDir 目标文件夹
* @param afterExtract 解压完成后回调
* @throws IOException
* @author chenrui
* @date 2025/3/20 14:37
*/
public static void unzipFile(String zipFilePath, String destDir, Consumer<File> afterExtract) throws IOException {
unzipFile(Paths.get(zipFilePath), Paths.get(destDir), afterExtract);
}
/**
* 解压缩文件
*
* @param zipFilePath 压缩文件路径
* @param targetDir 目标文件夹
* @param afterExtract 解压完成后回调
* @throws IOException
* @author chenrui
* @date 2025/4/28 17:02
*/
private static void unzipFile(Path zipFilePath, Path targetDir, Consumer<File> afterExtract) throws IOException {
long totalUnzippedSize = 0;
int entryCount = 0;
if (!Files.exists(targetDir)) {
Files.createDirectories(targetDir);
}
try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) {
Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
while (entries.hasMoreElements()) {
ZipArchiveEntry entry = entries.nextElement();
entryCount++;
if (entryCount > MAX_ENTRY_COUNT) {
throw new IOException("解压文件数量超限,可能是zip bomb攻击");
}
Path newPath = safeResolve(targetDir, entry.getName());
if (entry.isDirectory()) {
Files.createDirectories(newPath);
} else {
Files.createDirectories(newPath.getParent());
try (InputStream is = zipFile.getInputStream(entry);
OutputStream os = Files.newOutputStream(newPath)) {
long bytesCopied = copyLimited(is, os, MAX_FILE_SIZE);
totalUnzippedSize += bytesCopied;
if (totalUnzippedSize > MAX_TOTAL_SIZE) {
throw new IOException("解压总大小超限,可能是zip bomb攻击");
}
}
// 解压完成后回调
if (afterExtract != null) {
afterExtract.accept(newPath.toFile());
}
}
}
}
}
/**
* 安全解析路径,防止Zip Slip攻击
*
* @param targetDir
* @param entryName
* @return
* @throws IOException
* @author chenrui
* @date 2025/4/28 16:46
*/
private static Path safeResolve(Path targetDir, String entryName) throws IOException {
Path resolvedPath = targetDir.resolve(entryName).normalize();
if (!resolvedPath.startsWith(targetDir)) {
throw new IOException("ZIP 路径穿越攻击被阻止:" + entryName);
}
return resolvedPath;
}
/**
* 复制输入流到输出流,并限制最大字节数
*
* @param in
* @param out
* @param maxBytes
* @return
* @throws IOException
* @author chenrui
* @date 2025/4/28 17:03
*/
private static long copyLimited(InputStream in, OutputStream out, long maxBytes) throws IOException {
byte[] buffer = new byte[8192];
long totalCopied = 0;
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
totalCopied += bytesRead;
if (totalCopied > maxBytes) {
throw new IOException("单个文件解压超限,可能是zip bomb攻击");
}
out.write(buffer, 0, bytesRead);
}
return totalCopied;
}
}
@@ -0,0 +1,18 @@
package org.jeecg.modules.airag.llm.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
import org.jeecg.modules.airag.llm.mapper.AiragKnowledgeMapper;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeService;
import org.springframework.stereotype.Service;
/**
* @Description: AIRag知识库
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
@Service
public class AiragKnowledgeServiceImpl extends ServiceImpl<AiragKnowledgeMapper, AiragKnowledge> implements IAiragKnowledgeService {
}
@@ -0,0 +1,19 @@
package org.jeecg.modules.airag.llm.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import org.jeecg.modules.airag.llm.mapper.AiragModelMapper;
import org.jeecg.modules.airag.llm.service.IAiragModelService;
import org.springframework.stereotype.Service;
/**
* @Description: AiRag模型配置
* @Author: jeecg-boot
* @Date: 2025-02-14
* @Version: V1.0
*/
@Service
public class AiragModelServiceImpl extends ServiceImpl<AiragModelMapper, AiragModel> implements IAiragModelService {
}
@@ -0,0 +1,94 @@
package org.jeecg.modules.airag.ocr.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections.CollectionUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.airag.ocr.entity.AiOcr;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/airag/ocr")
public class AiOcrController {
@Autowired
private RedisUtil redisUtil;
private static final String AI_OCR_REDIS_KEY = "airag:ocr";
@GetMapping("/list")
public Result<?> list(){
Object aiOcr = redisUtil.get(AI_OCR_REDIS_KEY);
IPage<AiOcr> page = new Page<>(1,10);
if(null != aiOcr){
List<AiOcr> aiOcrList = JSONObject.parseArray(aiOcr.toString(), AiOcr.class);
page.setRecords(aiOcrList);
page.setTotal(aiOcrList.size());
page.setPages(aiOcrList.size());
}
return Result.OK(page);
}
@PostMapping("/add")
public Result<String> add(@RequestBody AiOcr aiOcr){
Object aiOcrList = redisUtil.get(AI_OCR_REDIS_KEY);
aiOcr.setId(UUID.randomUUID().toString().replace("-",""));
if(null == aiOcrList){
List<AiOcr> list = new ArrayList<>();
list.add(aiOcr);
redisUtil.set(AI_OCR_REDIS_KEY, JSONObject.toJSONString(list));
}else{
List<AiOcr> aiOcrs = JSONObject.parseArray(aiOcrList.toString(), AiOcr.class);
aiOcrs.add(aiOcr);
redisUtil.set(AI_OCR_REDIS_KEY,JSONObject.toJSONString(aiOcrs));
}
return Result.OK("添加成功");
}
@PutMapping("/edit")
public Result<String> updateById(@RequestBody AiOcr aiOcr){
Object aiOcrList = redisUtil.get(AI_OCR_REDIS_KEY);
if(null != aiOcrList){
List<AiOcr> aiOcrs = JSONObject.parseArray(aiOcrList.toString(), AiOcr.class);
aiOcrs.forEach(item->{
if(item.getId().equals(aiOcr.getId())){
BeanUtils.copyProperties(aiOcr,item);
}
});
redisUtil.set(AI_OCR_REDIS_KEY,JSONObject.toJSONString(aiOcrs));
}else{
return Result.OK("编辑失败,未找到该数据");
}
return Result.OK("编辑成功");
}
@DeleteMapping("/deleteById")
public Result<String> deleteById(@RequestBody AiOcr aiOcr){
Object aiOcrObj = redisUtil.get(AI_OCR_REDIS_KEY);
if(null != aiOcrObj){
List<AiOcr> aiOcrs = JSONObject.parseArray(aiOcrObj.toString(), AiOcr.class);
List<AiOcr> aiOcrList = new ArrayList<>();
for(AiOcr ocr: aiOcrs){
if(!ocr.getId().equals(aiOcr.getId())){
aiOcrList.add(ocr);
}
}
if(CollectionUtils.isNotEmpty(aiOcrList)){
redisUtil.set(AI_OCR_REDIS_KEY,JSONObject.toJSONString(aiOcrList));
}else{
redisUtil.removeAll(AI_OCR_REDIS_KEY);
}
}else{
return Result.OK("删除失败,未找到该数据");
}
return Result.OK("删除成功");
}
}
@@ -0,0 +1,29 @@
package org.jeecg.modules.airag.ocr.entity;
import lombok.Data;
/**
* @Description: OCR识别实体类
*
* @author: wangshuai
* @date: 2025/4/16 17:01
*/
@Data
public class AiOcr {
/**
* 编号
*/
private String id;
/**
* 标题
*/
private String title;
/**
* 提示词
*/
private String prompt;
}
@@ -0,0 +1,72 @@
server:
port: 7008
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
autoconfigure:
exclude:
- com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
- org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
datasource:
druid:
stat-view-servlet:
enabled: true
loginUsername: admin
loginPassword: 123456
allow:
web-stat-filter:
enabled: true
dynamic:
druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
# 连接池的配置信息
# 初始化大小,最小,最大
initial-size: 5
min-idle: 5
maxActive: 1000
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
# !!!!!mysql
# filters: stat,slf4j,wall
# !!!!!DM
filters: stat,slf4j
# 允许SELECT语句的WHERE子句是一个永真条件
# wall:
# selectWhereAlwayTrueCheck: false
# 打开mergeSql功能;慢SQL记录
stat:
merge-sql: true
slow-sql-millis: 5000
datasource:
master:
## !!!!!MYSQL
url: jdbc:mysql://localhost:3306/jeecg-boot-dev?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
database: 0
host: 192.168.1.188
port: 6379
password: 'res983'
jeecg:
ai-rag:
embed-store:
host: "localhost"
port: 15432
database: "postgres"
user: "postgres"
password: "123456"
table: "embeddings"
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 -->
<property name="LOG_HOME" value="../logs" />
<!--<property name="COLOR_PATTERN" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta( %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''})- %gray(%msg%xEx%n)" />-->
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/jeecgboot-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
</encoder>
</appender>
<!-- 生成 error html格式日志开始 -->
<appender name="HTML" class="ch.qos.logback.core.FileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!--设置日志级别,过滤掉info日志,只输入error日志-->
<level>ERROR</level>
</filter>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%p%d%msg%M%F{32}%L</pattern>
</layout>
</encoder>
<file>${LOG_HOME}/error-log.html</file>
</appender>
<!-- 生成 error html格式日志结束 -->
<!-- 每天生成一个html格式的日志开始 -->
<appender name="FILE_HTML" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/jeecgboot-%d{yyyy-MM-dd}.%i.html</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
<MaxFileSize>10MB</MaxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%p%d%msg%M%F{32}%L</pattern>
</layout>
</encoder>
</appender>
<!-- 每天生成一个html格式的日志结束 -->
<!--myibatis log configure -->
<logger name="com.apache.ibatis" level="TRACE" />
<logger name="java.sql.Connection" level="DEBUG" />
<logger name="java.sql.Statement" level="DEBUG" />
<logger name="java.sql.PreparedStatement" level="DEBUG" />
<logger name="logging.level.dev.langchain4j" level="DEBUG" />
<logger name="logging.level.dev.ai4j.openai4j" level="DEBUG" />
<!-- 日志输出级别 -->
<root level="info">
<appender-ref ref="STDOUT" />
<!-- <appender-ref ref="FILE" />-->
<!-- <appender-ref ref="HTML" />-->
<!-- <appender-ref ref="FILE_HTML" />-->
</root>
</configuration>
@@ -0,0 +1,82 @@
package org.jeecg.modules.airag.test;
import dev.langchain4j.data.document.Document;
import lombok.extern.slf4j.Slf4j;
import org.apache.tika.parser.AutoDetectParser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.airag.llm.document.TikaDocumentParser;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.wildfly.common.Assert;
import java.io.File;
import java.io.IOException;
/**
* @Description: 文件解析测试
* @Author: chenrui
* @Date: 2025/2/11 16:11
*/
@Slf4j
public class TestFileParse {
@Test
public void testParseTxt() {
readFile("test.txt");
}
@Test
public void testParsePdf() {
readFile("test.pdf");
}
@Test
public void testParseMd() {
readFile("test.md");
}
@Test
public void testParseDoc() {
readFile("test.docx");
}
@Test
public void testParseDoc2003() {
readFile("test.doc");
}
@Test
public void testParseExcel() {
readFile("test.xlsx");
}
@Test
public void testParseExcel2003() {
readFile("test.xls");
}
@Test
public void testParsePPT() {
readFile("test.pptx");
}
@Test
public void testParsePPT2003() {
readFile("test.ppt");
}
private static void readFile(String filePath) {
try {
ClassPathResource resource = new ClassPathResource(filePath);
File file = resource.getFile();
TikaDocumentParser parser = new TikaDocumentParser(AutoDetectParser::new, null, null, null);
Document document = parser.parse(file);
Assert.assertNotNull(document);
System.out.println(filePath + "----" + document.text());
Assert.assertTrue(oConvertUtils.isNotEmpty(document));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,54 @@
package org.jeecg.modules.airag.test;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.junit.jupiter.api.Test;
import java.io.IOException;
/**
* @Description: 流程测试
* @Author: chenrui
* @Date: 2025/2/11 16:11
*/
@Slf4j
public class TestFlows {
@Test
public void testRunFlow(){
String id = "1889499701976358913";
// String id = "1889571074002247682"; //switch
// String id = "1889608218175463425"; //脚本
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3Mzk1NDY0NDIsInVzZXJuYW1lIjoiamVlY2cifQ.CFIV79PUYmOAiqBKT3yjwihHWwf954DvS-4oKERmJVU";
String request = request(id,token);
System.out.println(request);
}
private String request(String id,String token) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://localhost:7008/airag/airagFlow/flow/run/" + id + "?field1=%25E5%2593%2588%25E5%2593%2588&field2=%25E4%25B8%25AD%25E5%259B%25BD")
.get()
.addHeader("X-Access-Token", token)
.addHeader("Accept", "*/*")
.addHeader("Accept-Encoding", "gzip, deflate, br")
.addHeader("User-Agent", "PostmanRuntime-ApipostRuntime/1.1.0")
.addHeader("Connection", "keep-alive")
.addHeader("Cookie", "JSESSIONID=442C48D3D1D0B2878A597AB6EBF2A07E")
.build();
try {
Response response = client.newCall(request).execute();
return response.body().string();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// TODO author: chenrui for:完善用例,使用java方式调用 date:2025/2/14
}
@@ -0,0 +1,225 @@
//package org.jeecg.modules.airag.test;
//
//import dev.langchain4j.agent.tool.ToolParameters;
//import dev.langchain4j.agent.tool.ToolSpecification;
//import dev.langchain4j.data.message.*;
//import dev.langchain4j.model.chat.ChatLanguageModel;
//import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
//import dev.langchain4j.model.openai.OpenAiChatModel;
//import dev.langchain4j.model.output.Response;
//import dev.langchain4j.service.AiServices;
//import dev.langchain4j.service.TokenStream;
//import lombok.extern.slf4j.Slf4j;
//import org.jeecg.ai.assistant.AiChatAssistant;
//import org.jeecg.ai.factory.AiModelFactory;
//import org.jeecg.ai.factory.AiModelOptions;
//import org.jeecg.ai.handler.AIParams;
//import org.jeecg.ai.handler.LLMHandler;
//import org.junit.jupiter.api.Test;
//
//import java.io.IOException;
//import java.io.Serial;
//import java.nio.file.Files;
//import java.nio.file.Paths;
//import java.util.*;
//import java.util.concurrent.ConcurrentHashMap;
//import java.util.concurrent.CountDownLatch;
//
///**
// * @Description: 流程测试
// * @Author: chenrui
// * @Date: 2025/2/11 16:11
// */
//@Slf4j
//public class TestLLM {
//
// @Test
// public void sendByModel() {
// String apiKey = "sk-xxx";
// String baseUrl = "https://api.v3.cm/v1";
// String modelName = "gpt-3.5-turbo";
// double temperature = 0.7;
// ChatLanguageModel llmModel = OpenAiChatModel.builder()
// .apiKey(apiKey)
// .baseUrl(baseUrl)
// .modelName(modelName)
// .temperature(temperature)
// .build();
// String hello = llmModel.generate("hello");
// System.out.println(hello);
// }
//
// @Test
// public void testChat() {
// AiModelOptions options = AiModelOptions.builder()
// .provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
// .apiKey("sk-xxx")
// .baseUrl("https://api.v3.cm/v1")
// .build();
// ChatLanguageModel chatModel = AiModelFactory.createChatModel(options);
// AiServices<AiChatAssistant> chatAssistantBuilder = AiServices.builder(AiChatAssistant.class);
// chatAssistantBuilder.chatLanguageModel(chatModel);
// AiChatAssistant chatAssistant = chatAssistantBuilder.build();
// String str = chatAssistant.chat("hello");
// System.out.println(str);
// }
//
// @Test
// public void sendImg() throws IOException {
// AiModelOptions options = AiModelOptions.builder()
// .provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
// .modelName("gpt-4o-mini")
// .apiKey("sk-xxx")
// .baseUrl("https://api.v3.cm/v1")
// .build();
// ChatLanguageModel chatModel = AiModelFactory.createChatModel(options);
//
// // 读取文件并转换为 base64 编码字符串
// byte[] fileContent = Files.readAllBytes(Paths.get("src/test/resources/test.jpg"));
// String base64Data = Base64.getEncoder().encodeToString(fileContent);
//
// // 获取文件的 MIME 类型
// String mimeType = Files.probeContentType(Paths.get("src/test/resources/test.jpg"));
//
// // 构建 ImageContent 对象
// ImageContent imageContent = new ImageContent(base64Data, mimeType);
//
// UserMessage userMessage = UserMessage.from(
// TextContent.from("你看到了什么"),
// // 构建 ImageContent 对象
// new ImageContent(base64Data, mimeType)
// );
// Response<AiMessage> generate = chatModel.generate(userMessage);
// System.out.println(generate.content());
// }
//
// @Test
// public void testSendImsWithLLMHandler() throws IOException {
// AiModelOptions options = AiModelOptions.builder()
// .provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
// .modelName("gpt-4o-mini")
// .apiKey("sk-xxx")
// .baseUrl("https://api.v3.cm/v1")
// .build();
// ChatLanguageModel chatModel = AiModelFactory.createChatModel(options);
//
// // 读取文件并转换为 base64 编码字符串
// byte[] fileContent = Files.readAllBytes(Paths.get("src/test/resources/test.jpg"));
// String base64Data = Base64.getEncoder().encodeToString(fileContent);
//
// // 获取文件的 MIME 类型
// String mimeType = Files.probeContentType(Paths.get("src/test/resources/test.jpg"));
//
// // 构建 ImageContent 对象
// ImageContent imageContent = new ImageContent(base64Data, mimeType);
//
// UserMessage userMessage = UserMessage.from(
// TextContent.from("你看到了什么"),
// // 构建 ImageContent 对象
// new ImageContent(base64Data, mimeType)
// );
// LLMHandler llmHandler = new LLMHandler();
// AIParams aiParams = new AIParams();
// aiParams.setProvider(AiModelFactory.AIMODEL_TYPE_OPENAI);
// aiParams.setModelName("gpt-4o-mini");
// aiParams.setApiKey("sk-xxx");
// aiParams.setBaseUrl("https://api.v3.cm/v1");
// String completions = llmHandler.completions(Collections.singletonList(userMessage), aiParams);
// System.out.println(completions);
// }
//
// @Test
// public void testChatImsWithLLMHandler() throws IOException, InterruptedException {
// AiModelOptions options = AiModelOptions.builder()
// .provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
// .modelName("gpt-4o-mini")
// .apiKey("sk-xxx")
// .baseUrl("https://api.v3.cm/v1")
// .build();
// ChatLanguageModel chatModel = AiModelFactory.createChatModel(options);
//
// // 读取文件并转换为 base64 编码字符串
// byte[] fileContent = Files.readAllBytes(Paths.get("src/test/resources/test.jpg"));
// String base64Data = Base64.getEncoder().encodeToString(fileContent);
//
// // 获取文件的 MIME 类型
// String mimeType = Files.probeContentType(Paths.get("src/test/resources/test.jpg"));
//
// // 构建 ImageContent 对象
// ImageContent imageContent = new ImageContent(base64Data, mimeType);
// UserMessage userMessage = UserMessage.from(
// TextContent.from("你看到了什么"),
// // 构建 ImageContent 对象
// imageContent,
// ImageContent.from("https://jeecgdev.oss-cn-beijing.aliyuncs.com/temp/logo-qqy_1741658353407.png")
// );
// LLMHandler llmHandler = new LLMHandler();
// AIParams aiParams = new AIParams();
// aiParams.setProvider(AiModelFactory.AIMODEL_TYPE_OPENAI);
// aiParams.setModelName("gpt-4o-mini");
// aiParams.setApiKey("sk-xxx");
// aiParams.setBaseUrl("https://api.v3.cm/v1");
// TokenStream chat = llmHandler.chat(Collections.singletonList(userMessage), aiParams);
// CountDownLatch countDownLatch = new CountDownLatch(1);
// chat.onNext(s -> System.out.println(s))
// .onComplete(s -> {
// System.out.println(s);
// countDownLatch.countDown();
// })
// .onError(e -> {
// System.out.println(e.getMessage());
// countDownLatch.countDown();
// }).start();
// countDownLatch.await();
//
// }
//
//
// @Test
// public void testFuncCalling() {
// String apiKey = "sk-";
// String baseUrl = "https://api.v3.cm/v1";
// String modelName = "gpt-4o-mini";
// double temperature = 0.7;
// ChatLanguageModel llmModel = OpenAiChatModel.builder()
// .apiKey(apiKey)
// .baseUrl(baseUrl)
// .modelName(modelName)
// .temperature(temperature)
// .build();
//
// List<ToolSpecification> toolSpecifications = new ArrayList<>();
// Map<String, Map<String, Object>> properties = new ConcurrentHashMap<>();
//
// properties.put("location", new HashMap<>() {
// @Serial
// private static final long serialVersionUID = -8440944665582258534L;
// {
// put("type", "string");
// put("description", "The city and state, e.g. San Francisco, CA");
// }
// });
// ToolSpecification toolSpecification = ToolSpecification.builder()
// .name("get_current_weather")
// .description("Get the current weather in a given location")
// .parameters(ToolParameters.builder()
// .properties(properties)
// .required(List.of("location"))
// .type("string")
// .build())
// .build();
// toolSpecifications.add(toolSpecification);
//
// List<ChatMessage> messages = new ArrayList<>();
// messages.add(UserMessage.from("How is the weather in Beijing today?"));
//
// Response<AiMessage> resp = llmModel.generate(messages, toolSpecifications);
// System.out.println(resp);
//
// /*Response { content = AiMessage { text = null
// toolExecutionRequests = [ToolExecutionRequest { id = "call_hjPaRh9WAHv6Ib7KpgHpp8Tl", name = "get_current_weather", arguments = "{"location":"Beijing, China"}" }] },
// tokenUsage = TokenUsage { inputTokenCount = 69, outputTokenCount = 19, totalTokenCount = 88 }, finishReason = TOOL_EXECUTION, metadata = {} }*/
// }
//
//
//}
@@ -0,0 +1,407 @@
//package org.jeecg.modules.airag.test;
//
//import dev.langchain4j.data.document.Document;
//import dev.langchain4j.data.document.DocumentSplitter;
//import dev.langchain4j.data.document.Metadata;
//import dev.langchain4j.data.document.splitter.DocumentSplitters;
//import dev.langchain4j.data.embedding.Embedding;
//import dev.langchain4j.data.segment.TextSegment;
//import dev.langchain4j.memory.ChatMemory;
//import dev.langchain4j.memory.chat.MessageWindowChatMemory;
//import dev.langchain4j.model.chat.ChatLanguageModel;
//import dev.langchain4j.model.embedding.EmbeddingModel;
//import dev.langchain4j.model.input.PromptTemplate;
//import dev.langchain4j.model.openai.OpenAiTokenizer;
//import dev.langchain4j.rag.DefaultRetrievalAugmentor;
//import dev.langchain4j.rag.RetrievalAugmentor;
//import dev.langchain4j.rag.content.injector.DefaultContentInjector;
//import dev.langchain4j.rag.content.retriever.ContentRetriever;
//import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
//import dev.langchain4j.rag.query.router.DefaultQueryRouter;
//import dev.langchain4j.rag.query.router.QueryRouter;
//import dev.langchain4j.service.AiServices;
//import dev.langchain4j.store.embedding.EmbeddingMatch;
//import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
//import dev.langchain4j.store.embedding.EmbeddingStore;
//import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
//import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
//import lombok.extern.slf4j.Slf4j;
//import org.jeecg.ai.assistant.AiChatAssistant;
//import org.jeecg.ai.factory.AiModelFactory;
//import org.jeecg.ai.factory.AiModelOptions;
//import org.junit.Before;
//import org.junit.Test;
//
//import java.util.List;
//
//import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
//
///**
// * @Description: 流程测试
// * @Author: chenrui
// * @Date: 2025/2/11 16:11
// */
//@Slf4j
//public class TestVector {
//
// String openAIBaseUrl = "https://api.v3.cm/v1";
// String openAIApiKey = "sk-xxx";
//
// EmbeddingModel embeddingModel;
// EmbeddingStore<TextSegment> embeddingStore;
//
// @Before
// public void before() {
// AiModelOptions.AiModelOptionsBuilder modelOpBuilder = AiModelOptions.builder().provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
// .baseUrl(openAIBaseUrl).apiKey(openAIApiKey);
// embeddingModel = AiModelFactory.createEmbeddingModel(modelOpBuilder.build());
// embeddingStore = PgVectorEmbeddingStore.builder()
// // Connection and table parameters
// .host("localhost")
// .port(15432)
// .database("postgres")
// .user("postgres")
// .password("123456")
// .table("test_embeddings")
// // Embedding dimension
// .dimension(embeddingModel.dimension()) // Required: Must match the embedding models output dimension
// // Indexing and performance options
// .useIndex(true) // Enable IVFFlat index
// .indexListSize(100) // Number of lists for IVFFlat index
// // Table creation options
// .createTable(true) // Automatically create the table if it doesnt exist
// .dropTableFirst(false) // Dont drop the table first (set to true if you want a fresh start)
// .build();
// }
//
// @Test
// public void testSave2Vector() {
// embeddingStore.removeAll(metadataKey("id").isEqualTo("sdfsdf"));
// DocumentSplitter splitter = DocumentSplitters.recursive(200,
// 50,
// new OpenAiTokenizer());
// EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
// .documentSplitter(splitter)
// .embeddingModel(embeddingModel)
// .embeddingStore(embeddingStore)
// .build();
// Document from = Document.from(doc, Metadata.metadata("id", "sdfsdf"));
// ingestor.ingest(from);
// }
//
// @Test
// public void testQueryByVector() {
// Embedding queryEmbedding = embeddingModel.embed("全日制工作与非全日制工作有什么区别?").content();
// EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
// .queryEmbedding(queryEmbedding)
// .maxResults(5)
// .filter(metadataKey("id").isEqualTo("sdfsdf"))
// .build();
//
// List<EmbeddingMatch<TextSegment>> relevant = embeddingStore.search(embeddingSearchRequest).matches();
// for (int i = 0; i < relevant.size(); i++) {
// EmbeddingMatch<TextSegment> embeddingMatch = relevant.get(i);
// System.out.println("结果:" + i + "=================================================");
// System.out.println("分数:" + embeddingMatch.score()); // 0.8144288608390052
// System.out.println("内容:" + embeddingMatch.embedded().text()); // I like football.
// }
// }
//
//
// @Test
// public void testQueryByRAG() {
// ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
// .embeddingStore(embeddingStore)
// .embeddingModel(embeddingModel)
// .maxResults(5)
// // maxResults can also be specified dynamically depending on the query
//// .dynamicMaxResults(query -> 3)
// .minScore(0.75)
// // minScore can also be specified dynamically depending on the query
//// .dynamicMinScore(query -> 0.75)
// .filter(metadataKey("id").isEqualTo("sdfsdf"))
// // filter can also be specified dynamically depending on the query
//// .dynamicFilter(query -> {
//// String userId = getUserId(query.metadata().chatMemoryId());
//// return metadataKey("userId").isEqualTo(userId);
//// })
// .build();
//
// ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
// ChatLanguageModel chatModel = AiModelFactory.createChatModel(AiModelOptions.builder()
// .baseUrl(openAIBaseUrl).apiKey(openAIApiKey).provider(AiModelFactory.AIMODEL_TYPE_OPENAI).build());
//
// QueryRouter queryRouter = new DefaultQueryRouter(contentRetriever);
// RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder().queryRouter(queryRouter)
// .contentInjector(DefaultContentInjector.builder()
// .promptTemplate(PromptTemplate.from("{{userMessage}}\n\n用以下信息回答问题:\n{{contents}}\n\n"))
// .build()).build();
// AiChatAssistant assistant = AiServices.builder(AiChatAssistant.class)
// .chatLanguageModel(chatModel)
// .chatMemory(chatMemory)
// .retrievalAugmentor(retrievalAugmentor)
// .build();
// String chat = assistant.chat("未签订劳动合同?");
// System.out.println(chat);
//
// }
//
//
// String doc = "中华人民共和国劳动合同法\n" +
// "已根据2013.07.01实施的修正案修改\n" +
// "目录\n" +
// "第一章 总则\n" +
// "第二章 劳动合同的订立\n" +
// "第三章 劳动合同的履行和变更\n" +
// "第四章 劳动合同的解除和终止\n" +
// "第五章 特别规定\n" +
// "第六章 监督检查\n" +
// "第七章 法律责任\n" +
// "第八章 附则\n" +
// "第一章 总则\n" +
// "第一条 为了完善劳动合同制度,明确劳动合同双方当事人的权利和义务,保护劳动者的合法权益,构建和发展和谐稳定的劳动关系,制定本法。\n" +
// "第二条 中华人民共和国境内的企业、个体经济组织、民办非企业单位等组织(以下称用人单位)与劳动者建立劳动关系,订立、履行、变更、解除或者终止劳动合同,适用本法。\n" +
// "国家机关、事业单位、社会团体和与其建立劳动关系的劳动者,订立、履行、变更、解除或者终止劳动合同,依照本法执行。\n" +
// "第三条 订立劳动合同,应当遵循合法、公平、平等自愿、协商一致、诚实信用的原则。\n" +
// "依法订立的劳动合同具有约束力,用人单位与劳动者应当履行劳动合同约定的义务。\n" +
// "第四条 用人单位应当依法建立和完善劳动规章制度,保障劳动者享有劳动权利、履行劳动义务。\n" +
// "用人单位在制定、修改或者决定有关劳动报酬、工作时间、休息休假、劳动安全卫生、保险福利、职工培训、劳动纪律以及劳动定额管理等直接涉及劳动者切身利益的规章制度或者重大事项时,应当经职工代表大会或者全体职工讨论,提出方案和意见,与工会或者职工代表平等协商确定。\n" +
// "在规章制度和重大事项决定实施过程中,工会或者职工认为不适当的,有权向用人单位提出,通过协商予以修改完善。\n" +
// "用人单位应当将直接涉及劳动者切身利益的规章制度和重大事项决定公示,或者告知劳动者。\n" +
// "第五条 县级以上人民政府劳动行政部门会同工会和企业方面代表,建立健全协调劳动关系三方机制,共同研究解决有关劳动关系的重大问题。\n" +
// "第六条 工会应当帮助、指导劳动者与用人单位依法订立和履行劳动合同,并与用人单位建立集体协商机制,维护劳动者的合法权益。\n" +
// "第二章 劳动合同的订立\n" +
// "第七条 用人单位自用工之日起即与劳动者建立劳动关系。用人单位应当建立职工名册备查。\n" +
// "第八条 用人单位招用劳动者时,应当如实告知劳动者工作内容、工作条件、工作地点、职业危害、安全生产状况、劳动报酬,以及劳动者要求了解的其他情况;用人单位有权了解劳动者与劳动合同直接相关的基本情况,劳动者应当如实说明。\n" +
// "第九条 用人单位招用劳动者,不得扣押劳动者的居民身份证和其他证件,不得要求劳动者提供担保或者以其他名义向劳动者收取财物。\n" +
// "第十条 建立劳动关系,应当订立书面劳动合同。\n" +
// "已建立劳动关系,未同时订立书面劳动合同的,应当自用工之日起一个月内订立书面劳动合同。\n" +
// "用人单位与劳动者在用工前订立劳动合同的,劳动关系自用工之日起建立。\n" +
// "第十一条 用人单位未在用工的同时订立书面劳动合同,与劳动者约定的劳动报酬不明确的,新招用的劳动者的劳动报酬按照集体合同规定的标准执行;没有集体合同或者集体合同未规定的,实行同工同酬。\n" +
// "第十二条 劳动合同分为固定期限劳动合同、无固定期限劳动合同和以完成一定工作任务为期限的劳动合同。\n" +
// "第十三条 固定期限劳动合同,是指用人单位与劳动者约定合同终止时间的劳动合同。\n" +
// "用人单位与劳动者协商一致,可以订立固定期限劳动合同。\n" +
// "第十四条 无固定期限劳动合同,是指用人单位与劳动者约定无确定终止时间的劳动合同。\n" +
// "用人单位与劳动者协商一致,可以订立无固定期限劳动合同。有下列情形之一,劳动者提出或者同意续订、订立劳动合同的,除劳动者提出订立固定期限劳动合同外,应当订立无固定期限劳动合同:\n" +
// "(一)劳动者在该用人单位连续工作满十年的;\n" +
// "(二)用人单位初次实行劳动合同制度或者国有企业改制重新订立劳动合同时,劳动者在该用人单位连续工作满十年且距法定退休年龄不足十年的;\n" +
// "(三)连续订立二次固定期限劳动合同,且劳动者没有本法第三十九条和第四十条第一项、第二项规定的情形,续订劳动合同的。\n" +
// "用人单位自用工之日起满一年不与劳动者订立书面劳动合同的,视为用人单位与劳动者已订立无固定期限劳动合同。\n" +
// "第十五条 以完成一定工作任务为期限的劳动合同,是指用人单位与劳动者约定以某项工作的完成为合同期限的劳动合同。\n" +
// "用人单位与劳动者协商一致,可以订立以完成一定工作任务为期限的劳动合同。\n" +
// "第十六条 劳动合同由用人单位与劳动者协商一致,并经用人单位与劳动者在劳动合同文本上签字或者盖章生效。\n" +
// "劳动合同文本由用人单位和劳动者各执一份。\n" +
// "第十七条 劳动合同应当具备以下条款:\n" +
// "(一)用人单位的名称、住所和法定代表人或者主要负责人;\n" +
// "(二)劳动者的姓名、住址和居民身份证或者其他有效身份证件号码;\n" +
// "(三)劳动合同期限;\n" +
// "(四)工作内容和工作地点;\n" +
// "(五)工作时间和休息休假;\n" +
// "(六)劳动报酬;\n" +
// "(七)社会保险;\n" +
// "(八)劳动保护、劳动条件和职业危害防护;\n" +
// "(九)法律、法规规定应当纳入劳动合同的其他事项。\n" +
// "劳动合同除前款规定的必备条款外,用人单位与劳动者可以约定试用期、培训、保守秘密、补充保险和福利待遇等其他事项。\n" +
// "第十八条 劳动合同对劳动报酬和劳动条件等标准约定不明确,引发争议的,用人单位与劳动者可以重新协商;协商不成的,适用集体合同规定;没有集体合同或者集体合同未规定劳动报酬的,实行同工同酬;没有集体合同或者集体合同未规定劳动条件等标准的,适用国家有关规定。\n" +
// "第十九条 劳动合同期限三个月以上不满一年的,试用期不得超过一个月;劳动合同期限一年以上不满三年的,试用期不得超过二个月;三年以上固定期限和无固定期限的劳动合同,试用期不得超过六个月。\n" +
// "同一用人单位与同一劳动者只能约定一次试用期。\n" +
// "以完成一定工作任务为期限的劳动合同或者劳动合同期限不满三个月的,不得约定试用期。\n" +
// "试用期包含在劳动合同期限内。劳动合同仅约定试用期的,试用期不成立,该期限为劳动合同期限。\n" +
// "第二十条 劳动者在试用期的工资不得低于本单位相同岗位最低档工资或者劳动合同约定工资的百分之八十,并不得低于用人单位所在地的最低工资标准。\n" +
// "第二十一条 在试用期中,除劳动者有本法第三十九条和第四十条第一项、第二项规定的情形外,用人单位不得解除劳动合同。用人单位在试用期解除劳动合同的,应当向劳动者说明理由。\n" +
// "第二十二条 用人单位为劳动者提供专项培训费用,对其进行专业技术培训的,可以与该劳动者订立协议,约定服务期。\n" +
// "劳动者违反服务期约定的,应当按照约定向用人单位支付违约金。违约金的数额不得超过用人单位提供的培训费用。用人单位要求劳动者支付的违约金不得超过服务期尚未履行部分所应分摊的培训费用。\n" +
// "用人单位与劳动者约定服务期的,不影响按照正常的工资调整机制提高劳动者在服务期期间的劳动报酬。\n" +
// "第二十三条 用人单位与劳动者可以在劳动合同中约定保守用人单位的商业秘密和与知识产权相关的保密事项。\n" +
// "对负有保密义务的劳动者,用人单位可以在劳动合同或者保密协议中与劳动者约定竞业限制条款,并约定在解除或者终止劳动合同后,在竞业限制期限内按月给予劳动者经济补偿。劳动者违反竞业限制约定的,应当按照约定向用人单位支付违约金。\n" +
// "第二十四条 竞业限制的人员限于用人单位的高级管理人员、高级技术人员和其他负有保密义务的人员。竞业限制的范围、地域、期限由用人单位与劳动者约定,竞业限制的约定不得违反法律、法规的规定。\n" +
// "在解除或者终止劳动合同后,前款规定的人员到与本单位生产或者经营同类产品、从事同类业务的有竞争关系的其他用人单位,或者自己开业生产或者经营同类产品、从事同类业务的竞业限制期限,不得超过二年。\n" +
// "第二十五条 除本法第二十二条和第二十三条规定的情形外,用人单位不得与劳动者约定由劳动者承担违约金。\n" +
// "第二十六条 下列劳动合同无效或者部分无效:\n" +
// "(一)以欺诈、胁迫的手段或者乘人之危,使对方在违背真实意思的情况下订立或者变更劳动合同的;\n" +
// "(二)用人单位免除自己的法定责任、排除劳动者权利的;\n" +
// "(三)违反法律、行政法规强制性规定的。\n" +
// "对劳动合同的无效或者部分无效有争议的,由劳动争议仲裁机构或者人民法院确认。\n" +
// "第二十七条 劳动合同部分无效,不影响其他部分效力的,其他部分仍然有效。\n" +
// "第二十八条 劳动合同被确认无效,劳动者已付出劳动的,用人单位应当向劳动者支付劳动报酬。劳动报酬的数额,参照本单位相同或者相近岗位劳动者的劳动报酬确定。\n" +
// "第三章 劳动合同的履行和变更\n" +
// "第二十九条 用人单位与劳动者应当按照劳动合同的约定,全面履行各自的义务。\n" +
// "第三十条 用人单位应当按照劳动合同约定和国家规定,向劳动者及时足额支付劳动报酬。\n" +
// "用人单位拖欠或者未足额支付劳动报酬的,劳动者可以依法向当地人民法院申请支付令,人民法院应当依法发出支付令。\n" +
// "第三十一条 用人单位应当严格执行劳动定额标准,不得强迫或者变相强迫劳动者加班。用人单位安排加班的,应当按照国家有关规定向劳动者支付加班费。\n" +
// "第三十二条 劳动者拒绝用人单位管理人员违章指挥、强令冒险作业的,不视为违反劳动合同。\n" +
// "劳动者对危害生命安全和身体健康的劳动条件,有权对用人单位提出批评、检举和控告。\n" +
// "第三十三条 用人单位变更名称、法定代表人、主要负责人或者投资人等事项,不影响劳动合同的履行。\n" +
// "第三十四条 用人单位发生合并或者分立等情况,原劳动合同继续有效,劳动合同由承继其权利和义务的用人单位继续履行。\n" +
// "第三十五条 用人单位与劳动者协商一致,可以变更劳动合同约定的内容。变更劳动合同,应当采用书面形式。\n" +
// "变更后的劳动合同文本由用人单位和劳动者各执一份。\n" +
// "第四章 劳动合同的解除和终止\n" +
// "第三十六条 用人单位与劳动者协商一致,可以解除劳动合同。\n" +
// "第三十七条 劳动者提前三十日以书面形式通知用人单位,可以解除劳动合同。劳动者在试用期内提前三日通知用人单位,可以解除劳动合同。\n" +
// "第三十八条 用人单位有下列情形之一的,劳动者可以解除劳动合同:\n" +
// "(一)未按照劳动合同约定提供劳动保护或者劳动条件的;\n" +
// "(二)未及时足额支付劳动报酬的;\n" +
// "(三)未依法为劳动者缴纳社会保险费的;\n" +
// "(四)用人单位的规章制度违反法律、法规的规定,损害劳动者权益的;\n" +
// "(五)因本法第二十六条第一款规定的情形致使劳动合同无效的;\n" +
// "(六)法律、行政法规规定劳动者可以解除劳动合同的其他情形。\n" +
// "用人单位以暴力、威胁或者非法限制人身自由的手段强迫劳动者劳动的,或者用人单位违章指挥、强令冒险作业危及劳动者人身安全的,劳动者可以立即解除劳动合同,不需事先告知用人单位。\n" +
// "第三十九条 劳动者有下列情形之一的,用人单位可以解除劳动合同:\n" +
// "(一)在试用期间被证明不符合录用条件的;\n" +
// "(二)严重违反用人单位的规章制度的;\n" +
// "(三)严重失职,营私舞弊,给用人单位造成重大损害的;\n" +
// "(四)劳动者同时与其他用人单位建立劳动关系,对完成本单位的工作任务造成严重影响,或者经用人单位提出,拒不改正的;\n" +
// "(五)因本法第二十六条第一款第一项规定的情形致使劳动合同无效的;\n" +
// "(六)被依法追究刑事责任的。\n" +
// "第四十条 有下列情形之一的,用人单位提前三十日以书面形式通知劳动者本人或者额外支付劳动者一个月工资后,可以解除劳动合同:\n" +
// "(一)劳动者患病或者非因工负伤,在规定的医疗期满后不能从事原工作,也不能从事由用人单位另行安排的工作的;\n" +
// "(二)劳动者不能胜任工作,经过培训或者调整工作岗位,仍不能胜任工作的;\n" +
// "(三)劳动合同订立时所依据的客观情况发生重大变化,致使劳动合同无法履行,经用人单位与劳动者协商,未能就变更劳动合同内容达成协议的。\n" +
// "第四十一条 有下列情形之一,需要裁减人员二十人以上或者裁减不足二十人但占企业职工总数百分之十以上的,用人单位提前三十日向工会或者全体职工说明情况,听取工会或者职工的意见后,裁减人员方案经向劳动行政部门报告,可以裁减人员:\n" +
// "(一)依照企业破产法规定进行重整的;\n" +
// "(二)生产经营发生严重困难的;\n" +
// "(三)企业转产、重大技术革新或者经营方式调整,经变更劳动合同后,仍需裁减人员的;\n" +
// "(四)其他因劳动合同订立时所依据的客观经济情况发生重大变化,致使劳动合同无法履行的。\n" +
// "裁减人员时,应当优先留用下列人员:\n" +
// "(一)与本单位订立较长期限的固定期限劳动合同的;\n" +
// "(二)与本单位订立无固定期限劳动合同的;\n" +
// "(三)家庭无其他就业人员,有需要扶养的老人或者未成年人的。\n" +
// "用人单位依照本条第一款规定裁减人员,在六个月内重新招用人员的,应当通知被裁减的人员,并在同等条件下优先招用被裁减的人员。\n" +
// "第四十二条 劳动者有下列情形之一的,用人单位不得依照本法第四十条、第四十一条的规定解除劳动合同:\n" +
// "(一)从事接触职业病危害作业的劳动者未进行离岗前职业健康检查,或者疑似职业病病人在诊断或者医学观察期间的;\n" +
// "(二)在本单位患职业病或者因工负伤并被确认丧失或者部分丧失劳动能力的;\n" +
// "(三)患病或者非因工负伤,在规定的医疗期内的;\n" +
// "(四)女职工在孕期、产期、哺乳期的;\n" +
// "(五)在本单位连续工作满十五年,且距法定退休年龄不足五年的;\n" +
// "(六)法律、行政法规规定的其他情形。\n" +
// "第四十三条 用人单位单方解除劳动合同,应当事先将理由通知工会。用人单位违反法律、行政法规规定或者劳动合同约定的,工会有权要求用人单位纠正。用人单位应当研究工会的意见,并将处理结果书面通知工会。\n" +
// "第四十四条 有下列情形之一的,劳动合同终止:\n" +
// "(一)劳动合同期满的;\n" +
// "(二)劳动者开始依法享受基本养老保险待遇的;\n" +
// "(三)劳动者死亡,或者被人民法院宣告死亡或者宣告失踪的;\n" +
// "(四)用人单位被依法宣告破产的;\n" +
// "(五)用人单位被吊销营业执照、责令关闭、撤销或者用人单位决定提前解散的;\n" +
// "(六)法律、行政法规规定的其他情形。\n" +
// "第四十五条 劳动合同期满,有本法第四十二条规定情形之一的,劳动合同应当续延至相应的情形消失时终止。但是,本法第四十二条第二项规定丧失或者部分丧失劳动能力劳动者的劳动合同的终止,按照国家有关工伤保险的规定执行。\n" +
// "第四十六条 有下列情形之一的,用人单位应当向劳动者支付经济补偿:\n" +
// "(一)劳动者依照本法第三十八条规定解除劳动合同的;\n" +
// "(二)用人单位依照本法第三十六条规定向劳动者提出解除劳动合同并与劳动者协商一致解除劳动合同的;\n" +
// "(三)用人单位依照本法第四十条规定解除劳动合同的;\n" +
// "(四)用人单位依照本法第四十一条第一款规定解除劳动合同的;\n" +
// "(五)除用人单位维持或者提高劳动合同约定条件续订劳动合同,劳动者不同意续订的情形外,依照本法第四十四条第一项规定终止固定期限劳动合同的;\n" +
// "(六)依照本法第四十四条第四项、第五项规定终止劳动合同的;\n" +
// "(七)法律、行政法规规定的其他情形。\n" +
// "第四十七条 经济补偿按劳动者在本单位工作的年限,每满一年支付一个月工资的标准向劳动者支付。六个月以上不满一年的,按一年计算;不满六个月的,向劳动者支付半个月工资的经济补偿。\n" +
// "劳动者月工资高于用人单位所在直辖市、设区的市级人民政府公布的本地区上年度职工月平均工资三倍的,向其支付经济补偿的标准按职工月平均工资三倍的数额支付,向其支付经济补偿的年限最高不超过十二年。\n" +
// "本条所称月工资是指劳动者在劳动合同解除或者终止前十二个月的平均工资。\n" +
// "第四十八条 用人单位违反本法规定解除或者终止劳动合同,劳动者要求继续履行劳动合同的,用人单位应当继续履行;劳动者不要求继续履行劳动合同或者劳动合同已经不能继续履行的,用人单位应当依照本法第八十七条规定支付赔偿金。\n" +
// "第四十九条 国家采取措施,建立健全劳动者社会保险关系跨地区转移接续制度。\n" +
// "第五十条 用人单位应当在解除或者终止劳动合同时出具解除或者终止劳动合同的证明,并在十五日内为劳动者办理档案和社会保险关系转移手续。\n" +
// "劳动者应当按照双方约定,办理工作交接。用人单位依照本法有关规定应当向劳动者支付经济补偿的,在办结工作交接时支付。\n" +
// "用人单位对已经解除或者终止的劳动合同的文本,至少保存二年备查。\n" +
// "第五章 特别规定\n" +
// "第一节 集体合同\n" +
// "第五十一条 企业职工一方与用人单位通过平等协商,可以就劳动报酬、工作时间、休息休假、劳动安全卫生、保险福利等事项订立集体合同。集体合同草案应当提交职工代表大会或者全体职工讨论通过。\n" +
// "集体合同由工会代表企业职工一方与用人单位订立;尚未建立工会的用人单位,由上级工会指导劳动者推举的代表与用人单位订立。\n" +
// "第五十二条 企业职工一方与用人单位可以订立劳动安全卫生、女职工权益保护、工资调整机制等专项集体合同。\n" +
// "第五十三条 在县级以下区域内,建筑业、采矿业、餐饮服务业等行业可以由工会与企业方面代表订立行业性集体合同,或者订立区域性集体合同。\n" +
// "第五十四条 集体合同订立后,应当报送劳动行政部门;劳动行政部门自收到集体合同文本之日起十五日内未提出异议的,集体合同即行生效。\n" +
// "依法订立的集体合同对用人单位和劳动者具有约束力。行业性、区域性集体合同对当地本行业、本区域的用人单位和劳动者具有约束力。\n" +
// "第五十五条 集体合同中劳动报酬和劳动条件等标准不得低于当地人民政府规定的最低标准;用人单位与劳动者订立的劳动合同中劳动报酬和劳动条件等标准不得低于集体合同规定的标准。\n" +
// "第五十六条 用人单位违反集体合同,侵犯职工劳动权益的,工会可以依法要求用人单位承担责任;因履行集体合同发生争议,经协商解决不成的,工会可以依法申请仲裁、提起诉讼。\n" +
// "第二节 劳务派遣\n" +
// "第五十七条 经营劳务派遣业务应当具备下列条件:\n" +
// " (一)注册资本不得少于人民币二百万元;\n" +
// " (二)有与开展业务相适应的固定的经营场所和设施;\n" +
// " (三)有符合法律、行政法规规定的劳务派遣管理制度;\n" +
// " (四)法律、行政法规规定的其他条件。\n" +
// " 经营劳务派遣业务,应当向劳动行政部门依法申请行政许可;经许可的,依法办理相应的公司登记。未经许可,任何单位和个人不得经营劳务派遣业务。\n" +
// "第五十八条 劳务派遣单位是本法所称用人单位,应当履行用人单位对劳动者的义务。劳务派遣单位与被派遣劳动者订立的劳动合同,除应当载明本法第十七条规定的事项外,还应当载明被派遣劳动者的用工单位以及派遣期限、工作岗位等情况。\n" +
// "劳务派遣单位应当与被派遣劳动者订立二年以上的固定期限劳动合同,按月支付劳动报酬;被派遣劳动者在无工作期间,劳务派遣单位应当按照所在地人民政府规定的最低工资标准,向其按月支付报酬。\n" +
// "第五十九条 劳务派遣单位派遣劳动者应当与接受以劳务派遣形式用工的单位(以下称用工单位)订立劳务派遣协议。劳务派遣协议应当约定派遣岗位和人员数量、派遣期限、劳动报酬和社会保险费的数额与支付方式以及违反协议的责任。\n" +
// "用工单位应当根据工作岗位的实际需要与劳务派遣单位确定派遣期限,不得将连续用工期限分割订立数个短期劳务派遣协议。\n" +
// "第六十条 劳务派遣单位应当将劳务派遣协议的内容告知被派遣劳动者。\n" +
// "劳务派遣单位不得克扣用工单位按照劳务派遣协议支付给被派遣劳动者的劳动报酬。\n" +
// "劳务派遣单位和用工单位不得向被派遣劳动者收取费用。\n" +
// "第六十一条 劳务派遣单位跨地区派遣劳动者的,被派遣劳动者享有的劳动报酬和劳动条件,按照用工单位所在地的标准执行。\n" +
// "第六十二条 用工单位应当履行下列义务:\n" +
// "(一)执行国家劳动标准,提供相应的劳动条件和劳动保护;\n" +
// "(二)告知被派遣劳动者的工作要求和劳动报酬;\n" +
// "(三)支付加班费、绩效奖金,提供与工作岗位相关的福利待遇;\n" +
// "(四)对在岗被派遣劳动者进行工作岗位所必需的培训;\n" +
// "(五)连续用工的,实行正常的工资调整机制。\n" +
// "用工单位不得将被派遣劳动者再派遣到其他用人单位。\n" +
// "第六十三条 被派遣劳动者享有与用工单位的劳动者同工同酬的权利。用工单位应当按照同工同酬原则,对被派遣劳动者与本单位同类岗位的劳动者实行相同的劳动报酬分配办法。用工单位无同类岗位劳动者的,参照用工单位所在地相同或者相近岗位劳动者的劳动报酬确定。\n" +
// "劳务派遣单位与被派遣劳动者订立的劳动合同和与用工单位订立的劳务派遣协议,载明或者约定的向被派遣劳动者支付的劳动报酬应当符合前款规定。\n" +
// "第六十四条 被派遣劳动者有权在劳务派遣单位或者用工单位依法参加或者组织工会,维护自身的合法权益。\n" +
// "第六十五条 被派遣劳动者可以依照本法第三十六条、第三十八条的规定与劳务派遣单位解除劳动合同。\n" +
// "被派遣劳动者有本法第三十九条和第四十条第一项、第二项规定情形的,用工单位可以将劳动者退回劳务派遣单位,劳务派遣单位依照本法有关规定,可以与劳动者解除劳动合同。\n" +
// "第六十六条 劳动合同用工是我国的企业基本用工形式。劳务派遣用工是补充形式,只能在临时性、辅助性或者替代性的工作岗位上实施。\n" +
// " 前款规定的临时性工作岗位是指存续时间不超过六个月的岗位;辅助性工作岗位是指为主营业务岗位提供服务的非主营业务岗位;替代性工作岗位是指用工单位的劳动者因脱产学习、休假等原因无法工作的一定期间内,可以由其他劳动者替代工作的岗位。\n" +
// " 用工单位应当严格控制劳务派遣用工数量,不得超过其用工总量的一定比例,具体比例由国务院劳动行政部门规定。\n" +
// "第六十七条 用人单位不得设立劳务派遣单位向本单位或者所属单位派遣劳动者。\n" +
// "第三节 非全日制用工\n" +
// "第六十八条 非全日制用工,是指以小时计酬为主,劳动者在同一用人单位一般平均每日工作时间不超过四小时,每周工作时间累计不超过二十四小时的用工形式。\n" +
// "第六十九条 非全日制用工双方当事人可以订立口头协议。\n" +
// "从事非全日制用工的劳动者可以与一个或者一个以上用人单位订立劳动合同;但是,后订立的劳动合同不得影响先订立的劳动合同的履行。\n" +
// "第七十条 非全日制用工双方当事人不得约定试用期。\n" +
// "第七十一条 非全日制用工双方当事人任何一方都可以随时通知对方终止用工。终止用工,用人单位不向劳动者支付经济补偿。\n" +
// "第七十二条 非全日制用工小时计酬标准不得低于用人单位所在地人民政府规定的最低小时工资标准。\n" +
// "非全日制用工劳动报酬结算支付周期最长不得超过十五日。\n" +
// "第六章 监督检查\n" +
// "第七十三条 国务院劳动行政部门负责全国劳动合同制度实施的监督管理。\n" +
// "县级以上地方人民政府劳动行政部门负责本行政区域内劳动合同制度实施的监督管理。\n" +
// "县级以上各级人民政府劳动行政部门在劳动合同制度实施的监督管理工作中,应当听取工会、企业方面代表以及有关行业主管部门的意见。\n" +
// "第七十四条 县级以上地方人民政府劳动行政部门依法对下列实施劳动合同制度的情况进行监督检查:\n" +
// "(一)用人单位制定直接涉及劳动者切身利益的规章制度及其执行的情况;\n" +
// "(二)用人单位与劳动者订立和解除劳动合同的情况;\n" +
// "(三)劳务派遣单位和用工单位遵守劳务派遣有关规定的情况;\n" +
// "(四)用人单位遵守国家关于劳动者工作时间和休息休假规定的情况;\n" +
// "(五)用人单位支付劳动合同约定的劳动报酬和执行最低工资标准的情况;\n" +
// "(六)用人单位参加各项社会保险和缴纳社会保险费的情况;\n" +
// "(七)法律、法规规定的其他劳动监察事项。\n" +
// "第七十五条 县级以上地方人民政府劳动行政部门实施监督检查时,有权查阅与劳动合同、集体合同有关的材料,有权对劳动场所进行实地检查,用人单位和劳动者都应当如实提供有关情况和材料。\n" +
// "劳动行政部门的工作人员进行监督检查,应当出示证件,依法行使职权,文明执法。\n" +
// "第七十六条 县级以上人民政府建设、卫生、安全生产监督管理等有关主管部门在各自职责范围内,对用人单位执行劳动合同制度的情况进行监督管理。\n" +
// "第七十七条 劳动者合法权益受到侵害的,有权要求有关部门依法处理,或者依法申请仲裁、提起诉讼。\n" +
// "第七十八条 工会依法维护劳动者的合法权益,对用人单位履行劳动合同、集体合同的情况进行监督。用人单位违反劳动法律、法规和劳动合同、集体合同的,工会有权提出意见或者要求纠正;劳动者申请仲裁、提起诉讼的,工会依法给予支持和帮助。\n" +
// "第七十九条 任何组织或者个人对违反本法的行为都有权举报,县级以上人民政府劳动行政部门应当及时核实、处理,并对举报有功人员给予奖励。\n" +
// "第七章 法律责任\n" +
// "第八十条 用人单位直接涉及劳动者切身利益的规章制度违反法律、法规规定的,由劳动行政部门责令改正,给予警告;给劳动者造成损害的,应当承担赔偿责任。\n" +
// "第八十一条 用人单位提供的劳动合同文本未载明本法规定的劳动合同必备条款或者用人单位未将劳动合同文本交付劳动者的,由劳动行政部门责令改正;给劳动者造成损害的,应当承担赔偿责任。\n" +
// "第八十二条 用人单位自用工之日起超过一个月不满一年未与劳动者订立书面劳动合同的,应当向劳动者每月支付二倍的工资。\n" +
// "用人单位违反本法规定不与劳动者订立无固定期限劳动合同的,自应当订立无固定期限劳动合同之日起向劳动者每月支付二倍的工资。\n" +
// "第八十三条 用人单位违反本法规定与劳动者约定试用期的,由劳动行政部门责令改正;违法约定的试用期已经履行的,由用人单位以劳动者试用期满月工资为标准,按已经履行的超过法定试用期的期间向劳动者支付赔偿金。\n" +
// "第八十四条 用人单位违反本法规定,扣押劳动者居民身份证等证件的,由劳动行政部门责令限期退还劳动者本人,并依照有关法律规定给予处罚。\n" +
// "用人单位违反本法规定,以担保或者其他名义向劳动者收取财物的,由劳动行政部门责令限期退还劳动者本人,并以每人五百元以上二千元以下的标准处以罚款;给劳动者造成损害的,应当承担赔偿责任。\n" +
// "劳动者依法解除或者终止劳动合同,用人单位扣押劳动者档案或者其他物品的,依照前款规定处罚。\n" +
// "第八十五条 用人单位有下列情形之一的,由劳动行政部门责令限期支付劳动报酬、加班费或者经济补偿;劳动报酬低于当地最低工资标准的,应当支付其差额部分;逾期不支付的,责令用人单位按应付金额百分之五十以上百分之一百以下的标准向劳动者加付赔偿金:\n" +
// "(一)未按照劳动合同的约定或者国家规定及时足额支付劳动者劳动报酬的;\n" +
// "(二)低于当地最低工资标准支付劳动者工资的;\n" +
// "(三)安排加班不支付加班费的;\n" +
// "(四)解除或者终止劳动合同,未依照本法规定向劳动者支付经济补偿的。\n" +
// "第八十六条 劳动合同依照本法第二十六条规定被确认无效,给对方造成损害的,有过错的一方应当承担赔偿责任。\n" +
// "第八十七条 用人单位违反本法规定解除或者终止劳动合同的,应当依照本法第四十七条规定的经济补偿标准的二倍向劳动者支付赔偿金。\n" +
// "第八十八条 用人单位有下列情形之一的,依法给予行政处罚;构成犯罪的,依法追究刑事责任;给劳动者造成损害的,应当承担赔偿责任:\n" +
// "(一)以暴力、威胁或者非法限制人身自由的手段强迫劳动的;\n" +
// "(二)违章指挥或者强令冒险作业危及劳动者人身安全的;\n" +
// "(三)侮辱、体罚、殴打、非法搜查或者拘禁劳动者的;\n" +
// "(四)劳动条件恶劣、环境污染严重,给劳动者身心健康造成严重损害的。\n" +
// "第八十九条 用人单位违反本法规定未向劳动者出具解除或者终止劳动合同的书面证明,由劳动行政部门责令改正;给劳动者造成损害的,应当承担赔偿责任。\n" +
// "第九十条 劳动者违反本法规定解除劳动合同,或者违反劳动合同中约定的保密义务或者竞业限制,给用人单位造成损失的,应当承担赔偿责任。\n" +
// "第九十一条 用人单位招用与其他用人单位尚未解除或者终止劳动合同的劳动者,给其他用人单位造成损失的,应当承担连带赔偿责任。\n" +
// "第九十二条 违反本法规定,未经许可,擅自经营劳务派遣业务的,由劳动行政部门责令停止违法行为,没收违法所得,并处违法所得一倍以上五倍以下的罚款;没有违法所得的,可以处五万元以下的罚款。\n" +
// " 劳务派遣单位、用工单位违反本法有关劳务派遣规定的,由劳动行政部门责令限期改正;逾期不改正的,以每人五千元以上一万元以下的标准处以罚款,对劳务派遣单位,吊销其劳务派遣业务经营许可证。用工单位给被派遣劳动者造成损害的,劳务派遣单位与用工单位承担连带赔偿责任。\n" +
// "第九十三条 对不具备合法经营资格的用人单位的违法犯罪行为,依法追究法律责任;劳动者已经付出劳动的,该单位或者其出资人应当依照本法有关规定向劳动者支付劳动报酬、经济补偿、赔偿金;给劳动者造成损害的,应当承担赔偿责任。\n" +
// "第九十四条 个人承包经营违反本法规定招用劳动者,给劳动者造成损害的,发包的组织与个人承包经营者承担连带赔偿责任。\n" +
// "第九十五条 劳动行政部门和其他有关主管部门及其工作人员玩忽职守、不履行法定职责,或者违法行使职权,给劳动者或者用人单位造成损害的,应当承担赔偿责任;对直接负责的主管人员和其他直接责任人员,依法给予行政处分;构成犯罪的,依法追究刑事责任。\n" +
// "第八章 附则\n" +
// "第九十六条 事业单位与实行聘用制的工作人员订立、履行、变更、解除或者终止劳动合同,法律、行政法规或者国务院另有规定的,依照其规定;未作规定的,依照本法有关规定执行。\n" +
// "第九十七条 本法施行前已依法订立且在本法施行之日存续的劳动合同,继续履行;本法第十四条第二款第三项规定连续订立固定期限劳动合同的次数,自本法施行后续订固定期限劳动合同时开始计算。\n" +
// "本法施行前已建立劳动关系,尚未订立书面劳动合同的,应当自本法施行之日起一个月内订立。\n" +
// "本法施行之日存续的劳动合同在本法施行后解除或者终止,依照本法第四十六条规定应当支付经济补偿的,经济补偿年限自本法施行之日起计算;本法施行前按照当时有关规定,用人单位应当向劳动者支付经济补偿的,按照当时有关规定执行。\n" +
// "第九十八条 本法自2008年1月1日起施行。\n" +
// "\n";
//
//
//}
@@ -0,0 +1,97 @@
常见问题
1.pnpm安装依赖报错
错误: node\_modules\\vite\\node\_modules\\esbuild\\esbuild.exe ENOENT
解决方案:https://blog.csdn.net/weixin_41760500/article/details/119885574
命令:node ./node_modules/esbuild/install.js
2.pnpm安装后,访问提示缺少依赖

解决方案: https://stackoverflow.com/questions/70597494/pnpm-does-not-resolve-dependencies
3.pnpm install出现错误
错误:ERR\_PNPM\_PEER\_DEP\_ISSUES Unmet peer dependencies
http://ms521.cn/index.php/Home/Index/article/aid/271
4.项目安装依赖无问题,访问页面报错
前端部分报错:
[plugin:vite:vue-jsx] Cannot find package 'C:\Users\123\Desktop\JeecgBoot-master\pincone_system\jeecgboot-vue3\node_modules\.pnpm\@vitejs+plugin-vue-jsx@3.1.0_vite@5.4.9_@types+node@20.16.13_less@4.2.0_terser@5.36.0__vue@3.5.12_typescript@4.9.5_\node_modules\@babel\plugin-transform-typescript\lib\index.js' imported from C:\Users\123\Desktop\JeecgBoot-master\pincone_system\jeecgboot-vue3\node_modules\.pnpm\@vitejs+plugin-vue-jsx@3.1.0_vite@5.4.9_@types+node@20.16.13_less@4.2.0_terser@5.36.0__vue@3.5.12_typescript@4.9.5_\node_modules\@vitejs\plugin-vue-jsx\dist\index.cjs Did you mean to import "@babel/plugin-transform-typescript/lib/index.js"? #7396
回答: 是因为项目的路径太长导致 ,相关问题Issues,查看相关博客
• https://blog.csdn.net/weixin_43235500/article/details/142144989
• https://blog.csdn.net/qq_25996219/article/details/140328092
5. 通过npm install启动报错
建议:请使用pnpm i 可以避免更多问题
错误情况:

解决:进入提示的路径 \node_modules\vite-plugin-mock\node_modules\esbuild\
执行命令: node install.js
再启动就好了
6. 前端刷新进不了登录页面
报错props.ts:15 Uncaught (in promise) SyntaxError: Unexpected token '='
错误截图:
原因:谷歌浏览器版本过低,升级浏览器
比如这边版本就过低了
7.表单如何全部禁用
加上这个属性就可以了

效果

8.table列表如何自定义排序
defSort: {
column: 'id',
order: 'desc',
},
参考示例:
9.idea编写js时爆红,提示statement expected
https://blog.csdn.net/mlsama/article/details/80633009
10.抽屉的setDrawerProps不好使(值会还原)
11. 如何删除不需要的demo,制作一个精简版本
精简项目,删除demo等非必须功能
12.vue3 暗黑模式下显示不完整
错误示例:
解决方案:
在样式中的字体颜色和背景颜色使用@变量名称来代替
color: @text-color;
background-color: @component-background;
[info] 通用样式变量名称可以在在目录bulid->vite->plugin->themes.ts中找到,darkModifyVars是重写antd中的样式
[info]更多样式变量名称请参考目录node_modules/es/style/themes/default.less

改造完成之后的效果
13.操作列“删除按钮”界面布局异常
相关issue
https://github.com/jeecgboot/jeecgboot-vue3/issues/458
问题截图:
解决方案:找到popConfirm填写属性placement: 'left'
placement: 'left',
效果截图
14.在centos7中下载依赖pnpm i时 mozjpeg依赖下载不下来
https://github.com/jeecgboot/jeecgboot-vue3/issues/433#issuecomment-1510470534
15.日期遮挡问题
问题截图:
下拉显示组件,页面滚动时,页面出现错位遮挡问题
解决方案:将组件挂载到父节点上
getPopupContainer: (node) => node.parentNode,
效果截图
16.nextTick作用
在 Vue 3 中,nextTick 方法用于在 DOM 更新之后执行回调函数。它的作用是在下次 DOM 更新循环结束后执行一些操作,以确保你在操作更新的 DOM 元素时能够获取到最新的结果。
nextTick 方法可以用于以下情况:
1 在更新数据后立即操作 DOM 元素。
2 在更新组件后执行某些逻辑或触发一些副作用。
3 在更新后获取更新后的 DOM 元素的尺寸或位置等信息。
使用nextTick 方法有两种方式:
1.使用回调函数:
nextTick(()=>
//在DOM更新后执行的操作
}):
2.使用Promise:
nextTick().then(()=>
//在DOM更新后执行的操作
})
无论使用哪种方式,传入的回调函数或Promisel回调都会在下一次DOM更新周期之后被调用。这样可以确保在数据变化后,Vue已经完成了相应的DOM更新。
需要注意的是,nextTick 方法是异步执行的,因此不能保证回调函数会立即执行。如果需要等待nextTick执行完成,可以使用await关键字或者. then()方法来等待Promise的完成。
17. 一个页面多个表格,列的展示会互相影响
[info] 相关issue
https://github.com/jeecgboot/jeecgboot-vue3/issues/1064
[info]问题截图
[info] 解决方案:在不同的tableProps下设置不同的checkKey即可解决
tableSetting: { cacheKey: 'depart_user_departInfo' },
18. 通过npm install启动报错

可以使用这个命令:
npm install --ignore-scripts
@@ -0,0 +1,236 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>jeecg-boot-module-joa-flowable</artifactId>
<parent>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-module</artifactId>
<version>3.8.0</version>
</parent>
<description>此模块属于商业功能范畴,需要购买商业授权才可使用,禁止盗卖、泄露、传播商业源码和商业资料,双方签订合同有保密协议,违者收回商业授权,并追究法律责任</description>
<url>http://www.jeecg.com</url>
<licenses>
<license>
<name>商业授权说明:商业版本只针对企业用户授权,提供更专业更强大的商业版本</name>
<url>http://jeecg.com/vip</url>
<distribution>禁止盗版买卖,违者必究,举报邮箱: jeecgos@163.com</distribution>
<comments>Copyright:北京国炬信息技术有限公司</comments>
</license>
</licenses>
<developers>
<developer>
<name>北京国炬信息技术有限公司</name>
<email>jeecgos@163.com</email>
</developer>
<developer>
<name>授权对象(合作企业): 中国船舶集团有限公司第七〇四研究所</name>
<email>授权范围:贵司通过商业合作,获得商业产品的使用权,但商业版源码、资料禁止传播、盗卖,违者必究!!!</email>
</developer>
</developers>
<scm>
<connection>http://www.jeecg.com</connection>
<developerConnection>http://www.guojusoft.com</developerConnection>
<url>http://www.jeecg.com/vip</url>
</scm>
<properties>
<flowable.version>6.8.0</flowable.version>
</properties>
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jeecg</id>
<name>jeecg Repository</name>
<url>https://maven.jeecg.org/nexus/content/repositories/jeecg</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<!-- 单体system api -->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-system-local-api</artifactId>
</dependency>
<!-- 微服务starter和system微服务 api
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-starter-cloud</artifactId>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-system-cloud-api</artifactId>
</dependency>-->
<!--flowable 核心依赖-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
<version>${flowable.version}</version>
<exclusions>
<!-- 排除mybatis依赖,跟mybatis-plus冲突 -->
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Flowable报错:Can't find scripting engine for 'javascript', 从Java15开始,JDK移除了内置的Nashorn JavaScript引擎 -->
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-bpmn-formatt</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-idm-spring</artifactId>
<version>${flowable.version}</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-dmn-register</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-idm-spring-configurator</artifactId>
<version>${flowable.version}</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-json-converter</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-bpmn-layout</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.12.2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-langs</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-xj-jdbc</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svg-all</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<exclusions>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 七牛云SDK -->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-uapp-sdk</artifactId>
<version>7.4.0</version>
</dependency>
<!-- 百度SDK OCR -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-langs</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>com.baidu.aip</groupId>
<artifactId>java-sdk</artifactId>
<exclusions>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
<exclusion>
<artifactId>jsr305</artifactId>
<groupId>com.google.code.findbugs</groupId>
</exclusion>
<exclusion>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- mongon -->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-starter-mongon</artifactId>
</dependency>
<!-- begin JOA作为微服务独立启动,需要放开以下依赖 -->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
</dependency>
<!-- end JOA作为微服务独立启动,需要放开以下依赖 -->
</dependencies>
</project>
@@ -0,0 +1,514 @@
package liquibase.database.core;
import liquibase.CatalogAndSchema;
import liquibase.Scope;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.DatabaseConnection;
import liquibase.database.OfflineConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.DatabaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.exception.ValidationErrors;
import liquibase.executor.ExecutorService;
import liquibase.statement.DatabaseFunction;
import liquibase.statement.SequenceCurrentValueFunction;
import liquibase.statement.SequenceNextValueFunction;
import liquibase.statement.core.RawCallStatement;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Catalog;
import liquibase.structure.core.Index;
import liquibase.structure.core.PrimaryKey;
import liquibase.structure.core.Schema;
import liquibase.util.JdbcUtils;
import liquibase.util.StringUtil;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class KingbaseDatabase extends AbstractJdbcDatabase {
private static final String PRODUCT_NAME = "KingbaseES";
@Override
protected String getDefaultDatabaseProductName() {
return PRODUCT_NAME;
}
@Override
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
}
@Override
public String getDefaultDriver(String url) {
if(url.startsWith("jdbc:kingbase8")) {
return "com.kingbase8.Driver";
}
return null;
}
@Override
public String getShortName() {
return "kingbase";
}
@Override
public Integer getDefaultPort() {
return 54321;
}
@Override
public boolean supportsInitiallyDeferrableColumns() {
return true;
}
@Override
public boolean supportsTablespaces() {
return true;
}
@Override
public int getPriority() {
return PRIORITY_DEFAULT;
}
private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
protected final int SHORT_IDENTIFIERS_LENGTH = 30;
protected final int LONG_IDENTIFIERS_LEGNTH = 128;
public static final int ORACLE_12C_MAJOR_VERSION = 12;
private Set<String> reservedWords = new HashSet<>();
private Set<String> userDefinedTypes;
private Map<String, String> savedSessionNlsSettings;
private Boolean canAccessDbaRecycleBin;
private Integer databaseMajorVersion;
private Integer databaseMinorVersion;
public KingbaseDatabase() {
super.unquotedObjectsAreUppercased = true;
//noinspection HardCodedStringLiteral
super.setCurrentDateTimeFunction("SYSTIMESTAMP");
// Setting list of Oracle's native functions
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("SYSDATE"));
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
//noinspection HardCodedStringLiteral
super.sequenceNextValueFunction = "%s.nextval";
//noinspection HardCodedStringLiteral
super.sequenceCurrentValueFunction = "%s.currval";
}
private void tryProxySession(final String url, final Connection con) {
Matcher m = PROXY_USER.matcher(url);
if (m.matches()) {
Properties props = new Properties();
props.put("PROXY_USER_NAME", m.group(1));
try {
Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class);
method.setAccessible(true);
method.invoke(con, 1, props);
} catch (Exception e) {
Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
}
}
}
@Override
public int getDatabaseMajorVersion() throws DatabaseException {
if (databaseMajorVersion == null) {
return super.getDatabaseMajorVersion();
} else {
return databaseMajorVersion;
}
}
@Override
public int getDatabaseMinorVersion() throws DatabaseException {
if (databaseMinorVersion == null) {
return super.getDatabaseMinorVersion();
} else {
return databaseMinorVersion;
}
}
@Override
public String getJdbcCatalogName(CatalogAndSchema schema) {
return null;
}
@Override
public String getJdbcSchemaName(CatalogAndSchema schema) {
return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
}
@Override
protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) {
if (StringUtil.isEmpty(generationType)) {
return super.getAutoIncrementClause();
}
String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ]
String generationStrategy = generationType;
if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
generationStrategy += " ON NULL";
}
return String.format(autoIncrementClause, generationStrategy);
}
@Override
public String generatePrimaryKeyName(String tableName) {
if (tableName.length() > 27) {
//noinspection HardCodedStringLiteral
return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27);
} else {
//noinspection HardCodedStringLiteral
return "PK_" + tableName.toUpperCase(Locale.US);
}
}
@Override
public boolean isReservedWord(String objectName) {
return reservedWords.contains(objectName.toUpperCase());
}
@Override
public boolean supportsSequences() {
return true;
}
/**
* Oracle supports catalogs in liquibase terms
*
* @return false
*/
@Override
public boolean supportsSchemas() {
return false;
}
// //启动报错:select sys_context( 'userenv', 'current_schema' ) from dual
// @Override
// protected String getConnectionCatalogName() throws DatabaseException {
// if (getConnection() instanceof OfflineConnection) {
// return getConnection().getCatalog();
// }
// try {
// //noinspection HardCodedStringLiteral
// return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
// } catch (Exception e) {
// //noinspection HardCodedStringLiteral
// Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e);
// }
// return null;
// }
@Override
public String getDefaultCatalogName() {//NOPMD
return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
}
@Override
public String getDateLiteral(String isoDate) {
String normalLiteral = super.getDateLiteral(isoDate);
if (isDateOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
} else if (isTimeOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
} else if (isTimestamp(isoDate)) {
return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
} else if (isDateTime(isoDate)) {
int seppos = normalLiteral.lastIndexOf('.');
if (seppos != -1) {
normalLiteral = normalLiteral.substring(0, seppos) + "'";
}
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
}
return "UNSUPPORTED:" + isoDate;
}
@Override
public boolean isSystemObject(DatabaseObject example) {
if (example == null) {
return false;
}
if (this.isLiquibaseObject(example)) {
return false;
}
if (example instanceof Schema) {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
return true;
}
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
return true;
}
} else if (isSystemObject(example.getSchema())) {
return true;
}
if (example instanceof Catalog) {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) {
return true;
}
} else if (example.getName() != null) {
//noinspection HardCodedStringLiteral
if (example.getName().startsWith("BIN$")) { //oracle deleted table
boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
if (!filteredInOriginalQuery) {
filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
}
if (filteredInOriginalQuery) {
return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof
liquibase.statement.UniqueConstraint));
} else {
return true;
}
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("AQ$")) { //oracle AQ tables
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("DR$")) { //oracle index tables
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table
return true;
} else //noinspection HardCodedStringLiteral,HardCodedStringLiteral
if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
// CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("MLOG$_")) { //Created by materliaized view logs for every table that is part of a materialized view. Not available for DDL operations.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables.
return true;
} else //noinspection HardCodedStringLiteral
if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { //This table contains the name of the Java object, the date it was loaded, and has a BLOB column to store the Java object.
return true;
} else //noinspection HardCodedStringLiteral
if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("USLOG$")) { //for update materialized view
return true;
} else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables
return true;
}
}
return super.isSystemObject(example);
}
@Override
public boolean supportsAutoIncrement() {
// Oracle supports Identity beginning with version 12c
boolean isAutoIncrementSupported = false;
try {
if (getDatabaseMajorVersion() >= 12) {
isAutoIncrementSupported = true;
}
} catch (DatabaseException ex) {
isAutoIncrementSupported = false;
}
return isAutoIncrementSupported;
}
@Override
public boolean supportsRestrictForeignKeys() {
return false;
}
@Override
public int getDataTypeMaxParameters(String dataTypeName) {
//noinspection HardCodedStringLiteral
if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
return 0;
}
//noinspection HardCodedStringLiteral
if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) {
return 0;
}
return super.getDataTypeMaxParameters(dataTypeName);
}
public String getSystemTableWhereClause(String tableNameColumn) {
List<String> clauses = new ArrayList<String>(Arrays.asList("BIN$",
"AQ$",
"DR$",
"SYS_IOT_OVER",
"MLOG$_",
"RUPD$_",
"WM$_",
"ISEQ$$_",
"USLOG$",
"SYS_FBA"));
for (int i = 0;i<clauses.size(); i++) {
clauses.set(i, tableNameColumn+" NOT LIKE '"+clauses.get(i)+"%'");
}
return "("+ StringUtil.join(clauses, " AND ") + ")";
}
@Override
public boolean jdbcCallsCatalogsSchemas() {
return true;
}
public Set<String> getUserDefinedTypes() {
if (userDefinedTypes == null) {
userDefinedTypes = new HashSet<>();
if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) {
try {
try {
//noinspection HardCodedStringLiteral
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
} catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES
//noinspection HardCodedStringLiteral
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
}
} catch (DatabaseException e) {
//ignore error
}
}
}
return userDefinedTypes;
}
@Override
public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
//noinspection HardCodedStringLiteral
if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
return databaseFunction.toString();
}
if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof
SequenceCurrentValueFunction)) {
String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
// replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval
return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\"");
}
return super.generateDatabaseFunctionValue(databaseFunction);
}
@Override
public ValidationErrors validate() {
ValidationErrors errors = super.validate();
DatabaseConnection connection = getConnection();
if ((connection == null) || (connection instanceof OfflineConnection)) {
//noinspection HardCodedStringLiteral
Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database");
return errors;
}
if (!canAccessDbaRecycleBin()) {
errors.addWarning(getDbaRecycleBinWarning());
}
return errors;
}
public String getDbaRecycleBinWarning() {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,
// HardCodedStringLiteral
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " +
"constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " +
"referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" +
" issue.\n" +
"\n" +
"The user you used to connect to the database (" + getConnection().getConnectionUserName() +
") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " +
"Please run the following SQL to set the appropriate permissions, and try running the command again.\n" +
"\n" +
" GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";";
}
public boolean canAccessDbaRecycleBin() {
if (canAccessDbaRecycleBin == null) {
DatabaseConnection connection = getConnection();
if ((connection == null) || (connection instanceof OfflineConnection)) {
return false;
}
Statement statement = null;
try {
statement = ((JdbcConnection) connection).createStatement();
@SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
resultSet.close(); //don't need to do anything with the result set, just make sure statement ran.
this.canAccessDbaRecycleBin = true;
} catch (Exception e) {
//noinspection HardCodedStringLiteral
if ((e instanceof SQLException) && e.getMessage().startsWith("ORA-00942")) { //ORA-00942: table or view does not exist
this.canAccessDbaRecycleBin = false;
} else {
//noinspection HardCodedStringLiteral
Scope.getCurrentScope().getLog(getClass()).warning("Cannot check dba_recyclebin access", e);
this.canAccessDbaRecycleBin = false;
}
} finally {
JdbcUtils.close(null, statement);
}
}
return canAccessDbaRecycleBin;
}
@Override
public boolean supportsNotNullConstraintNames() {
return true;
}
public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
if ((identifier == null) || (identifier.length() < 1)) {
return false;
}
if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$")) {
return false;
}
return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH);
}
public int getIdentifierMaximumLength() {
try {
if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) {
return SHORT_IDENTIFIERS_LENGTH;
} else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) {
return SHORT_IDENTIFIERS_LENGTH;
} else {
return LONG_IDENTIFIERS_LEGNTH;
}
} catch (DatabaseException ex) {
throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
}
}
}
@@ -0,0 +1,523 @@
package liquibase.database.core;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import dm.jdbc.driver.DmdbConnection;
import liquibase.CatalogAndSchema;
import liquibase.GlobalConfiguration;
import liquibase.Scope;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.DatabaseConnection;
import liquibase.database.OfflineConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.DatabaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.exception.ValidationErrors;
import liquibase.executor.ExecutorService;
import liquibase.statement.DatabaseFunction;
import liquibase.statement.SequenceCurrentValueFunction;
import liquibase.statement.SequenceNextValueFunction;
import liquibase.statement.UniqueConstraint;
import liquibase.statement.core.RawCallStatement;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Catalog;
import liquibase.structure.core.Index;
import liquibase.structure.core.PrimaryKey;
import liquibase.structure.core.Schema;
import liquibase.util.JdbcUtil;
import liquibase.util.StringUtil;
public class OracleDatabase extends AbstractJdbcDatabase {
public static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
public static final String PRODUCT_NAME = "oracle";
private static ResourceBundle coreBundle = ResourceBundle.getBundle("liquibase/i18n/liquibase-core");
protected final int SHORT_IDENTIFIERS_LENGTH = 30;
protected final int LONG_IDENTIFIERS_LEGNTH = 128;
public static final int ORACLE_12C_MAJOR_VERSION = 12;
private Set<String> reservedWords = new HashSet();
private Set<String> userDefinedTypes;
private Map<String, String> savedSessionNlsSettings;
private Boolean canAccessDbaRecycleBin;
private Integer databaseMajorVersion;
private Integer databaseMinorVersion;
public OracleDatabase() {
super.unquotedObjectsAreUppercased = true;
super.setCurrentDateTimeFunction("SYSTIMESTAMP");
this.dateFunctions.add(new DatabaseFunction("SYSDATE"));
this.dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
this.dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
super.sequenceNextValueFunction = "%s.nextval";
super.sequenceCurrentValueFunction = "%s.currval";
}
public int getPriority() {
return 1;
}
private void tryProxySession(String url, Connection con) {
Matcher m = PROXY_USER.matcher(url);
if (m.matches()) {
Properties props = new Properties();
props.put("PROXY_USER_NAME", m.group(1));
Method method;
try {
method = con.getClass().getMethod("openProxySession", Integer.TYPE, Properties.class);
method.setAccessible(true);
method.invoke(con, 1, props);
} catch (Exception var8) {
Scope.getCurrentScope().getLog(this.getClass()).info("Could not open proxy session on OracleDatabase: " + var8.getCause().getMessage());
return;
}
try {
method = con.getClass().getMethod("isProxySession");
method.setAccessible(true);
boolean b = (Boolean)method.invoke(con);
if (!b) {
Scope.getCurrentScope().getLog(this.getClass()).info("Proxy session not established on OracleDatabase: ");
}
} catch (Exception var7) {
Scope.getCurrentScope().getLog(this.getClass()).info("Could not open proxy session on OracleDatabase: " + var7.getCause().getMessage());
}
}
}
public void setConnection(DatabaseConnection conn) {
this.reservedWords.addAll(Arrays.asList("GROUP", "USER", "SESSION", "PASSWORD", "RESOURCE", "START", "SIZE", "UID", "DESC", "ORDER"));
Connection sqlConn = null;
if (!(conn instanceof OfflineConnection)) {
try {
if (conn instanceof JdbcConnection) {
sqlConn = ((JdbcConnection)conn).getWrappedConnection();
}
} catch (Exception var29) {
throw new UnexpectedLiquibaseException(var29);
}
if (sqlConn != null) {
this.tryProxySession(conn.getURL(), sqlConn);
try {
this.reservedWords.addAll(Arrays.asList(sqlConn.getMetaData().getSQLKeywords().toUpperCase().split(",\\s*")));
} catch (SQLException var28) {
Scope.getCurrentScope().getLog(this.getClass()).info("Could get sql keywords on OracleDatabase: " + var28.getMessage());
}
try {
Method method = sqlConn.getClass().getMethod("setRemarksReporting", Boolean.TYPE);
method.setAccessible(true);
method.invoke(sqlConn, true);
} catch (Exception var27) {
Scope.getCurrentScope().getLog(this.getClass()).info("Could not set remarks reporting on OracleDatabase: " + var27.getMessage());
}
CallableStatement statement = null;
try {
DatabaseMetaData metaData = sqlConn.getMetaData();
Connection connection = metaData.getConnection();
if (connection instanceof DmdbConnection) {
String compatibleVersion = "11.2.0.4.0";
Matcher majorVersionMatcher = Pattern.compile("(\\d+)\\.(\\d+)\\..*").matcher(compatibleVersion);
if (majorVersionMatcher.matches()) {
this.databaseMajorVersion = Integer.valueOf(majorVersionMatcher.group(1));
this.databaseMinorVersion = Integer.valueOf(majorVersionMatcher.group(2));
}
}else{
//noinspection HardCodedStringLiteral
statement = sqlConn.prepareCall("{call DBMS_UTILITY.DB_VERSION(?,?)}");
statement.registerOutParameter(1, Types.VARCHAR);
statement.registerOutParameter(2, Types.VARCHAR);
statement.execute();
String compatibleVersion = statement.getString(2); //"11.2.0.4.0";
if (compatibleVersion != null) {
Matcher majorVersionMatcher = Pattern.compile("(\\d+)\\.(\\d+)\\..*").matcher(compatibleVersion);
if (majorVersionMatcher.matches()) {
this.databaseMajorVersion = Integer.valueOf(majorVersionMatcher.group(1));
this.databaseMinorVersion = Integer.valueOf(majorVersionMatcher.group(2));
}
}
}
} catch (SQLException e) {
@SuppressWarnings("HardCodedStringLiteral") String message = "Cannot read from DBMS_UTILITY.DB_VERSION: " + e.getMessage();
//noinspection HardCodedStringLiteral
Scope.getCurrentScope().getLog(getClass()).info("Could not set check compatibility mode on OracleDatabase, assuming not running in any sort of compatibility mode: " + message);
} finally {
JdbcUtil.closeStatement(statement);
}
}
}
super.setConnection(conn);
}
public String getShortName() {
return "oracle";
}
protected String getDefaultDatabaseProductName() {
return "Oracle";
}
public int getDatabaseMajorVersion() throws DatabaseException {
return this.databaseMajorVersion == null ? super.getDatabaseMajorVersion() : this.databaseMajorVersion;
}
public int getDatabaseMinorVersion() throws DatabaseException {
return this.databaseMinorVersion == null ? super.getDatabaseMinorVersion() : this.databaseMinorVersion;
}
public Integer getDefaultPort() {
return 1521;
}
public String getJdbcCatalogName(CatalogAndSchema schema) {
return null;
}
public String getJdbcSchemaName(CatalogAndSchema schema) {
return this.correctObjectName(schema.getCatalogName() == null ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
}
protected String getAutoIncrementClause(String generationType, Boolean defaultOnNull) {
if (StringUtil.isEmpty(generationType)) {
return super.getAutoIncrementClause();
} else {
String autoIncrementClause = "GENERATED %s AS IDENTITY";
String generationStrategy = generationType;
if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
generationStrategy = generationType + " ON NULL";
}
return String.format(autoIncrementClause, generationStrategy);
}
}
public String generatePrimaryKeyName(String tableName) {
return tableName.length() > 27 ? "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27) : "PK_" + tableName.toUpperCase(Locale.US);
}
public boolean supportsInitiallyDeferrableColumns() {
return true;
}
public boolean isReservedWord(String objectName) {
return this.reservedWords.contains(objectName.toUpperCase());
}
public boolean supportsSequences() {
return true;
}
public boolean supportsSchemas() {
return false;
}
protected String getConnectionCatalogName() throws DatabaseException {
if (this.getConnection() instanceof OfflineConnection) {
return this.getConnection().getCatalog();
} else if (!(this.getConnection() instanceof JdbcConnection)) {
return this.defaultCatalogName;
} else {
try {
return (String)((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
} catch (Exception var2) {
Scope.getCurrentScope().getLog(this.getClass()).info("Error getting default schema", var2);
return null;
}
}
}
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
return "oracle".equalsIgnoreCase(conn.getDatabaseProductName());
}
public String getDefaultDriver(String url) {
return url.startsWith("jdbc:oracle") ? "oracle.jdbc.OracleDriver" : null;
}
public String getDefaultCatalogName() {
return super.getDefaultCatalogName() == null ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
}
public String getDateLiteral(String isoDate) {
String normalLiteral = super.getDateLiteral(isoDate);
if (this.isDateOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
} else if (this.isTimeOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
} else if (this.isTimestamp(isoDate)) {
return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
} else if (this.isDateTime(isoDate)) {
int seppos = normalLiteral.lastIndexOf(46);
if (seppos != -1) {
normalLiteral = normalLiteral.substring(0, seppos) + "'";
}
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
} else {
return "UNSUPPORTED:" + isoDate;
}
}
public boolean isSystemObject(DatabaseObject example) {
if (example == null) {
return false;
} else if (this.isLiquibaseObject(example)) {
return false;
} else {
if (example instanceof Schema) {
label131: {
if (!"SYSTEM".equals(example.getName()) && !"SYS".equals(example.getName()) && !"CTXSYS".equals(example.getName()) && !"XDB".equals(example.getName())) {
if (!"SYSTEM".equals(example.getSchema().getCatalogName()) && !"SYS".equals(example.getSchema().getCatalogName()) && !"CTXSYS".equals(example.getSchema().getCatalogName()) && !"XDB".equals(example.getSchema().getCatalogName())) {
break label131;
}
return true;
}
return true;
}
} else if (this.isSystemObject(example.getSchema())) {
return true;
}
if (example instanceof Catalog) {
if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
return true;
}
} else if (example.getName() != null) {
if (example.getName().startsWith("BIN$")) {
boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
if (!filteredInOriginalQuery) {
filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
}
if (!filteredInOriginalQuery) {
return true;
}
return !(example instanceof PrimaryKey) && !(example instanceof Index) && !(example instanceof UniqueConstraint);
}
if (example.getName().startsWith("AQ$")) {
return true;
}
if (example.getName().startsWith("DR$")) {
return true;
}
if (example.getName().startsWith("SYS_IOT_OVER")) {
return true;
}
if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
return true;
}
if (example.getName().startsWith("MLOG$_")) {
return true;
}
if (example.getName().startsWith("RUPD$_")) {
return true;
}
if (example.getName().startsWith("WM$_")) {
return true;
}
if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) {
return true;
}
if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) {
return true;
}
if (example.getName().startsWith("ISEQ$$_")) {
return true;
}
if (example.getName().startsWith("USLOG$")) {
return true;
}
if (example.getName().startsWith("SYS_FBA")) {
return true;
}
}
return super.isSystemObject(example);
}
}
public boolean supportsTablespaces() {
return true;
}
public boolean supportsAutoIncrement() {
boolean isAutoIncrementSupported = false;
try {
if (this.getDatabaseMajorVersion() >= 12) {
isAutoIncrementSupported = true;
}
} catch (DatabaseException var3) {
isAutoIncrementSupported = false;
}
return isAutoIncrementSupported;
}
public boolean supportsRestrictForeignKeys() {
return false;
}
public int getDataTypeMaxParameters(String dataTypeName) {
if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
return 0;
} else {
return "BINARY_DOUBLE".equals(dataTypeName.toUpperCase()) ? 0 : super.getDataTypeMaxParameters(dataTypeName);
}
}
public String getSystemTableWhereClause(String tableNameColumn) {
List<String> clauses = new ArrayList(Arrays.asList("BIN$", "AQ$", "DR$", "SYS_IOT_OVER", "MLOG$_", "RUPD$_", "WM$_", "ISEQ$$_", "USLOG$", "SYS_FBA"));
for(int i = 0; i < clauses.size(); ++i) {
clauses.set(i, tableNameColumn + " NOT LIKE '" + (String)clauses.get(i) + "%'");
}
return "(" + StringUtil.join(clauses, " AND ") + ")";
}
public boolean jdbcCallsCatalogsSchemas() {
return true;
}
public Set<String> getUserDefinedTypes() {
if (this.userDefinedTypes == null) {
this.userDefinedTypes = new HashSet();
if (this.getConnection() != null && !(this.getConnection() instanceof OfflineConnection)) {
try {
try {
this.userDefinedTypes.addAll(((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
} catch (DatabaseException var2) {
this.userDefinedTypes.addAll(((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
}
} catch (DatabaseException var3) {
}
}
}
return this.userDefinedTypes;
}
public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
if (databaseFunction != null && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
return databaseFunction.toString();
} else if (!(databaseFunction instanceof SequenceNextValueFunction) && !(databaseFunction instanceof SequenceCurrentValueFunction)) {
return super.generateDatabaseFunctionValue(databaseFunction);
} else {
String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
return quotedSeq.replaceFirst("\"([^.\"]+)\\.([^.\"]+)\"", "\"$1\".\"$2\"");
}
}
public ValidationErrors validate() {
ValidationErrors errors = super.validate();
DatabaseConnection connection = this.getConnection();
if (connection != null && !(connection instanceof OfflineConnection)) {
if (!this.canAccessDbaRecycleBin()) {
errors.addWarning(this.getDbaRecycleBinWarning());
}
return errors;
} else {
Scope.getCurrentScope().getLog(this.getClass()).info("Cannot validate offline database");
return errors;
}
}
public String getDbaRecycleBinWarning() {
return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where constraints are deleted and restored. Since Oracle doesn't properly restore the original table names referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this issue.\n\nThe user you used to connect to the database (" + this.getConnection().getConnectionUserName() + ") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. Please run the following SQL to set the appropriate permissions, and try running the command again.\n\n GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + this.getConnection().getConnectionUserName() + ";";
}
public boolean canAccessDbaRecycleBin() {
if (this.canAccessDbaRecycleBin == null) {
DatabaseConnection connection = this.getConnection();
if (connection == null || connection instanceof OfflineConnection) {
return false;
}
Statement statement = null;
try {
statement = ((JdbcConnection)connection).createStatement();
ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
resultSet.close();
this.canAccessDbaRecycleBin = true;
} catch (Exception var7) {
if (var7 instanceof SQLException && var7.getMessage().startsWith("ORA-00942")) {
this.canAccessDbaRecycleBin = false;
} else {
Scope.getCurrentScope().getLog(this.getClass()).warning("Cannot check dba_recyclebin access", var7);
this.canAccessDbaRecycleBin = false;
}
} finally {
JdbcUtil.close((ResultSet)null, statement);
}
}
return this.canAccessDbaRecycleBin;
}
public boolean supportsNotNullConstraintNames() {
return true;
}
public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
if (identifier != null && identifier.length() >= 1) {
if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$")) {
return false;
} else {
return identifier.length() <= 128;
}
} else {
return false;
}
}
public int getIdentifierMaximumLength() {
try {
if (this.getDatabaseMajorVersion() < 12) {
return 30;
} else {
return this.getDatabaseMajorVersion() == 12 && this.getDatabaseMinorVersion() <= 1 ? 30 : 128;
}
} catch (DatabaseException var2) {
throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", var2);
}
}
}
@@ -0,0 +1,151 @@
package liquibase.datatype.core;
import java.util.Locale;
import liquibase.change.core.LoadDataChange;
import liquibase.change.core.LoadDataChange.LOAD_DATA_TYPE;
import liquibase.database.Database;
import liquibase.database.core.*;
import liquibase.datatype.DataTypeInfo;
import liquibase.datatype.DatabaseDataType;
import liquibase.datatype.LiquibaseDataType;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.statement.DatabaseFunction;
import liquibase.util.StringUtil;
@DataTypeInfo(
name = "boolean",
aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"},
minParameters = 0,
maxParameters = 0,
priority = 1
)
public class BooleanType extends LiquibaseDataType {
public BooleanType() {
}
@Override
public DatabaseDataType toDatabaseDataType(Database database) {
String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
if ((database instanceof Firebird3Database)) {
return new DatabaseDataType("BOOLEAN");
}
if ((database instanceof AbstractDb2Database) || (database instanceof FirebirdDatabase)) {
return new DatabaseDataType("SMALLINT");
} else if (database instanceof MSSQLDatabase) {
return new DatabaseDataType(database.escapeDataTypeName("bit"));
} else if (database instanceof MySQLDatabase) {
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
return new DatabaseDataType("BIT", getParameters());
}
return new DatabaseDataType("BIT", 1);
} else if (database instanceof OracleDatabase) {
return new DatabaseDataType("NUMBER", 1);
} else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
return new DatabaseDataType("BIT");
} else if (database instanceof DerbyDatabase) {
if (((DerbyDatabase) database).supportsBooleanDataType()) {
return new DatabaseDataType("BOOLEAN");
} else {
return new DatabaseDataType("SMALLINT");
}
} else if (database.getClass().isAssignableFrom(DB2Database.class)) {
if (((DB2Database) database).supportsBooleanDataType()) {
return new DatabaseDataType("BOOLEAN");
} else {
return new DatabaseDataType("SMALLINT");
}
} else if (database instanceof HsqlDatabase) {
return new DatabaseDataType("BOOLEAN");
} else if (database instanceof PostgresDatabase) {
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
return new DatabaseDataType("BIT", getParameters());
}
} else if(database instanceof KingbaseDatabase) {
return new DatabaseDataType("bit");
}
return super.toDatabaseDataType(database);
}
@Override
public String objectToSql(Object value, Database database) {
if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
return null;
}
String returnValue;
if (value instanceof String) {
value = ((String) value).replaceAll("'", "");
if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) {
returnValue = this.getTrueBooleanValue(database);
} else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
returnValue = this.getFalseBooleanValue(database);
} else {
throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
}
} else if (value instanceof Long) {
if (Long.valueOf(1).equals(value)) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else if (value instanceof Number) {
if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else if (value instanceof DatabaseFunction) {
return value.toString();
} else if (value instanceof Boolean) {
if (((Boolean) value)) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else {
throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
}
return returnValue;
}
protected boolean isNumericBoolean(Database database) {
if (database instanceof DerbyDatabase) {
return !((DerbyDatabase) database).supportsBooleanDataType();
} else if (database.getClass().isAssignableFrom(DB2Database.class)) {
return !((DB2Database) database).supportsBooleanDataType();
}
return (database instanceof Db2zDatabase) || (database instanceof DB2Database) || (database instanceof FirebirdDatabase) || (database instanceof
MSSQLDatabase) || (database instanceof MySQLDatabase) || (database instanceof OracleDatabase) ||
(database instanceof SQLiteDatabase) || (database instanceof SybaseASADatabase) || (database instanceof
SybaseDatabase) || (database instanceof KingbaseDatabase);
}
public String getFalseBooleanValue(Database database) {
if (isNumericBoolean(database)) {
return "0";
}
if (database instanceof InformixDatabase) {
return "'f'";
}
return "FALSE";
}
public String getTrueBooleanValue(Database database) {
if (isNumericBoolean(database)) {
return "1";
}
if (database instanceof InformixDatabase) {
return "'t'";
}
return "TRUE";
}
@Override
public LOAD_DATA_TYPE getLoadTypeName() {
return LOAD_DATA_TYPE.BOOLEAN;
}
}
@@ -0,0 +1,209 @@
package liquibase.datatype.core;
import liquibase.Scope;
import liquibase.change.core.LoadDataChange.LOAD_DATA_TYPE;
import liquibase.database.Database;
import liquibase.database.core.*;
import liquibase.datatype.DataTypeInfo;
import liquibase.datatype.DatabaseDataType;
import liquibase.datatype.LiquibaseDataType;
import liquibase.statement.DatabaseFunction;
import liquibase.util.StringUtil;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
@DataTypeInfo(
name = "datetime",
minParameters = 0,
maxParameters = 1,
aliases = {"java.sql.Types.DATETIME", "java.util.Date", "smalldatetime", "datetime2"},
priority = 1
)
public class DateTimeType extends LiquibaseDataType {
protected static final String SQL_DATETYPE_TIMESTAMP = "TIMESTAMP";
public DateTimeType() {
}
@Override
public DatabaseDataType toDatabaseDataType(Database database) {
String originalDefinition = StringUtil.trimToEmpty(this.getRawDefinition());
if (!(database instanceof DerbyDatabase) && !(database instanceof FirebirdDatabase) && !(database instanceof H2Database) && !(database instanceof HsqlDatabase)) {
if (database instanceof AbstractDb2Database) {
return new DatabaseDataType("TIMESTAMP", this.getParameters());
} else if (database instanceof OracleDatabase) {
return originalDefinition.toUpperCase(Locale.US).contains("TIME ZONE") ? new DatabaseDataType(originalDefinition.replaceFirst("\\(\\d+\\)$", "")) : new DatabaseDataType("TIMESTAMP", this.getParameters());
} else if (database instanceof MSSQLDatabase) {
Object[] parameters = this.getParameters();
if (originalDefinition.matches("(?i)^\\[?smalldatetime.*")) {
return new DatabaseDataType(database.escapeDataTypeName("smalldatetime"));
} else if (!"datetime2".equals(originalDefinition.toLowerCase(Locale.US)) && !"[datetime2]".equals(originalDefinition.toLowerCase(Locale.US)) && !originalDefinition.toLowerCase(Locale.US).matches("(?i)\\[?datetime2\\]?\\s*\\(.+")) {
return new DatabaseDataType(database.escapeDataTypeName("datetime"));
} else {
if (parameters.length >= 1 && Integer.parseInt(parameters[0].toString()) == database.getDefaultScaleForNativeDataType("datetime2")) {
parameters = new Object[0];
}
return new DatabaseDataType(database.escapeDataTypeName("datetime2"), parameters);
}
} else {
String parameter;
if (!(database instanceof InformixDatabase)) {
Object[] params;
if (database instanceof PostgresDatabase || database instanceof KingbaseDatabase) {
parameter = originalDefinition.toLowerCase(Locale.US);
params = this.getParameters();
Object param;
if (!parameter.contains("tz") && !parameter.contains("with time zone")) {
if (params.length == 0) {
return new DatabaseDataType("TIMESTAMP WITHOUT TIME ZONE");
} else {
param = params[0];
if (params.length == 2) {
param = params[1];
}
return new DatabaseDataType("TIMESTAMP(" + param + ") WITHOUT TIME ZONE");
}
} else if (params.length == 0) {
return new DatabaseDataType("TIMESTAMP WITH TIME ZONE");
} else {
param = params[0];
if (params.length == 2) {
param = params[1];
}
return new DatabaseDataType("TIMESTAMP(" + param + ") WITH TIME ZONE");
}
} else if (database instanceof SQLiteDatabase) {
return new DatabaseDataType("TEXT");
} else {
int maxFractionalDigits = database.getMaxFractionalDigitsForTimestamp();
if (database instanceof MySQLDatabase) {
if (this.getParameters().length != 0 && maxFractionalDigits != 0) {
params = this.getParameters();
Integer precision = Integer.valueOf(params[0].toString());
if (precision > 6) {
Scope.getCurrentScope().getLog(this.getClass()).warning("MySQL does not support a timestamp precision of '" + precision + "' - resetting to the maximum of '6'");
params = new Object[]{6};
}
return new DatabaseDataType(this.getName(), params);
} else {
return new DatabaseDataType(this.getName());
}
} else {
return new DatabaseDataType(this.getName());
}
}
} else {
if ((this.getAdditionalInformation() == null || this.getAdditionalInformation().isEmpty()) && this.getParameters() != null && this.getParameters().length > 0) {
parameter = String.valueOf(this.getParameters()[0]);
if ("4365".equals(parameter)) {
return new DatabaseDataType("DATETIME YEAR TO FRACTION(3)");
}
if ("3594".equals(parameter)) {
return new DatabaseDataType("DATETIME YEAR TO SECOND");
}
if ("3080".equals(parameter)) {
return new DatabaseDataType("DATETIME YEAR TO MINUTE");
}
if ("2052".equals(parameter)) {
return new DatabaseDataType("DATETIME YEAR TO DAY");
}
}
return this.getAdditionalInformation() != null && !this.getAdditionalInformation().isEmpty() ? new DatabaseDataType(originalDefinition) : new DatabaseDataType("DATETIME YEAR TO FRACTION", new Object[]{5});
}
}
} else {
return new DatabaseDataType("TIMESTAMP");
}
}
public String objectToSql(Object value, Database database) {
if (value != null && !"null".equals(value.toString().toLowerCase(Locale.US))) {
if (value instanceof DatabaseFunction) {
return database.generateDatabaseFunctionValue((DatabaseFunction)value);
} else if (database.isFunction(value.toString())) {
return value.toString();
} else {
return value instanceof String ? "'" + ((String)value).replaceAll("'", "''") + "'" : database.getDateTimeLiteral((Timestamp)value);
}
} else {
return null;
}
}
public Object sqlToObject(String value, Database database) {
if (this.zeroTime(value)) {
return value;
} else if (database instanceof AbstractDb2Database) {
return value.replaceFirst("^\"SYSIBM\".\"TIMESTAMP\"\\('", "").replaceFirst("'\\)", "");
} else if (database instanceof DerbyDatabase) {
return value.replaceFirst("^TIMESTAMP\\('", "").replaceFirst("'\\)", "");
} else {
try {
DateFormat dateTimeFormat = this.getDateTimeFormat(database);
if (database instanceof OracleDatabase && value.matches("to_date\\('\\d+\\-\\d+\\-\\d+ \\d+:\\d+:\\d+', 'YYYY\\-MM\\-DD HH24:MI:SS'\\)")) {
dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:s");
value = value.replaceFirst(".*?'", "").replaceFirst("',.*", "");
}
if (database instanceof HsqlDatabase && value.matches("TIMESTAMP'\\d+\\-\\d+\\-\\d+ \\d+:\\d+:\\d+(?:\\.\\d+)?'")) {
dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:s.S");
value = value.replaceFirst(".*?'", "").replaceFirst("',.*", "");
}
return new Timestamp(((DateFormat)dateTimeFormat).parse(value).getTime());
} catch (ParseException var12) {
String[] genericFormats = new String[]{"yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'HH:mm:ss"};
String shortenedValue = value.replaceFirst("(\\.\\d{3})\\d+", "$1");
String[] var6 = genericFormats;
int var7 = genericFormats.length;
int var8 = 0;
while(var8 < var7) {
String format = var6[var8];
try {
return new Timestamp((new SimpleDateFormat(format)).parse(shortenedValue).getTime());
} catch (ParseException var11) {
++var8;
}
}
if (!value.contains("/") && !value.contains("-")) {
return new DatabaseFunction(value);
} else {
return value;
}
}
}
}
public LOAD_DATA_TYPE getLoadTypeName() {
return LOAD_DATA_TYPE.DATE;
}
private boolean zeroTime(String stringVal) {
return "".equals(stringVal.replace("-", "").replace(":", "").replace(" ", "").replace("0", ""));
}
protected DateFormat getDateTimeFormat(Database database) {
if (database instanceof MySQLDatabase) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
} else if (database instanceof MSSQLDatabase) {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
} else {
return database instanceof AbstractDb2Database ? new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss.SSS") : new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
}
}
}
@@ -0,0 +1,38 @@
//package org.jeecg;
//
//import org.jeecg.common.base.BaseMap;
//import org.jeecg.common.constant.GlobalConstants;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.boot.CommandLineRunner;
//import org.springframework.boot.SpringApplication;
//import org.springframework.boot.autoconfigure.SpringBootApplication;
//import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
//import org.springframework.cloud.openfeign.EnableFeignClients;
//import org.springframework.data.redis.core.RedisTemplate;
//import org.springframework.scheduling.annotation.EnableScheduling;
//
//@SpringBootApplication
//@EnableFeignClients(basePackages = {"org.jeecg"})
//public class JeecgJoaApplication implements CommandLineRunner {
// @Autowired
// private RedisTemplate<String, Object> redisTemplate;
//
// public static void main(String[] args) {
// SpringApplication.run(JeecgJoaApplication.class, args);
// }
//
// /**
// * 启动的时候,触发下gateway网关刷新
// * <p>
// * 解决: 先启动gateway后启动服务,Swagger接口文档访问不通的问题
// *
// * @param args
// */
// @Override
// public void run(String... args) {
// BaseMap params = new BaseMap();
// params.put(GlobalConstants.HANDLER_NAME, GlobalConstants.LODER_ROUDER_HANDLER);
// //刷新网关
// redisTemplate.convertAndSend(GlobalConstants.REDIS_TOPIC_NAME, params);
// }
//}
@@ -0,0 +1,217 @@
package org.jeecg.config.extend;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.dto.LogDTO;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.base.service.BaseCommonService;
import org.jeecg.modules.drag.service.IOnlDragExternalService;
import org.jeecg.modules.drag.vo.DragDictModel;
import org.jeecg.modules.drag.vo.DragLogDTO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Description: 仪表盘扩展实现类,用于字典翻译
* @Author: lsq
* @Date:2023-01-09
* @Version:V1.0
*/
@Slf4j
@Service("jimuDragExternalServiceImpl")
public class JimuDragExternalServiceImpl implements IOnlDragExternalService {
@Autowired
@Lazy
private BaseCommonService baseCommonService;
@Autowired
@Lazy
private ISysBaseAPI sysBaseApi;
/**
* 根据多个字典code查询多个字典项
* @param codeList
* @return key = dictCode value=对应的字典项
*/
@Override
public Map<String, List<DragDictModel>> getManyDictItems(List<String> codeList, List<JSONObject> tableDictList) {
Map<String, List<DragDictModel>> manyDragDictItems = new HashMap<>();
if(!CollectionUtils.isEmpty(codeList)){
Map<String, List<DictModel>> dictItemsMap = sysBaseApi.getManyDictItems(codeList);
dictItemsMap.forEach((k,v)->{
List<DragDictModel> dictItems = new ArrayList<>();
v.forEach(dictItem->{
DragDictModel dictModel = new DragDictModel();
BeanUtils.copyProperties(dictItem,dictModel);
dictItems.add(dictModel);
});
manyDragDictItems.put(k,dictItems);
});
}
if(!CollectionUtils.isEmpty(tableDictList)){
tableDictList.forEach(item->{
List<DragDictModel> dictItems = new ArrayList<>();
JSONObject object = JSONObject.parseObject(item.toString());
String dictField = object.getString("dictField");
String dictTable = object.getString("dictTable");
String dictText = object.getString("dictText");
String fieldName = object.getString("fieldName");
List<DictModel> dictItemsList = sysBaseApi.queryTableDictItemsByCode(dictTable,dictText,dictField);
dictItemsList.forEach(dictItem->{
DragDictModel dictModel = new DragDictModel();
BeanUtils.copyProperties(dictItem,dictModel);
dictItems.add(dictModel);
});
manyDragDictItems.put(fieldName,dictItems);
});
}
return manyDragDictItems;
}
/**
*
* @param dictCode
* @return
*/
@Override
public List<DragDictModel> getDictItems(String dictCode) {
List<DragDictModel> dictItems = new ArrayList<>();
if(oConvertUtils.isNotEmpty(dictCode)){
List<DictModel> dictItemsList = sysBaseApi.getDictItems(dictCode);
dictItemsList.forEach(dictItem->{
DragDictModel dictModel = new DragDictModel();
BeanUtils.copyProperties(dictItem,dictModel);
dictItems.add(dictModel);
});
}
return dictItems;
}
/**
* 添加日志
* @param dragLogDTO
*/
@Override
public void addLog(DragLogDTO dragLogDTO) {
if(oConvertUtils.isNotEmpty(dragLogDTO)){
LogDTO dto = new LogDTO();
BeanUtils.copyProperties(dragLogDTO,dto);
baseCommonService.addLog(dto);
}
}
/**
*获取表字典下拉配置
* @param dictTable
* @param dictText
* @param dictCode
* @return
*/
@Override
public List<DragDictModel> getTableDictItems(String dictTable, String dictText, String dictCode) {
List<DragDictModel> dictItems = new ArrayList<>();
if(oConvertUtils.isNotEmpty(dictCode)){
List<DictModel> dictItemsList = sysBaseApi.queryTableDictItemsByCode(dictTable,dictText,dictCode);
dictItemsList.forEach(dictItem->{
DragDictModel dictModel = new DragDictModel();
BeanUtils.copyProperties(dictItem,dictModel);
dictItems.add(dictModel);
});
}
return dictItems;
}
/**
* 获取分类字典下拉配置
* @param ids
* @return
*/
@Override
public List<DragDictModel> getCategoryTreeDictItems(List<String> ids) {
List<DragDictModel> dictItems = new ArrayList<>();
if(oConvertUtils.isNotEmpty(ids)){
//ids转字符串
String idsStr = String.join(",", ids);
if (oConvertUtils.isNotEmpty(idsStr)) {
List<String> dictItemsList = sysBaseApi.loadCategoryDictItem(idsStr);
for (int i = 0; i < dictItemsList.size(); i++) {
DragDictModel dictModel = new DragDictModel();
String text = dictItemsList.get(i);
String value = ids.get(i);
dictModel.setText(text);
dictModel.setValue(value);
dictItems.add(dictModel);
}
}
}
return dictItems;
}
/**
* 获取用户翻译配置
* @param ids
* @return
*/
@Override
public List<DragDictModel> getUserDictItems(List<String> ids) {
List<DragDictModel> dictItems = new ArrayList<>();
if(oConvertUtils.isNotEmpty(ids)){
//ids转字符串
String idsStr = String.join(",", ids);
if (oConvertUtils.isNotEmpty(idsStr)) {
List<JSONObject> dictItemsList = sysBaseApi.queryUsersByUsernames(idsStr);
for (int i = 0; i < dictItemsList.size(); i++) {
JSONObject user = dictItemsList.get(i);
DragDictModel dictModel = new DragDictModel();
dictModel.setText(user.getString("realname"));
dictModel.setValue(user.getString("username"));
dictItems.add(dictModel);
}
}
}
return dictItems;
}
/**
* 获取部门翻译配置
* @param ids
* @return
*/
@Override
public List<DragDictModel> getDeptsDictItems(List<String> ids) {
List<DragDictModel> dictItems = new ArrayList<>();
if(oConvertUtils.isNotEmpty(ids)){
//ids转字符串
String idsStr = String.join(",", ids);
if (oConvertUtils.isNotEmpty(idsStr)) {
List<JSONObject> dictItemsList = sysBaseApi.queryDepartsByIds(idsStr);
for (int i = 0; i < dictItemsList.size(); i++) {
JSONObject user = dictItemsList.get(i);
DragDictModel dictModel = new DragDictModel();
dictModel.setText(user.getString("departName"));
dictModel.setValue(user.getString("id"));
dictItems.add(dictModel);
}
}
}
return dictItems;
}
/**
* 保存日志
* @param logMsg
* @param logType
* @param operateType
*/
@Override
public void addLog(String logMsg, int logType, int operateType) {
baseCommonService.addLog(logMsg,logType,operateType);
}
}
@@ -0,0 +1,194 @@
package org.jeecg.modules.biz.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtil;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.SqlInjectionUtil;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.biz.entity.ExtBizLeave;
import org.jeecg.modules.biz.service.IExtBizLeaveService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
/**
* @Title: Controller
* @Description: 请假单表
* @author jeecg-boot
* @date 2019-03-26
* @version V1.0
*
* NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=
*/
@RestController
@RequestMapping("/joa/biz/extBizLeave")
@Slf4j
public class ExtBizLeaveController {
@Autowired
private IExtBizLeaveService extBizLeaveService;
/**
* 分页列表查询
* @param extBizLeave
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<ExtBizLeave>> queryPageList(ExtBizLeave extBizLeave,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<IPage<ExtBizLeave>> result = new Result<IPage<ExtBizLeave>>();
QueryWrapper<ExtBizLeave> queryWrapper = new QueryWrapper<ExtBizLeave>(extBizLeave);
Page<ExtBizLeave> page = new Page<ExtBizLeave>(pageNo,pageSize);
//排序逻辑 处理
String column = req.getParameter("column");
String order = req.getParameter("order");
if(StringUtil.isNotEmpty(column) && org.apache.commons.lang.StringUtil.isNotEmpty(order)) {
if("asc".equals(order)) {
queryWrapper.orderByAsc(SqlInjectionUtil.getSqlInjectSortField(column));
}else {
queryWrapper.orderByDesc(SqlInjectionUtil.getSqlInjectSortField(column));
}
}
//--------------------------我的审批任务过滤---开始-------------------------------
String userid = JwtUtil.getUserNameByToken(req); // 获取当前登录用户
String bizTaskType = req.getParameter("bizTaskType");//业务类型1待我审批2我发起的申请
//update-begin--author:scott--date:20220118--forJTC-865 业务办理下,批量申请(自定义)待我审批列表SQL报错-----
if("1".equals(bizTaskType)) {
List<String> formDataIds = this.extBizLeaveService.findRunningTask(userid,"TEST001");
if(formDataIds!=null && formDataIds.size()>0){
queryWrapper.in("id",formDataIds);
}else{
result.setSuccess(true);
return result;
}
}else {
queryWrapper.eq("create_by",userid);
}
//JoaUtil.filterRunningTask(bizTaskType,queryWrapper,userid, "TEST001");
//JoaUtil.filterRunningTask(bizTaskType,queryWrapper,userid, "NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=");
//update-end--author:scott--date:20220118--forJTC-865业务办理下,批量申请(自定义) 待我审批列表SQL报错-----
//--------------------------我的审批任务过滤---结束-------------------------------
IPage<ExtBizLeave> pageList = extBizLeaveService.page(page, queryWrapper);
//log.debug("查询当前页:"+pageList.getCurrent());
//log.debug("查询当前页数量:"+pageList.getSize());
//log.debug("查询结果数量:"+pageList.getRecords().size());
//log.debug("数据总数:"+pageList.getTotal());
result.setSuccess(true);
result.setResult(pageList);
return result;
}
/**
* 添加
* @param extBizLeave
* @return
*/
@PostMapping(value = "/add")
public Result<ExtBizLeave> add(@RequestBody ExtBizLeave extBizLeave) {
Result<ExtBizLeave> result = new Result<ExtBizLeave>();
try {
extBizLeave.setBpmStatus("1");
extBizLeaveService.save(extBizLeave);
result.success("添加成功!");
} catch (Exception e) {
log.error(e.getMessage(),e);
result.error500("操作失败");
}
return result;
}
/**
* 编辑
* @param extBizLeave
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<ExtBizLeave> edit(@RequestBody ExtBizLeave extBizLeave) {
Result<ExtBizLeave> result = new Result<ExtBizLeave>();
ExtBizLeave extBizLeaveEntity = extBizLeaveService.getById(extBizLeave.getId());
if(extBizLeaveEntity==null) {
result.error500("未找到对应实体");
}else {
boolean ok = extBizLeaveService.updateById(extBizLeave);
//TODO 返回false说明什么?
if(ok) {
result.success("修改成功!");
}
}
return result;
}
/**
* 通过id删除
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<ExtBizLeave> delete(@RequestParam(name="id",required=true) String id) {
Result<ExtBizLeave> result = new Result<ExtBizLeave>();
ExtBizLeave extBizLeave = extBizLeaveService.getById(id);
if(extBizLeave==null) {
result.error500("未找到对应实体");
}else {
boolean ok = extBizLeaveService.removeById(id);
if(ok) {
result.success("删除成功!");
}
}
return result;
}
/**
* 批量删除
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<ExtBizLeave> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
Result<ExtBizLeave> result = new Result<ExtBizLeave>();
if(ids==null || "".equals(ids.trim())) {
result.error500("参数不识别!");
}else {
this.extBizLeaveService.removeByIds(Arrays.asList(ids.split(",")));
result.success("删除成功!");
}
return result;
}
/**
* 通过id查询
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<ExtBizLeave> queryById(@RequestParam(name="id",required=true) String id) {
Result<ExtBizLeave> result = new Result<ExtBizLeave>();
ExtBizLeave extBizLeave = extBizLeaveService.getById(id);
if(extBizLeave==null) {
result.error500("未找到对应实体");
}else {
result.setResult(extBizLeave);
result.setSuccess(true);
}
return result;
}
}
@@ -0,0 +1,56 @@
package org.jeecg.modules.biz.entity;
import java.io.Serializable;
import org.springframework.format.annotation.DateTimeFormat;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* @Description: 请假单表
* @author jeecg-boot
* @date 2019-03-26
* @version V1.0
*/
@Data
@TableName("ext_biz_leave")
public class ExtBizLeave implements Serializable {
private static final long serialVersionUID = 1L;
/**ID*/
@TableId(type = IdType.ASSIGN_UUID)
private java.lang.String id;
/**请假人*/
private java.lang.String name;
/**请假天数*/
private java.lang.Integer days;
/**开始时间*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
private java.util.Date beginDate;
/**请假结束时间*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
private java.util.Date endDate;
/**请假原因*/
private java.lang.String reason;
/**流程状态*/
private java.lang.String bpmStatus;
/**创建人id*/
private java.lang.String createBy;
/**创建时间*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
private java.util.Date createTime;
/**修改时间*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
private java.util.Date updateTime;
/**修改人id*/
private java.lang.String updateBy;
}
@@ -0,0 +1,23 @@
package org.jeecg.modules.biz.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.biz.entity.ExtBizLeave;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @Description: 请假单表
* @author jeecg-boot
* @date 2019-03-26
* @version V1.0
*/
public interface ExtBizLeaveMapper extends BaseMapper<ExtBizLeave> {
@InterceptorIgnore(illegalSql = "true", tenantLine = "true")
List<String> findRunningTask(@Param("username")String username, @Param("flowCode")String flowCode);
@InterceptorIgnore(illegalSql = "true", tenantLine = "true")
List<String> findRunningTaskByTaskDefKey(@Param("username")String username, @Param("flowCode")String flowCode,@Param("taskDefKey")String taskDefKey);
}
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.biz.mapper.ExtBizLeaveMapper">
<select id="findRunningTask" parameterType="string" resultType="java.lang.String">
SELECT
fd.FORM_DATA_ID
FROM
EXT_ACT_FLOW_DATA fd
RIGHT JOIN (
SELECT
*
FROM
(
(
SELECT DISTINCT
RES.*
FROM
ACT_RU_TASK RES
INNER JOIN ACT_RU_IDENTITYLINK I ON I.TASK_ID_ = RES.ID_
WHERE
RES.ASSIGNEE_ IS NULL
AND I.TYPE_ = 'candidate'
AND (
I.USER_ID_ = #{username}
OR I.GROUP_ID_ IN ( SELECT g.GROUP_ID_ FROM ACT_ID_MEMBERSHIP g WHERE g.USER_ID_ = #{username} )
)
AND RES.SUSPENSION_STATE_ = 1
) UNION
( SELECT DISTINCT RES.* FROM ACT_RU_TASK RES WHERE RES.ASSIGNEE_ =#{username} )) v
) art ON fd.PROCESS_INST_ID = art.PROC_INST_ID_
WHERE
fd.RELATION_CODE = #{flowCode}
</select>
<select id="findRunningTaskByTaskDefKey" parameterType="string" resultType="java.lang.String">
SELECT
fd.FORM_DATA_ID
FROM
EXT_ACT_FLOW_DATA fd
RIGHT JOIN (
SELECT
*
FROM
(
(
SELECT DISTINCT
RES.*
FROM
ACT_RU_TASK RES
INNER JOIN ACT_RU_IDENTITYLINK I ON I.TASK_ID_ = RES.ID_
WHERE
RES.ASSIGNEE_ IS NULL
AND I.TYPE_ = 'candidate'
AND ( I.USER_ID_ = #{username} OR I.GROUP_ID_ IN ( SELECT g.GROUP_ID_ FROM ACT_ID_MEMBERSHIP g WHERE g.USER_ID_ = #{username} ) )
AND RES.SUSPENSION_STATE_ = 1
) UNION
( SELECT DISTINCT RES.* FROM ACT_RU_TASK RES WHERE RES.ASSIGNEE_ = #{username} )) v
) art ON fd.PROCESS_INST_ID = art.PROC_INST_ID_
WHERE
fd.RELATION_CODE = #{flowCode}
AND art.TASK_DEF_KEY_ = #{taskDefKey}
</select>
</mapper>
@@ -0,0 +1,19 @@
package org.jeecg.modules.biz.service;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.biz.entity.ExtBizLeave;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* @Description: 请假单表 @author jeecg-boot @date 2019-03-26 @version V1.0
*
* NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=
*/
public interface IExtBizLeaveService extends IService<ExtBizLeave> {
List<String> findRunningTask(String username, String flowCode);
List<String> findRunningTaskByTaskDefKey(String username, String flowCode, String taskDefKey);
}
@@ -0,0 +1,33 @@
package org.jeecg.modules.biz.service.impl;
import org.jeecg.modules.biz.entity.ExtBizLeave;
import org.jeecg.modules.biz.mapper.ExtBizLeaveMapper;
import org.jeecg.modules.biz.service.IExtBizLeaveService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import javax.annotation.Resource;
import java.util.List;
/**
* @Description: 请假单表
* @author jeecg-boot
* @date 2019-03-26
* @version V1.0
*/
@Service
public class ExtBizLeaveServiceImpl extends ServiceImpl<ExtBizLeaveMapper, ExtBizLeave> implements IExtBizLeaveService {
@Resource
private ExtBizLeaveMapper extBizLeaveMapper;
@Override
public List<String> findRunningTask(String username, String flowCode) {
return extBizLeaveMapper.findRunningTask(username, flowCode);
}
@Override
public List<String> findRunningTaskByTaskDefKey(String username, String flowCode, String taskDefKey) {
return extBizLeaveMapper.findRunningTaskByTaskDefKey(username, flowCode, taskDefKey);
}
}
@@ -0,0 +1,272 @@
package org.jeecg.modules.designform.controller;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.designform.entity.DesignFormCommuse;
import org.jeecg.modules.designform.model.DesignFormModel;
import org.jeecg.modules.designform.model.SysRoleDeisgnModel;
import org.jeecg.modules.designform.service.IDesignFormCommuseService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.modules.designform.service.ISysRoleDesignFormService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
/**
* @Description: 常用流程
* @Author: jeecg-boot
* @Date: 2020-03-25
* @Version: V1.0
*/
@Slf4j
@Tag(name="常用流程")
@RestController
@RequestMapping("/joa/designform/designFormCommuse")
public class DesignFormCommuseController extends JeecgController<DesignFormCommuse, IDesignFormCommuseService> {
@Autowired
private IDesignFormCommuseService designFormCommuseService;
@Autowired
private ISysRoleDesignFormService roleDesignFormService;
/**
* OA菜单权限-查看
*/
@RequestMapping(value = "/getRoleDegisnList", method = RequestMethod.GET)
public Result<List<DesignFormModel>> getRoleDegisnList(@RequestParam(value = "roleId") String roleId){
Result<List<DesignFormModel>> result = new Result<>();
List<DesignFormModel> roleDegisnList = roleDesignFormService.getRoleDegisnList(roleId);
result.setResult(roleDegisnList);
return result;
}
/**
* OA菜单权限-添加
*/
@RequestMapping(value = "/sysRoleDesignAdd", method = RequestMethod.POST)
public Result<?> sysRoleDesignAdd(@RequestBody JSONObject json) {
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
String roleId = json.getString("roleId");
String newDesignId = json.getString("newDesignId");
String oldDessignId = json.getString("oldDessignId");
roleDesignFormService.sysRoleDesignAdd(loginUser.getId(),roleId,newDesignId,oldDessignId);
return Result.ok("添加成功!");
}
/**
* 根据当前登录人查看OA申请单权限
*/
@RequestMapping(value = "/roleDegisnList", method = RequestMethod.GET)
public Result<List<SysRoleDeisgnModel>> roleDegisnList(@RequestParam(name = "procType", required = false) String procType){
Result<List<SysRoleDeisgnModel>> result = new Result<>();
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
String username=loginUser.getUsername();
List<SysRoleDeisgnModel> designForms = roleDesignFormService.roleDegisnList(username,procType);
result.setSuccess(true);
result.setResult(designForms);
return result;
}
/**
* 根据当前登录人查看OA申请单权限
*/
@RequestMapping(value = "/roleOnlineList", method = RequestMethod.GET)
public Result<List<SysRoleDeisgnModel>> roleOnlineList(@RequestParam(name = "procType", required = false) String procType){
Result<List<SysRoleDeisgnModel>> result = new Result<>();
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
String username=loginUser.getUsername();
List<SysRoleDeisgnModel> designForms = roleDesignFormService.roleOnlineList(username,procType);
result.setSuccess(true);
result.setResult(designForms);
return result;
}
/**
* 查询online表单
* @param procType
* @return
*/
@RequestMapping(value = "/queryOnlineFormList", method = RequestMethod.GET)
public Result<List<SysRoleDeisgnModel>> queryOnlineFormList(@RequestParam(name = "procType", required = false) String procType){
Result<List<SysRoleDeisgnModel>> result = new Result<>();
List<SysRoleDeisgnModel> designForms = roleDesignFormService.queryOnlineFormList(procType);
result.setSuccess(true);
result.setResult(designForms);
return result;
}
/**
* 根据用户id获取常用流程
* @return
*/
@RequestMapping(value = "/getCommuseByUserId", method = RequestMethod.GET)
public Result<List<SysRoleDeisgnModel>> getCommuseByUserId(){
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
Result<List<SysRoleDeisgnModel>> result = new Result<>();
// 查询设计器表单
List<SysRoleDeisgnModel> designList = designFormCommuseService.getCommuseByUserId(sysUser.getId());
// 查询online表单
List<SysRoleDeisgnModel> onlineList = designFormCommuseService.getCommuseOnlineFormByUserId(sysUser.getId());
designList.addAll(onlineList);
result.setResult(designList);
return result;
}
/**
* 设置常用流程
* @param json
* @return
*/
@RequestMapping(value = "/commUseDesignAdd", method = RequestMethod.POST)
public Result<?> commUseDesignAdd(@RequestBody JSONObject json) {
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
String newDesignId = json.getString("newDesignId");
String oldDessignId = json.getString("oldDessignId");
// 新增参数获取online表单的id
String onlineForm = json.getString("onlineForm");
designFormCommuseService.commUseDesignAdd(sysUser.getId(),newDesignId,oldDessignId, onlineForm);
return Result.ok("添加成功!");
}
/**
* 分页列表查询
*
* @param designFormCommuse
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@Operation(summary="常用流程-分页列表查询")
@GetMapping(value = "/list")
public Result<?> queryPageList(DesignFormCommuse designFormCommuse,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<DesignFormCommuse> queryWrapper = QueryGenerator.initQueryWrapper(designFormCommuse, req.getParameterMap());
Page<DesignFormCommuse> page = new Page<DesignFormCommuse>(pageNo, pageSize);
IPage<DesignFormCommuse> pageList = designFormCommuseService.page(page, queryWrapper);
return Result.ok(pageList);
}
/**
* 添加
*
* @param designFormCommuse
* @return
*/
@Operation(summary="常用流程-添加")
@PostMapping(value = "/add")
public Result<?> add(@RequestBody DesignFormCommuse designFormCommuse) {
designFormCommuseService.save(designFormCommuse);
return Result.ok("添加成功!");
}
/**
* 编辑
*
* @param designFormCommuse
* @return
*/
@AutoLog(value = "常用流程-编辑")
@Operation(summary="常用流程-编辑")
@PutMapping(value = "/edit")
public Result<?> edit(@RequestBody DesignFormCommuse designFormCommuse) {
designFormCommuseService.updateById(designFormCommuse);
return Result.ok("编辑成功!");
}
/**
* 通过id删除
*
* @param id
* @return
*/
@Operation(summary="常用流程-通过id删除")
@DeleteMapping(value = "/delete")
public Result<?> delete(@RequestParam(name="id",required=true) String id) {
designFormCommuseService.removeById(id);
return Result.ok("删除成功!");
}
/**
* 批量删除
*
* @param ids
* @return
*/
@Operation(summary="常用流程-批量删除")
@DeleteMapping(value = "/deleteBatch")
public Result<?> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
this.designFormCommuseService.removeByIds(Arrays.asList(ids.split(",")));
return Result.ok("批量删除成功!");
}
/**
* 通过id查询
*
* @param id
* @return
*/
@Operation(summary="常用流程-通过id查询")
@GetMapping(value = "/queryById")
public Result<?> queryById(@RequestParam(name="id",required=true) String id) {
DesignFormCommuse designFormCommuse = designFormCommuseService.getById(id);
return Result.ok(designFormCommuse);
}
/**
* 排序修改
* @return
*/
@Operation(summary="工单申请-排序修改")
@PostMapping(value = "/sortChange")
@ResponseBody
public Result<?> sortChange(@RequestBody JSONObject jsonObject) {
JSONArray jsonArr = jsonObject.getJSONArray("changeItem");
for(int i= 0;i < jsonArr.size();i++){
String tableCode = jsonArr.getJSONObject(i).getString("desformCode");
Integer sortNum = jsonArr.getJSONObject(i).getInteger("sortNum");
roleDesignFormService.updateSortByCode(tableCode,sortNum);
}
return Result.OK("修改成功!");
}
/**
* 导出excel
*
* @param request
* @param designFormCommuse
*/
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, DesignFormCommuse designFormCommuse) {
return super.exportXls(request, designFormCommuse, DesignFormCommuse.class, "常用流程");
}
/**
* 通过excel导入数据
*
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, DesignFormCommuse.class);
}
}
@@ -0,0 +1,73 @@
package org.jeecg.modules.designform.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
/**
* @Description: 常用流程
* @Author: jeecg-boot
* @Date: 2020-03-25
* @Version: V1.0
*/
@Data
@TableName("design_form_commuse")
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Schema(description="常用流程")
public class DesignFormCommuse {
/**id*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "id")
private java.lang.String id;
/**用户id*/
@Excel(name = "用户id", width = 15)
@Schema(description = "用户id")
private java.lang.String userId;
/**desformId*/
@Excel(name = "desformId", width = 15)
@Schema(description = "desformId")
private java.lang.String desformId;
/**创建人*/
@Excel(name = "创建人", width = 15)
@Schema(description = "创建人")
private java.lang.String createBy;
/**创建时间*/
@Excel(name = "创建时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建时间")
private java.util.Date createTime;
/**修改人*/
@Excel(name = "修改人", width = 15)
@Schema(description = "修改人")
private java.lang.String updateBy;
/**修改时间*/
@Excel(name = "修改时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
@Schema(description = "修改时间")
private java.util.Date updateTime;
/**
* 表单类型-online/design
*/
private String formType;
public DesignFormCommuse(String userId, String desformId) {
this.userId = userId;
this.desformId = desformId;
}
public DesignFormCommuse() {
}
}
@@ -0,0 +1,47 @@
package org.jeecg.modules.designform.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecgframework.poi.excel.annotation.Excel;
/**
* @Description: 系统角色与OA表单授权
* @Author: jeecg-boot
* @Date: 2020-03-25
* @Version: V1.0
*/
@Data
@TableName("sys_role_design")
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Schema(description="系统角色与OA表单授权")
public class SysRoleDesignForm {
/**主键*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private java.lang.String id;
/**角色id*/
@Excel(name = "角色id", width = 15)
@Schema(description = "角色id")
private java.lang.String roleId;
/**表单设计器id*/
@Excel(name = "表单设计器id", width = 15)
@Schema(description = "表单设计器id")
private java.lang.String designId;
public SysRoleDesignForm(String roleId, String designId) {
this.roleId = roleId;
this.designId = designId;
}
public SysRoleDesignForm() {
}
}
@@ -0,0 +1,27 @@
package org.jeecg.modules.designform.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.designform.entity.DesignFormCommuse;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.designform.model.SysRoleDeisgnModel;
/**
* @Description: 常用流程
* @Author: jeecg-boot
* @Date: 2020-03-25
* @Version: V1.0
*/
public interface DesignFormCommuseMapper extends BaseMapper<DesignFormCommuse> {
List<SysRoleDeisgnModel> getCommuseByUserId(@Param("userId") String userId);
/**
* 查询常用表单--online表单
* @Date:20220215
* @Author:taoyan
* @param userId
* @return
*/
List<SysRoleDeisgnModel> getCommuseOnlineFormByUserId(@Param("userId") String userId);
}
@@ -0,0 +1,61 @@
package org.jeecg.modules.designform.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.designform.entity.SysRoleDesignForm;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.designform.model.DesignFormModel;
import org.jeecg.modules.designform.model.SysRoleDeisgnModel;
/**
* @Description: 系统角色与OA表单授权
* @Author: jeecg-boot
* @Date: 2020-03-25
* @Version: V1.0
*/
public interface SysRoleDesignFormMapper extends BaseMapper<SysRoleDesignForm> {
/**
* 根据角色id获取已授权表单
* @param roleId
* @return
*/
List<DesignFormModel> getRoleDegisnList(@Param("roleId") String roleId);
/**
* 根据角色id获取已授权online表单
* @param roleId
* @return
*/
List<DesignFormModel> getOnlineRoleDegisnList(@Param("roleId")String roleId);
/**
* 根据用户获取关联表单
* @param username
* @param procType
* @return
*/
List<SysRoleDeisgnModel> roleDegisnList(@Param("username") String username, @Param("procType") String procType);
/**
* 根据用户获取关联online表单
* @param username
* @param procType
* @return
*/
List<SysRoleDeisgnModel> roleOnlineList(@Param("username")String username,@Param("procType")String procType);
/**
* 查询online表单
* @param procType
* @return
*/
List<SysRoleDeisgnModel> queryOnlineFormList(@Param("procType") String procType);
/**
* 根据表单code排序
* @param tableCode
* @param sortNum
*/
void updateSortByCode(@Param("tableCode")String tableCode, @Param("sortNum")Integer sortNum);
}
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.designform.mapper.DesignFormCommuseMapper">
<!--根据用户id查询已设置常用表单-->
<select id="getCommuseByUserId" parameterType="string" resultType="org.jeecg.modules.designform.model.SysRoleDeisgnModel">
SELECT
df.id,
df.desform_code,
df.desform_name,
ea.pc_icon AS desform_icon,
ea.app_icon,
ea.process_type AS procType,
ea.process_name AS procName,
ef.title_exp,
ef.sort_num AS sortNum,
dc.form_type as formType
FROM
design_form df
INNER JOIN design_form_commuse dc ON df.id = dc.desform_id and dc.form_type = 'design'
JOIN ext_act_process_form ef ON df.desform_code = ef.form_table_name and ef.form_type = '2'
JOIN ext_act_process ea ON ef.process_id = ea.id
WHERE
dc.user_id = #{userId}
order by ef.sort_num
</select>
<!--根据用户id查询已设置常用表单(online) -->
<select id="getCommuseOnlineFormByUserId" parameterType="string" resultType="org.jeecg.modules.designform.model.SysRoleDeisgnModel">
SELECT
df.id,
df.table_name AS desform_code,
df.table_txt AS desform_name,
ea.pc_icon AS desform_icon,
ea.app_icon,
ea.process_type AS procType,
ea.process_name AS procName,
ef.title_exp,
ef.sort_num AS sortNum,
dc.form_type as formType
FROM
onl_cgform_head df
INNER JOIN design_form_commuse dc ON df.id = dc.desform_id and dc.form_type = 'online'
JOIN ext_act_process_form ef ON df.table_name = ef.form_table_name and ef.form_type = '1'
JOIN ext_act_process ea ON ef.process_id = ea.id
WHERE
dc.user_id = #{userId}
order by ef.sort_num
</select>
</mapper>
@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.designform.mapper.SysRoleDesignFormMapper">
<!--根据角色查询已授权表单-->
<select id="getRoleDegisnList" parameterType="string" resultType="org.jeecg.modules.designform.model.DesignFormModel">
SELECT
df.*,
ea.process_type AS procType
FROM
design_form df
JOIN sys_role_design rd ON df.id = rd.design_id
JOIN ext_act_process_form ef ON df.desform_code = ef.form_table_name
JOIN ext_act_process ea ON ef.process_id = ea.id
WHERE
rd.role_id = #{roleId}
</select>
<!--根据角色查询已授权online表单-->
<select id="getOnlineRoleDegisnList" parameterType="string" resultType="org.jeecg.modules.designform.model.DesignFormModel">
SELECT
df.id,
df.table_name AS desform_code,
df.table_txt AS desform_name
FROM
onl_cgform_head df
JOIN sys_role_design rd ON df.id = rd.design_id
JOIN ext_act_process_form ef ON df.table_name = ef.form_table_name
JOIN ext_act_process ea ON ef.process_id = ea.id
WHERE
rd.role_id = #{roleId}
</select>
<!--查询表单关联流程-->
<select id="roleDegisnList" parameterType="string" resultType="org.jeecg.modules.designform.model.SysRoleDeisgnModel">
SELECT
df.id,
df.desform_code,
df.desform_name,
ea.pc_icon as desformIcon,
ea.app_icon,
ea.process_type AS procType,
ea.process_name AS procName,
ef.title_exp,
ef.sort_num AS sortNum
FROM
design_form df
JOIN ext_act_process_form ef ON df.desform_code = ef.form_table_name
JOIN ext_act_process ea ON ef.process_id = ea.id
WHERE
EXISTS (
SELECT
a.id
FROM
design_form a
JOIN sys_role_design sd ON a.id = sd.design_id
JOIN sys_role b ON sd.role_id = b.id
JOIN sys_user_role c ON b.id = c.role_id
JOIN sys_user d ON c.user_id = d.id
WHERE
a.id = df.id
AND d.username = #{username}
)
AND ef.form_type = '2'
<if test="procType !=null and procType != ''">
AND ea.process_type=#{procType}
</if>
order by ef.sort_num ASC,df.CREATE_TIME DESC
</select>
<!-- 查看online角色表单 -->
<select id="roleOnlineList" parameterType="string" resultType="org.jeecg.modules.designform.model.SysRoleDeisgnModel">
SELECT
df.id,
df.table_name AS desform_code,
df.table_txt AS desform_name,
ea.process_type AS proc_type,
ea.process_name AS proc_name,
ef.title_exp,
ef.sort_num AS sortNum,
ea.app_icon
FROM
onl_cgform_head df
JOIN ext_act_process_form ef ON df.table_name = ef.form_table_name
JOIN ext_act_process ea ON ef.process_id = ea.id
WHERE
EXISTS (
SELECT
a.id
FROM
onl_cgform_head a
JOIN sys_role_design sd ON a.id = sd.design_id
JOIN sys_role b ON sd.role_id = b.id
JOIN sys_user_role c ON b.id = c.role_id
JOIN sys_user d ON c.user_id = d.id
WHERE
a.id = df.id
AND d.username = #{username}
)
AND ef.form_type = '1'
<if test="procType !=null and procType != ''">
AND ea.process_type=#{procType}
</if>
order by ef.sort_num ASC, df.CREATE_TIME desc
</select>
<!-- 查看online表单 -->
<select id="queryOnlineFormList" parameterType="string" resultType="org.jeecg.modules.designform.model.SysRoleDeisgnModel">
SELECT
df.id,
df.table_name AS desform_code,
df.table_txt AS desform_name,
ea.process_type AS proc_type,
ea.process_name AS proc_name,
ef.title_exp,
ef.sort_num AS sortNum,
ea.app_icon
FROM
onl_cgform_head df
JOIN ext_act_process_form ef ON df.table_name = ef.form_table_name
JOIN ext_act_process ea ON ef.process_id = ea.id
WHERE ef.form_type = '1'
<if test="procType !=null and procType != ''">
AND ea.process_type=#{procType}
</if>
order by ef.sort_num ASC, df.CREATE_TIME desc
</select>
<!-- 根据表单code更新排序数值字段 -->
<update id="updateSortByCode">
UPDATE ext_act_process_form SET sort_num = #{sortNum} WHERE form_table_name = #{tableCode}
</update>
</mapper>
@@ -0,0 +1,34 @@
package org.jeecg.modules.designform.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
* OA表单model
*/
@Data
public class DesignFormModel {
private String id;
private String desformCode;
private String desformName;
private String desformIcon;
private String desformDesignJson;
private String cgformCode;
private String parentId;
private String parentCode;
private Integer desformType;
private Boolean izOaShow;
private String createBy;
/**流程类型*/
private String procType;
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
@@ -0,0 +1,34 @@
package org.jeecg.modules.designform.model;
import lombok.Data;
/**
* 角色表单授权model
*/
@Data
public class SysRoleDeisgnModel {
/**主键*/
private String id;
/**变单设计器code*/
private String desformCode;
/**变单设计器名称*/
private String desformName;
/**变单设计器图标 PC端*/
private String desformIcon;
/**变单设计器图标 移动app端*/
private String appIcon;
/**流程类型*/
private String procType;
/**流程名称*/
private String procName;
/**标题表达式*/
private String titleExp;
/**
* 表单类型 online/design
*/
private String formType;
/**
* 排序字段
*/
private Integer sortNum;
}
@@ -0,0 +1,41 @@
package org.jeecg.modules.designform.service;
import org.jeecg.modules.designform.entity.DesignFormCommuse;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.designform.model.SysRoleDeisgnModel;
import java.util.List;
/**
* @Description: 常用流程
* @Author: jeecg-boot
* @Date: 2020-03-25
* @Version: V1.0
*/
public interface IDesignFormCommuseService extends IService<DesignFormCommuse> {
/**
* 根据用户id查询常用流程
* @param userId
* @return
*/
List<SysRoleDeisgnModel> getCommuseByUserId(String userId);
/**
* 根据用户id查询常用流程--online表单
* @Date:20220215
* @Author:taoyan
* @param userId
* @return
*/
List<SysRoleDeisgnModel> getCommuseOnlineFormByUserId(String userId);
/**
* 设置常用流程
* @param userId
* @param newDesignId
* @param oldDessignId
* @param onlineForm online表单的ID
*/
void commUseDesignAdd(String userId, String newDesignId, String oldDessignId, String onlineForm);
}
@@ -0,0 +1,62 @@
package org.jeecg.modules.designform.service;
import org.jeecg.modules.designform.entity.SysRoleDesignForm;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.designform.model.DesignFormModel;
import org.jeecg.modules.designform.model.SysRoleDeisgnModel;
import java.util.List;
/**
* @Description: 系统角色与OA表单授权
* @Author: jeecg-boot
* @Date: 2020-03-25
* @Version: V1.0
*/
public interface ISysRoleDesignFormService extends IService<SysRoleDesignForm> {
/**
* 添加授权表单
* @param userId
* @param roleId
* @param newDesignId
* @param oldDessignId
*/
void sysRoleDesignAdd(String userId,String roleId, String newDesignId, String oldDessignId);
/**
* 根据系统角色获取已授权表单
* @param roleId
* @return
*/
List<DesignFormModel> getRoleDegisnList(String roleId);
/**
* 根据用户获取关联表单
* @param username
* @param procType
* @return
*/
List<SysRoleDeisgnModel> roleDegisnList(String username, String procType);
/**
* 查询online表单
* @param procType
* @return
*/
List<SysRoleDeisgnModel> queryOnlineFormList(String procType);
/**
* 根据表单code排序
* @param tableCode
* @param sortNum
*/
void updateSortByCode(String tableCode, Integer sortNum);
/**
* 根据用户获取关联online表单
* @param username
* @param procType
* @return
*/
List<SysRoleDeisgnModel> roleOnlineList(String username, String procType);
}
@@ -0,0 +1,99 @@
package org.jeecg.modules.designform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.designform.entity.DesignFormCommuse;
import org.jeecg.modules.designform.mapper.DesignFormCommuseMapper;
import org.jeecg.modules.designform.model.SysRoleDeisgnModel;
import org.jeecg.modules.designform.service.IDesignFormCommuseService;
import org.jeecg.modules.designform.util.Constant;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
/**
* @Description: 常用流程
* @Author: jeecg-boot
* @Date: 2020-03-25
* @Version: V1.0
*/
@Service
public class DesignFormCommuseServiceImpl extends ServiceImpl<DesignFormCommuseMapper, DesignFormCommuse> implements IDesignFormCommuseService {
@Resource
private DesignFormCommuseMapper commuserMapper;
@Override
public List<SysRoleDeisgnModel> getCommuseByUserId(String userId) {
return commuserMapper.getCommuseByUserId(userId);
}
@Override
public List<SysRoleDeisgnModel> getCommuseOnlineFormByUserId(String userId) {
return commuserMapper.getCommuseOnlineFormByUserId(userId);
}
@Override
public void commUseDesignAdd(String userId, String newDesignId, String oldDessignId,String onlineForm) {
// update-begin-author:taoyan date:20220215 for:jtc-983 【工单申请 建议】常用流程设置,增加online流程的设置
String token = "NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=";
List<String> onlineList = new ArrayList<>();
if(oConvertUtils.isNotEmpty(onlineForm)){
String str[] = onlineForm.split(",");
onlineList = Arrays.asList(str);
}
List<String> add = getDiff(oldDessignId,newDesignId);
if(add!=null && add.size()>0) {
List<DesignFormCommuse> list = new ArrayList<DesignFormCommuse>();
for (String designId : add) {
if(org.apache.commons.lang.StringUtil.isNotEmpty(designId)) {
DesignFormCommuse rolepms = new DesignFormCommuse(userId, designId);
if(onlineList.contains(designId)){
rolepms.setFormType(Constant.FORM_TYPE_OL);
}else{
rolepms.setFormType(Constant.FORM_TYPE_DS);
}
list.add(rolepms);
}
}
this.saveBatch(list);
}
// update-end-author:taoyan date:20220215 for:jtc-983 【工单申请 建议】常用流程设置,增加online流程的设置
List<String> remove = getDiff(newDesignId,oldDessignId);
if(remove!=null && remove.size()>0) {
for (String designId : remove) {
this.remove(new QueryWrapper<DesignFormCommuse>().lambda().eq(DesignFormCommuse::getUserId, userId).eq(DesignFormCommuse::getDesformId, designId));
}
}
}
/**
* 从diff中找出main中没有的元素
* @param main
* @param diff
* @return
*/
private List<String> getDiff(String main, String diff){
if(org.apache.commons.lang.StringUtil.isEmpty(diff)) {
return null;
}
if(org.apache.commons.lang.StringUtil.isEmpty(main)) {
return Arrays.asList(diff.split(","));
}
String[] mainArr = main.split(",");
String[] diffArr = diff.split(",");
Map<String, Integer> map = new HashMap<>();
for (String string : mainArr) {
map.put(string, 1);
}
List<String> res = new ArrayList<String>();
for (String key : diffArr) {
if(org.apache.commons.lang.StringUtil.isNotEmpty(key) && !map.containsKey(key)) {
res.add(key);
}
}
return res;
}
}
@@ -0,0 +1,125 @@
package org.jeecg.modules.designform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.modules.designform.entity.DesignFormCommuse;
import org.jeecg.modules.designform.entity.SysRoleDesignForm;
import org.jeecg.modules.designform.mapper.DesignFormCommuseMapper;
import org.jeecg.modules.designform.mapper.SysRoleDesignFormMapper;
import org.jeecg.modules.designform.model.DesignFormModel;
import org.jeecg.modules.designform.model.SysRoleDeisgnModel;
import org.jeecg.modules.designform.service.ISysRoleDesignFormService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.*;
/**
* @Description: 系统角色与OA表单授权
* @Author: jeecg-boot
* @Date: 2020-03-25
* @Version: V1.0
*/
@Service
public class SysRoleDesignFormServiceImpl extends ServiceImpl<SysRoleDesignFormMapper, SysRoleDesignForm> implements ISysRoleDesignFormService {
@Autowired
private SysRoleDesignFormMapper sysRoleDesignFormMapper;
@Autowired
private DesignFormCommuseMapper commuserMapper;
@Override
public void sysRoleDesignAdd(String userId, String roleId, String newDesignId, String oldDessignId) {
List<String> add = getDiff(oldDessignId,newDesignId);
if(add!=null && add.size()>0) {
List<SysRoleDesignForm> list = new ArrayList<SysRoleDesignForm>();
for (String designId : add) {
if(org.apache.commons.lang.StringUtil.isNotEmpty(designId)) {
SysRoleDesignForm rolepms = new SysRoleDesignForm(roleId, designId);
list.add(rolepms);
}
}
this.saveBatch(list);
}
List<String> remove = getDiff(newDesignId,oldDessignId);
if(remove!=null && remove.size()>0) {
for (String designId : remove) {
this.remove(new QueryWrapper<SysRoleDesignForm>().lambda().eq(SysRoleDesignForm::getRoleId, roleId).eq(SysRoleDesignForm::getDesignId, designId));
//删除已设置的常用功能
commuserMapper.delete(new QueryWrapper<DesignFormCommuse>().lambda().eq(DesignFormCommuse::getUserId, userId).eq(DesignFormCommuse::getDesformId, designId));
}
}
}
@Override
public List<DesignFormModel> getRoleDegisnList(String roleId) {
//1.获取设计器授权表单
List<DesignFormModel> roleDegisnList = sysRoleDesignFormMapper.getRoleDegisnList(roleId);
//2.获取online授权表单
List<DesignFormModel> onlineRoleDegisnList = sysRoleDesignFormMapper.getOnlineRoleDegisnList(roleId);
if(!CollectionUtils.isEmpty(onlineRoleDegisnList)){
roleDegisnList.addAll(onlineRoleDegisnList);
}
return roleDegisnList;
}
@Override
public List<SysRoleDeisgnModel> roleDegisnList(String username, String procType) {
return sysRoleDesignFormMapper.roleDegisnList(username,procType);
}
@Override
public List<SysRoleDeisgnModel> queryOnlineFormList(String procType) {
return sysRoleDesignFormMapper.queryOnlineFormList(procType);
}
/**
* 根据表单code排序
* @param tableCode
* @param sortNum
*/
@Override
public void updateSortByCode(String tableCode, Integer sortNum) {
sysRoleDesignFormMapper.updateSortByCode(tableCode,sortNum);
}
/**
* 根据用户获取关联online表单
* @param username
* @param procType
* @return
*/
@Override
public List<SysRoleDeisgnModel> roleOnlineList(String username, String procType) {
return sysRoleDesignFormMapper.roleOnlineList(username,procType);
}
/**
* 从diff中找出main中没有的元素
* @param main
* @param diff
* @return
*/
private List<String> getDiff(String main, String diff){
if(org.apache.commons.lang.StringUtil.isEmpty(diff)) {
return null;
}
if(org.apache.commons.lang.StringUtil.isEmpty(main)) {
return Arrays.asList(diff.split(","));
}
String[] mainArr = main.split(",");
String[] diffArr = diff.split(",");
Map<String, Integer> map = new HashMap<>();
for (String string : mainArr) {
map.put(string, 1);
}
List<String> res = new ArrayList<String>();
for (String key : diffArr) {
if(org.apache.commons.lang.StringUtil.isNotEmpty(key) && !map.containsKey(key)) {
res.add(key);
}
}
return res;
}
}
@@ -0,0 +1,23 @@
package org.jeecg.modules.designform.util;
/**
* 常量类
* @Author taoYan
* @Date 2022/2/18 17:48
**/
public class Constant {
/**
* 常用流程类型: online
*/
public static final String FORM_TYPE_OL = "online";
/**
* 常用流程类型:表单设计器
*/
public static final String FORM_TYPE_DS = "design";
public static final String FORM_TYPE_DS_TOKEN_CC = "NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=";
}
@@ -0,0 +1,54 @@
package org.jeecg.modules.extbpm.enums;
/**
* 流程节点消息类型
* @author: jeecg-boot
*/
public enum TaskTipTypeEnum {
XT("system", "bpm_system"),
YJ("email", "bpm_email"),
DD("dingtalk", "bpm_dingtalk"),
QYWX("wechat_enterprise", "bpm_wechat_enterprise");
TaskTipTypeEnum(String type, String code){
this.type = type;
this.code = code;
}
/**
* 消息类型
*/
String type;
/**
* 模板编码
*/
String code;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public static String getCodeByType(String type) {
for (TaskTipTypeEnum e : TaskTipTypeEnum.values()) {
if (type.startsWith(e.getType())) {
return e.getCode();
}
}
return null;
}
}
@@ -0,0 +1,204 @@
package org.jeecg.modules.extbpm.process.common;
/**
* 项目名称:jeecg
* 类名称:Globals
* 类描述: 全局变量定义
* 创建人:zhoujf
* @version
*
*/
public final class WorkFlowGlobals {
/**
* 流程发布状态
*/
public static Integer Process_Deploy_NO=0;//未发布
public static Integer Process_Deploy_YES=1;//已发布
/**
* BPM_BUS_STATUS[流程业务单据状态位]
* 待提交:1/处理中:2/处理完毕:3
*/
public static String BPM_BUS_STATUS_1 = "1";//待提交
public static String BPM_BUS_STATUS_2 = "2";//处理中
public static String BPM_BUS_STATUS_3 = "3";//处理完毕
public static String BPM_BUS_STATUS_4 = "4";//流程作废
public static String BPM_BUS_STATUS_5 = "5";//流程挂起
/**
* BPM 流程对应的表单KEY
*/
public static String BPM_FORM_KEY = "BPM_FORM_KEY";
/**
* BPM 流程流程开始节点名字
*/
public static String BPM_NODE_START = "start";
/**
* BPM 流程流程开始节点名字
*/
public static String BPM_NODE_END = "end";
/**
* BPM 流程对应的流程实例ID KEY[当前流程]
*/
public static String JG_LOCAL_PROCESS_ID = "JG_LOCAL_PROCESS_ID";
/**
* BPM 流程对应的流程实例ID KEY[主流程]
*/
public static String JG_SUB_MAIN_PROCESS_ID = "JG_SUB_MAIN_PROCESS_ID";
/**
* 业务数据对应ID
*/
public static String BPM_DATA_ID = "BPM_DATA_ID";
public static String DATA_ID = "id";
/**
* 日期字段触发流程的字段名
*/
public static String TIME_TRIGGER_FIELD = "TIME_TRIGGER_FIELD";
/**
* 删除数据对象(删除数据走流程)
*/
public static final String BPM_DELETE_DATA = "BPM_DELETE_DATA";
/**
* 流程发起人
*/
public static String APPLY_USER_ID = "applyUserId";
/**
* 设计器表单编码
*/
public static String BPM_DES_FORM_CODE = "BPM_DES_FORM_CODE";
/**
* 设计器表单数据ID
*/
public static String BPM_DES_DATA_ID = "BPM_DES_DATA_ID";
/**
* 设计器表单关联编码——固定前缀
*/
public static String BPM_DES_FORM_RELATION_CODE_PREFIX = "desform_";
/**
* BPM 节点对应的表单URL(全局)
*/
public static String BPM_FORM_CONTENT_URL = "BPM_FORM_CONTENT_URL";
/**
* BPM 节点对应的表单URL(全局) - 移动端
*/
public static String BPM_FORM_CONTENT_URL_MOBILE = "BPM_FORM_CONTENT_URL_MOBILE";
/**
* BPM 流程状态
*/
public static String BPM_STATUS = "bpm_status";
/**
* BPM 业务标题表达式(全局)
*/
public static String BPM_BIZ_TITLE = "bpm_biz_title";
/**
* BPM 流程办理风格(全局)
*/
public static String BPM_PROC_DEAL_STYLE = "bpm_proc_deal_style";
/**
* BPM 业务表单类型流程变量
*/
public static String BPM_FORM_TYPE = "BPM_FORM_TYPE";
/**
* BPM 业务表单类型 表单类型:1:Online表单,2:设计器表单,3:自定义开发
*/
public static String BPM_FORM_TYPE_1 = "1";
public static String BPM_FORM_TYPE_2 = "2";
public static String BPM_FORM_TYPE_3 = "3";
/**
* BPM 流程对应的流程业务KEY
*/
public static String BPM_FORM_BUSINESSKEY = "BPM_FORM_BUSINESSKEY";
/**
* 会签【用户组常量】
*/
public static String ASSIGNEE_USER_ID_LIST = "assigneeUserIdList";
/**
* 流程追回状态
*/
public static String PROCESS_CALLBACKPROCESS_STATUS = "callBackProcess";
/**
* 流程作废状态
*/
public static String PROCESS_INVALIDPROCESS_STATUS = "invalidProcess";
/**
* 流程驳回状态
*/
public static String PROCESS_REJECTPROCESS_STATUS = "rejectProcess";
/**
* BPM 节点对应的表单URL类型--pc电脑端 mobile移动端
*/
public static String SUFFIX_BPM_FORM_URL = "pc";
public static String SUFFIX_BPM_FORM_URL_MOBILE = "mobile";
/**
* BPM_OP_TYPE[流程日志操作类型]
* 普通任务:1/会签任务:2/驳回:3/作废:4/取回:5
*/
public static String BPM_OP_TYPE_1 = "1";//普通任务
public static String BPM_OP_TYPE_2 = "2";//会签任务
public static String BPM_OP_TYPE_3 = "3";//驳回
public static String BPM_OP_TYPE_4 = "4";//作废
public static String BPM_OP_TYPE_5 = "5";//取回
/**
* 定时任务,自动委派用户ID
*/
public static final String SYS_AGENT_JOB_OPUSER_ID = "System Job";
/**
* 表单设计器,流程中间草稿表
*/
public static final String DESIGN_FORM_DRAFT_TABLE = "EXT_ACT_DESIGN_FLOW_DATA";
/**
* 流程用户组类型
* role 角色
* depart 部门
* position 职务
*/
public static final String ACTIVITI_GROUP_TYPE_DEP = "depart";
public static final String ACTIVITI_GROUP_TYPE_ROLE = "role";
public static final String ACTIVITI_GROUP_TYPE_POSITION = "position";
/**
* 动态key
*/
public static final String DESIGN_FORM_DRAFT_TABLE_KEY = "NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=";
/**
* 当前驳回节点key
*/
public static final String CURRENT_REJECT_NODE_KEY = "CURRENT_REJECT_NODE_KEY";
/**
* 并发运行方式(默认并行)
* 并行0、串行 1
*/
public static String RUN_CONCURRENT_MODE_PARALLEL = "0";
public static String RUN_CONCURRENT_MODE_SERIAL = "1";
/**
* 子流程中的节点 需要添加一个标识
*/
public static String SUB_EVENT = "subEvent_";
/**
* 流程信号启动类型:按钮触发
*/
public static String START_BUTTON_EVENT = "buttonEvent";
}
@@ -0,0 +1,65 @@
package org.jeecg.modules.joa.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.joa.entity.JoaDocReceiving;
import org.jeecg.modules.joa.model.DraftModel;
import org.jeecg.modules.joa.service.IJoaAppService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/joa/app")
@Slf4j
public class JoaAppController {
@Autowired
private IJoaAppService joaAppService;
/**
* 查询草稿箱
* /app/joa/draftList
* @param model
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/draftList")
public Result<List<DraftModel>> queryDraftList(DraftModel model,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<List<DraftModel>> result = new Result<List<DraftModel>>();
//设置查询条件 创建人为当前登陆人
LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
model.setCreateBy(user.getUsername());
Page<DraftModel> pageList = joaAppService.queryDraftList(model,pageNo,pageSize);
result.setResult(pageList.getRecords());
result.setSuccess(true);
return result;
}
/**
* 通过id删除
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name="id",required=true) String id) {
Result<String> result = new Result<String>();
boolean ok = joaAppService.deleteById(id);
if(ok) {
result.success("删除成功!");
}
return result;
}
}
@@ -0,0 +1,180 @@
package org.jeecg.modules.joa.controller;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.modules.extbpm.process.common.CodeUtil;
import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals;
import org.jeecg.modules.joa.entity.JoaBusinesStrip;
import org.jeecg.modules.joa.service.IJoaBusinesStripService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
/**
* @Title: Controller
* @Description: 出差表
* @author jeecg-boot
* @date 2019-04-09
* @version V1.0
*/
@RestController
@RequestMapping("/joa/joaBusinesStrip")
@Slf4j
public class JoaBusinesStripController {
@Autowired
private IJoaBusinesStripService joaBusinesStripService;
/**
* 分页列表查询
* @param joaBusinesStrip
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<JoaBusinesStrip>> queryPageList(JoaBusinesStrip joaBusinesStrip,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<IPage<JoaBusinesStrip>> result = new Result<IPage<JoaBusinesStrip>>();
QueryWrapper<JoaBusinesStrip> queryWrapper = QueryGenerator.initQueryWrapper(joaBusinesStrip, req.getParameterMap());
Page<JoaBusinesStrip> page = new Page<JoaBusinesStrip>(pageNo, pageSize);
IPage<JoaBusinesStrip> pageList = joaBusinesStripService.page(page, queryWrapper);
result.setSuccess(true);
result.setResult(pageList);
return result;
}
/**
* 添加
* @param joaBusinesStrip
* @return
*/
@PostMapping(value = "/add")
public Result<JoaBusinesStrip> add(@RequestBody JoaBusinesStrip joaBusinesStrip,HttpServletRequest request) {
Result<JoaBusinesStrip> result = new Result<JoaBusinesStrip>();
try {
joaBusinesStrip.setApplyNo(CodeUtil.getUUIDNum());
joaBusinesStrip.setBpmStatus(WorkFlowGlobals.BPM_BUS_STATUS_1);
String username = JwtUtil.getUserNameByToken(request);
joaBusinesStrip.setApplyUserName(username);
joaBusinesStripService.save(joaBusinesStrip);
result.setResult(joaBusinesStrip);
result.success("添加成功!");
} catch (Exception e) {
log.error(e.getMessage(),e);
result.error500("操作失败");
}
return result;
}
/**
* 编辑
* @param joaBusinesStrip
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<JoaBusinesStrip> edit(@RequestBody JoaBusinesStrip joaBusinesStrip) {
Result<JoaBusinesStrip> result = new Result<JoaBusinesStrip>();
JoaBusinesStrip joaBusinesStripEntity = joaBusinesStripService.getById(joaBusinesStrip.getId());
if(joaBusinesStripEntity==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaBusinesStripService.updateById(joaBusinesStrip);
//TODO 返回false说明什么?
if(ok) {
result.success("修改成功!");
}
}
return result;
}
/**
* 通过id删除
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<JoaBusinesStrip> delete(@RequestParam(name="id",required=true) String id) {
Result<JoaBusinesStrip> result = new Result<JoaBusinesStrip>();
JoaBusinesStrip joaBusinesStrip = joaBusinesStripService.getById(id);
if(joaBusinesStrip==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaBusinesStripService.removeById(id);
if(ok) {
result.success("删除成功!");
}
}
return result;
}
/**
* 批量删除
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<JoaBusinesStrip> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
Result<JoaBusinesStrip> result = new Result<JoaBusinesStrip>();
if(ids==null || "".equals(ids.trim())) {
result.error500("参数不识别!");
}else {
this.joaBusinesStripService.removeByIds(Arrays.asList(ids.split(",")));
result.success("删除成功!");
}
return result;
}
/**
* 通过id查询
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<JoaBusinesStrip> queryById(@RequestParam(name="id",required=true) String id) {
Result<JoaBusinesStrip> result = new Result<JoaBusinesStrip>();
JoaBusinesStrip joaBusinesStrip = joaBusinesStripService.getById(id);
if(joaBusinesStrip==null) {
result.error500("未找到对应实体");
}else {
result.setResult(joaBusinesStrip);
result.setSuccess(true);
}
return result;
}
/**
* 通过tripApplyNo查询出差单
* @param tripApplyNo
* @return
*/
@GetMapping(value = "/queryByTripApplyNo")
public Result<JoaBusinesStrip> queryByTripApplyNo(@RequestParam(name="tripApplyNo",required=true) String tripApplyNo) {
Result<JoaBusinesStrip> result = new Result<JoaBusinesStrip>();
QueryWrapper<JoaBusinesStrip> queryWrapper = new QueryWrapper<JoaBusinesStrip>();
queryWrapper.lambda().eq(JoaBusinesStrip::getApplyNo, tripApplyNo);
JoaBusinesStrip joaBusinesStrip = joaBusinesStripService.getOne(queryWrapper);
if(joaBusinesStrip==null) {
result.error500("未找到对应实体");
}else {
result.setResult(joaBusinesStrip);
result.setSuccess(true);
}
return result;
}
}
@@ -0,0 +1,160 @@
package org.jeecg.modules.joa.controller;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.joa.entity.JoaDocReceiving;
import org.jeecg.modules.joa.service.IJoaDocReceivingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
/**
* @Title: Controller
* @Description: 收文表
* @author jeecg-boot
* @date 2019-04-15
* @version V1.0
*/
@RestController
@RequestMapping("/joa/joaDocReceiving")
@Slf4j
public class JoaDocReceivingController {
@Autowired
private IJoaDocReceivingService joaDocReceivingService;
/**
* 分页列表查询
* @param joaDocReceiving
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<JoaDocReceiving>> queryPageList(JoaDocReceiving joaDocReceiving,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<IPage<JoaDocReceiving>> result = new Result<IPage<JoaDocReceiving>>();
QueryWrapper<JoaDocReceiving> queryWrapper = QueryGenerator.initQueryWrapper(joaDocReceiving, req.getParameterMap());
Page<JoaDocReceiving> page = new Page<JoaDocReceiving>(pageNo, pageSize);
IPage<JoaDocReceiving> pageList = joaDocReceivingService.page(page, queryWrapper);
result.setSuccess(true);
result.setResult(pageList);
return result;
}
/**
* 添加
* @param joaDocReceiving
* @return
*/
@PostMapping(value = "/add")
public Result<JoaDocReceiving> add(@RequestBody JoaDocReceiving joaDocReceiving) {
Result<JoaDocReceiving> result = new Result<JoaDocReceiving>();
try {
joaDocReceivingService.save(joaDocReceiving);
result.setResult(joaDocReceiving);
result.success("添加成功!");
} catch (Exception e) {
log.error(e.getMessage(),e);
result.error500("操作失败");
}
return result;
}
/**
* 编辑
* @param joaDocReceiving
* @return
*/
@PutMapping(value = "/edit")
public Result<JoaDocReceiving> edit(@RequestBody JoaDocReceiving joaDocReceiving) {
Result<JoaDocReceiving> result = new Result<JoaDocReceiving>();
JoaDocReceiving joaDocReceivingEntity = joaDocReceivingService.getById(joaDocReceiving.getId());
if(joaDocReceivingEntity==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaDocReceivingService.updateById(joaDocReceiving);
//TODO 返回false说明什么?
if(ok) {
result.success("修改成功!");
}
}
return result;
}
/**
* 通过id删除
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<JoaDocReceiving> delete(@RequestParam(name="id",required=true) String id) {
Result<JoaDocReceiving> result = new Result<JoaDocReceiving>();
JoaDocReceiving joaDocReceiving = joaDocReceivingService.getById(id);
if(joaDocReceiving==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaDocReceivingService.removeById(id);
if(ok) {
result.success("删除成功!");
}
}
return result;
}
/**
* 批量删除
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<JoaDocReceiving> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
Result<JoaDocReceiving> result = new Result<JoaDocReceiving>();
if(ids==null || "".equals(ids.trim())) {
result.error500("参数不识别!");
}else {
this.joaDocReceivingService.removeByIds(Arrays.asList(ids.split(",")));
result.success("删除成功!");
}
return result;
}
/**
* 通过id查询
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<JoaDocReceiving> queryById(@RequestParam(name="id",required=true) String id) {
Result<JoaDocReceiving> result = new Result<JoaDocReceiving>();
JoaDocReceiving joaDocReceiving = joaDocReceivingService.getById(id);
if(joaDocReceiving==null) {
result.error500("未找到对应实体");
}else {
result.setResult(joaDocReceiving);
result.setSuccess(true);
}
return result;
}
}
@@ -0,0 +1,162 @@
package org.jeecg.modules.joa.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.FillRuleConstant;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.FillRuleUtil;
import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals;
import org.jeecg.modules.joa.entity.JoaDocSending;
import org.jeecg.modules.joa.service.IJoaDocSendingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
/**
* @Title: Controller
* @Description: 出差表
* @author jeecg-boot
* @date 2019-04-10
* @version V1.0
*/
@RestController
@RequestMapping("/joa/joaDocSending")
@Slf4j
public class JoaDocSendingController {
@Autowired
private IJoaDocSendingService joaDocSendingService;
/**
* 分页列表查询
* @param joaDocSending
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<JoaDocSending>> queryPageList(JoaDocSending joaDocSending,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<IPage<JoaDocSending>> result = new Result<IPage<JoaDocSending>>();
QueryWrapper<JoaDocSending> queryWrapper = QueryGenerator.initQueryWrapper(joaDocSending, req.getParameterMap());
Page<JoaDocSending> page = new Page<JoaDocSending>(pageNo, pageSize);
IPage<JoaDocSending> pageList = joaDocSendingService.page(page, queryWrapper);
result.setSuccess(true);
result.setResult(pageList);
return result;
}
/**
* 添加
* @param joaDocSending
* @return
*/
@PostMapping(value = "/add")
public Result<JoaDocSending> add(@RequestBody JoaDocSending joaDocSending) {
Result<JoaDocSending> result = new Result<JoaDocSending>();
try {
joaDocSending.setBpmStatus(WorkFlowGlobals.BPM_BUS_STATUS_1);
JSONObject formData = (JSONObject) JSONObject.toJSON(joaDocSending);
String docCode = (String)FillRuleUtil.executeRule(FillRuleConstant.DOC_SEND,formData);
joaDocSending.setDocCode(docCode);
joaDocSending.setBookDate(new Date());
joaDocSendingService.save(joaDocSending);
result.setResult(joaDocSending);
result.success("添加成功!");
} catch (Exception e) {
log.error(e.getMessage(),e);
result.error500("操作失败");
}
return result;
}
/**
* 编辑
* @param joaDocSending
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<JoaDocSending> edit(@RequestBody JoaDocSending joaDocSending) {
Result<JoaDocSending> result = new Result<JoaDocSending>();
JoaDocSending joaDocSendingEntity = joaDocSendingService.getById(joaDocSending.getId());
if(joaDocSendingEntity==null) {
result.error500("未找到对应实体");
}else {
JSONObject formData = (JSONObject) JSONObject.toJSON(joaDocSending);
String docCode = (String)FillRuleUtil.executeRule(FillRuleConstant.DOC_SEND,formData);
joaDocSending.setDocCode(docCode);
boolean ok = joaDocSendingService.updateById(joaDocSending);
//TODO 返回false说明什么?
if(ok) {
result.success("修改成功!");
}
}
return result;
}
/**
* 通过id删除
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<JoaDocSending> delete(@RequestParam(name="id",required=true) String id) {
Result<JoaDocSending> result = new Result<JoaDocSending>();
JoaDocSending joaDocSending = joaDocSendingService.getById(id);
if(joaDocSending==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaDocSendingService.removeById(id);
if(ok) {
result.success("删除成功!");
}
}
return result;
}
/**
* 批量删除
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<JoaDocSending> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
Result<JoaDocSending> result = new Result<JoaDocSending>();
if(ids==null || "".equals(ids.trim())) {
result.error500("参数不识别!");
}else {
this.joaDocSendingService.removeByIds(Arrays.asList(ids.split(",")));
result.success("删除成功!");
}
return result;
}
/**
* 通过id查询
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<JoaDocSending> queryById(@RequestParam(name="id",required=true) String id) {
Result<JoaDocSending> result = new Result<JoaDocSending>();
JoaDocSending joaDocSending = joaDocSendingService.getById(id);
if(joaDocSending==null) {
result.error500("未找到对应实体");
}else {
result.setResult(joaDocSending);
result.setSuccess(true);
}
return result;
}
}
@@ -0,0 +1,182 @@
package org.jeecg.modules.joa.controller;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.extbpm.process.common.CodeUtil;
import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals;
import org.jeecg.modules.joa.entity.JoaEmployeeLeave;
import org.jeecg.modules.joa.model.JoaBizLeaveModel;
import org.jeecg.modules.joa.service.IJoaEmployeeLeaveService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
/**
* @Title: Controller
* @Description: 请假申请表
* @author jeecg-boot
* @date 2019-04-08
* @version V1.0
*/
@RestController
@RequestMapping("/joa/joaEmployeeLeave")
@Slf4j
public class JoaEmployeeLeaveController {
@Autowired
private IJoaEmployeeLeaveService joaEmployeeLeaveService;
/**
* 分页列表查询
* @param joaEmployeeLeave
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<JoaEmployeeLeave>> queryPageList(JoaEmployeeLeave joaEmployeeLeave,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<IPage<JoaEmployeeLeave>> result = new Result<IPage<JoaEmployeeLeave>>();
QueryWrapper<JoaEmployeeLeave> queryWrapper = QueryGenerator.initQueryWrapper(joaEmployeeLeave, req.getParameterMap());
Page<JoaEmployeeLeave> page = new Page<JoaEmployeeLeave>(pageNo, pageSize);
IPage<JoaEmployeeLeave> pageList = joaEmployeeLeaveService.page(page, queryWrapper);
result.setSuccess(true);
result.setResult(pageList);
return result;
}
/**
* 添加
* @param joaEmployeeLeave
* @return
*/
@PostMapping(value = "/add")
public Result<JoaEmployeeLeave> add(@RequestBody JoaEmployeeLeave joaEmployeeLeave) {
Result<JoaEmployeeLeave> result = new Result<JoaEmployeeLeave>();
try {
joaEmployeeLeave.setApplyNo(CodeUtil.getUUIDNum());
joaEmployeeLeave.setBpmStatus(WorkFlowGlobals.BPM_BUS_STATUS_1);
joaEmployeeLeaveService.save(joaEmployeeLeave);
result.setResult(joaEmployeeLeave);
result.success("添加成功!");
} catch (Exception e) {
log.error(e.getMessage(),e);
result.error500("操作失败");
}
return result;
}
/**
* 编辑
* @param joaEmployeeLeave
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<JoaEmployeeLeave> edit(@RequestBody JoaEmployeeLeave joaEmployeeLeave) {
Result<JoaEmployeeLeave> result = new Result<JoaEmployeeLeave>();
JoaEmployeeLeave joaEmployeeLeaveEntity = joaEmployeeLeaveService.getById(joaEmployeeLeave.getId());
if(joaEmployeeLeaveEntity==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaEmployeeLeaveService.updateById(joaEmployeeLeave);
//TODO 返回false说明什么?
if(ok) {
result.success("修改成功!");
}
}
return result;
}
/**
* 通过id删除
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<JoaEmployeeLeave> delete(@RequestParam(name="id",required=true) String id) {
Result<JoaEmployeeLeave> result = new Result<JoaEmployeeLeave>();
JoaEmployeeLeave joaEmployeeLeave = joaEmployeeLeaveService.getById(id);
if(joaEmployeeLeave==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaEmployeeLeaveService.removeById(id);
if(ok) {
result.success("删除成功!");
}
}
return result;
}
/**
* 批量删除
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<JoaEmployeeLeave> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
Result<JoaEmployeeLeave> result = new Result<JoaEmployeeLeave>();
if(ids==null || "".equals(ids.trim())) {
result.error500("参数不识别!");
}else {
this.joaEmployeeLeaveService.removeByIds(Arrays.asList(ids.split(",")));
result.success("删除成功!");
}
return result;
}
/**
* 通过id查询
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<JoaEmployeeLeave> queryById(@RequestParam(name="id",required=true) String id) {
Result<JoaEmployeeLeave> result = new Result<JoaEmployeeLeave>();
JoaEmployeeLeave joaEmployeeLeave = joaEmployeeLeaveService.getById(id);
if(joaEmployeeLeave==null) {
result.error500("未找到对应实体");
}else {
result.setResult(joaEmployeeLeave);
result.setSuccess(true);
}
return result;
}
/**
* 【工作流】基于业务的流程操作模式
* @param joaBizLeave
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/taskList")
public Result<Page<JoaBizLeaveModel>> taskList(JoaBizLeaveModel joaBizLeave,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<Page<JoaBizLeaveModel>> result = new Result<Page<JoaBizLeaveModel>>();
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
joaBizLeave.setAssignee(sysUser.getUsername());
Page<JoaBizLeaveModel> pageList = joaEmployeeLeaveService.queryTaskList(joaBizLeave,pageNo,pageSize);
result.setSuccess(true);
result.setResult(pageList);
return result;
}
}
@@ -0,0 +1,198 @@
package org.jeecg.modules.joa.controller;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.extbpm.process.common.CodeUtil;
import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals;
import org.jeecg.modules.joa.entity.JoaLoan;
import org.jeecg.modules.joa.service.IJoaLoanService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
/**
* @Title: Controller
* @Description: 借款表
* @author jeecg-boot
* @date 2019-04-08
* @version V1.0
*/
@RestController
@RequestMapping("/joa/joaLoan")
@Slf4j
public class JoaLoanController {
@Autowired
private IJoaLoanService joaLoanService;
/**
* 分页列表查询
* @param joaLoan
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<JoaLoan>> queryPageList(JoaLoan joaLoan,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<IPage<JoaLoan>> result = new Result<IPage<JoaLoan>>();
QueryWrapper<JoaLoan> queryWrapper = QueryGenerator.initQueryWrapper(joaLoan, req.getParameterMap());
Page<JoaLoan> page = new Page<JoaLoan>(pageNo, pageSize);
IPage<JoaLoan> pageList = joaLoanService.page(page, queryWrapper);
result.setSuccess(true);
result.setResult(pageList);
return result;
}
/**
* 添加
* @param joaLoan
* @return
*/
@PostMapping(value = "/add")
public Result<JoaLoan> add(@RequestBody JoaLoan joaLoan) {
Result<JoaLoan> result = new Result<JoaLoan>();
try {
joaLoan.setApplyNo(CodeUtil.getUUIDNum());
joaLoanService.save(joaLoan);
result.setResult(joaLoan);
result.success("添加成功!");
} catch (Exception e) {
log.error(e.getMessage(),e);
result.error500("操作失败");
}
return result;
}
/**
* 子流程添加
* @param joaLoan
* @return
*/
@PostMapping(value = "/addForSub")
public Result<JoaLoan> addForSub(@RequestBody JoaLoan joaLoan) {
Result<JoaLoan> result = new Result<JoaLoan>();
try {
joaLoan.setApplyNo(CodeUtil.getUUIDNum());
joaLoan.setBpmStatus(WorkFlowGlobals.BPM_BUS_STATUS_2);
joaLoanService.save(joaLoan);
result.success("添加成功!");
} catch (Exception e) {
log.error(e.getMessage(),e);
result.error500("操作失败");
}
return result;
}
/**
* 编辑
* @param joaLoan
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<JoaLoan> edit(@RequestBody JoaLoan joaLoan) {
Result<JoaLoan> result = new Result<JoaLoan>();
JoaLoan joaLoanEntity = joaLoanService.getById(joaLoan.getId());
if(joaLoanEntity==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaLoanService.updateById(joaLoan);
//TODO 返回false说明什么?
if(ok) {
result.success("修改成功!");
}
}
return result;
}
/**
* 通过id删除
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<JoaLoan> delete(@RequestParam(name="id",required=true) String id) {
Result<JoaLoan> result = new Result<JoaLoan>();
JoaLoan joaLoan = joaLoanService.getById(id);
if(joaLoan==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaLoanService.removeById(id);
if(ok) {
result.success("删除成功!");
}
}
return result;
}
/**
* 批量删除
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<JoaLoan> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
Result<JoaLoan> result = new Result<JoaLoan>();
if(ids==null || "".equals(ids.trim())) {
result.error500("参数不识别!");
}else {
this.joaLoanService.removeByIds(Arrays.asList(ids.split(",")));
result.success("删除成功!");
}
return result;
}
/**
* 通过id查询
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<JoaLoan> queryById(@RequestParam(name="id",required=true) String id) {
Result<JoaLoan> result = new Result<JoaLoan>();
JoaLoan joaLoan = joaLoanService.getById(id);
if(joaLoan==null) {
result.error500("未找到对应实体");
}else {
result.setResult(joaLoan);
result.setSuccess(true);
}
return result;
}
/**
* 通过tripApplyNo查询借款单
* @param tripApplyNo
* @return
*/
@GetMapping(value = "/queryByTripApplyNo")
public Result<JoaLoan> queryByTripApplyNo(@RequestParam(name="id",required=true) String tripApplyNo) {
Result<JoaLoan> result = new Result<JoaLoan>();
LambdaQueryWrapper<JoaLoan> queryWrapper = new LambdaQueryWrapper<JoaLoan>();
queryWrapper.eq(JoaLoan::getTripApplyNo, tripApplyNo);
JoaLoan joaLoan = joaLoanService.getOne(queryWrapper);
if(joaLoan==null) {
result.error500("未找到对应实体");
}else {
result.setResult(joaLoan);
result.setSuccess(true);
}
return result;
}
}
@@ -0,0 +1,160 @@
package org.jeecg.modules.joa.controller;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.joa.entity.JoaOvertime;
import org.jeecg.modules.joa.service.IJoaOvertimeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
/**
* @Title: Controller
* @Description: 加班申请单
* @author jeecg-boot
* @date 2019-04-01
* @version V1.0
*/
@RestController
@RequestMapping("/joa/joaOvertime")
@Slf4j
public class JoaOvertimeController {
@Autowired
private IJoaOvertimeService joaOvertimeService;
/**
* 分页列表查询
* @param joaOvertime
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<JoaOvertime>> queryPageList(JoaOvertime joaOvertime,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<IPage<JoaOvertime>> result = new Result<IPage<JoaOvertime>>();
QueryWrapper<JoaOvertime> queryWrapper = QueryGenerator.initQueryWrapper(joaOvertime, req.getParameterMap());
Page<JoaOvertime> page = new Page<JoaOvertime>(pageNo, pageSize);
IPage<JoaOvertime> pageList = joaOvertimeService.page(page, queryWrapper);
result.setSuccess(true);
result.setResult(pageList);
return result;
}
/**
* 添加
* @param joaOvertime
* @return
*/
@PostMapping(value = "/add")
public Result<JoaOvertime> add(@RequestBody JoaOvertime joaOvertime) {
Result<JoaOvertime> result = new Result<JoaOvertime>();
try {
joaOvertimeService.save(joaOvertime);
result.setResult(joaOvertime);
result.success("添加成功!");
} catch (Exception e) {
log.error(e.getMessage(),e);
result.error500("操作失败");
}
return result;
}
/**
* 编辑
* @param joaOvertime
* @return
*/
@PutMapping(value = "/edit")
public Result<JoaOvertime> edit(@RequestBody JoaOvertime joaOvertime) {
Result<JoaOvertime> result = new Result<JoaOvertime>();
JoaOvertime joaOvertimeEntity = joaOvertimeService.getById(joaOvertime.getId());
if(joaOvertimeEntity==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaOvertimeService.updateById(joaOvertime);
//TODO 返回false说明什么?
if(ok) {
result.success("修改成功!");
}
}
return result;
}
/**
* 通过id删除
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<JoaOvertime> delete(@RequestParam(name="id",required=true) String id) {
Result<JoaOvertime> result = new Result<JoaOvertime>();
JoaOvertime joaOvertime = joaOvertimeService.getById(id);
if(joaOvertime==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaOvertimeService.removeById(id);
if(ok) {
result.success("删除成功!");
}
}
return result;
}
/**
* 批量删除
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<JoaOvertime> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
Result<JoaOvertime> result = new Result<JoaOvertime>();
if(ids==null || "".equals(ids.trim())) {
result.error500("参数不识别!");
}else {
this.joaOvertimeService.removeByIds(Arrays.asList(ids.split(",")));
result.success("删除成功!");
}
return result;
}
/**
* 通过id查询
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<JoaOvertime> queryById(@RequestParam(name="id",required=true) String id) {
Result<JoaOvertime> result = new Result<JoaOvertime>();
JoaOvertime joaOvertime = joaOvertimeService.getById(id);
if(joaOvertime==null) {
result.error500("未找到对应实体");
}else {
result.setResult(joaOvertime);
result.setSuccess(true);
}
return result;
}
}
@@ -0,0 +1,160 @@
package org.jeecg.modules.joa.controller;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.joa.entity.JoaOvertimeDetail;
import org.jeecg.modules.joa.service.IJoaOvertimeDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
/**
* @Title: Controller
* @Description: 调休明细表
* @author jeecg-boot
* @date 2019-04-03
* @version V1.0
*/
@RestController
@RequestMapping("/joa/joaOvertimeDetail")
@Slf4j
public class JoaOvertimeDetailController {
@Autowired
private IJoaOvertimeDetailService joaOvertimeDetailService;
/**
* 分页列表查询
* @param joaOvertimeDetail
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<JoaOvertimeDetail>> queryPageList(JoaOvertimeDetail joaOvertimeDetail,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<IPage<JoaOvertimeDetail>> result = new Result<IPage<JoaOvertimeDetail>>();
QueryWrapper<JoaOvertimeDetail> queryWrapper = QueryGenerator.initQueryWrapper(joaOvertimeDetail, req.getParameterMap());
Page<JoaOvertimeDetail> page = new Page<JoaOvertimeDetail>(pageNo, pageSize);
IPage<JoaOvertimeDetail> pageList = joaOvertimeDetailService.page(page, queryWrapper);
result.setSuccess(true);
result.setResult(pageList);
return result;
}
/**
* 添加
* @param joaOvertimeDetail
* @return
*/
@PostMapping(value = "/add")
public Result<JoaOvertimeDetail> add(@RequestBody JoaOvertimeDetail joaOvertimeDetail) {
Result<JoaOvertimeDetail> result = new Result<JoaOvertimeDetail>();
try {
joaOvertimeDetailService.save(joaOvertimeDetail);
result.setResult(joaOvertimeDetail);
result.success("添加成功!");
} catch (Exception e) {
log.error(e.getMessage(),e);
result.error500("操作失败");
}
return result;
}
/**
* 编辑
* @param joaOvertimeDetail
* @return
*/
@PutMapping(value = "/edit")
public Result<JoaOvertimeDetail> edit(@RequestBody JoaOvertimeDetail joaOvertimeDetail) {
Result<JoaOvertimeDetail> result = new Result<JoaOvertimeDetail>();
JoaOvertimeDetail joaOvertimeDetailEntity = joaOvertimeDetailService.getById(joaOvertimeDetail.getId());
if(joaOvertimeDetailEntity==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaOvertimeDetailService.updateById(joaOvertimeDetail);
//TODO 返回false说明什么?
if(ok) {
result.success("修改成功!");
}
}
return result;
}
/**
* 通过id删除
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<JoaOvertimeDetail> delete(@RequestParam(name="id",required=true) String id) {
Result<JoaOvertimeDetail> result = new Result<JoaOvertimeDetail>();
JoaOvertimeDetail joaOvertimeDetail = joaOvertimeDetailService.getById(id);
if(joaOvertimeDetail==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaOvertimeDetailService.removeById(id);
if(ok) {
result.success("删除成功!");
}
}
return result;
}
/**
* 批量删除
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<JoaOvertimeDetail> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
Result<JoaOvertimeDetail> result = new Result<JoaOvertimeDetail>();
if(ids==null || "".equals(ids.trim())) {
result.error500("参数不识别!");
}else {
this.joaOvertimeDetailService.removeByIds(Arrays.asList(ids.split(",")));
result.success("删除成功!");
}
return result;
}
/**
* 通过id查询
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<JoaOvertimeDetail> queryById(@RequestParam(name="id",required=true) String id) {
Result<JoaOvertimeDetail> result = new Result<JoaOvertimeDetail>();
JoaOvertimeDetail joaOvertimeDetail = joaOvertimeDetailService.getById(id);
if(joaOvertimeDetail==null) {
result.error500("未找到对应实体");
}else {
result.setResult(joaOvertimeDetail);
result.setSuccess(true);
}
return result;
}
}
@@ -0,0 +1,198 @@
package org.jeecg.modules.joa.controller;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.joa.entity.JoaOvertime;
import org.jeecg.modules.joa.entity.JoaOvertimeLeave;
import org.jeecg.modules.joa.service.IJoaOvertimeLeaveService;
import org.jeecg.modules.joa.service.IJoaOvertimeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
/**
* @Title: Controller
* @Description: 调休申请表
* @author jeecg-boot
* @date 2019-04-08
* @version V1.0
*/
@RestController
@RequestMapping("/joa/joaOvertimeLeave")
@Slf4j
public class JoaOvertimeLeaveController {
@Autowired
private IJoaOvertimeLeaveService joaOvertimeLeaveService;
@Autowired
private IJoaOvertimeService joaOvertimeService;
/**
* 分页列表查询
* @param joaOvertimeLeave
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<JoaOvertimeLeave>> queryPageList(JoaOvertimeLeave joaOvertimeLeave,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<IPage<JoaOvertimeLeave>> result = new Result<IPage<JoaOvertimeLeave>>();
QueryWrapper<JoaOvertimeLeave> queryWrapper = QueryGenerator.initQueryWrapper(joaOvertimeLeave, req.getParameterMap());
Page<JoaOvertimeLeave> page = new Page<JoaOvertimeLeave>(pageNo, pageSize);
IPage<JoaOvertimeLeave> pageList = joaOvertimeLeaveService.page(page, queryWrapper);
result.setSuccess(true);
result.setResult(pageList);
return result;
}
/**
* 添加
* @param joaOvertimeLeave
* @return
*/
@PostMapping(value = "/add")
public Result<JoaOvertimeLeave> add(@RequestBody JoaOvertimeLeave joaOvertimeLeave) {
Result<JoaOvertimeLeave> result = new Result<JoaOvertimeLeave>();
try {
boolean istrue=joaOvertimeLeaveService.joaOvertimeLeaveAdd(joaOvertimeLeave);
if(istrue) {
result.setResult(joaOvertimeLeave);
result.success("添加成功!");
}else {
result.success("添加失败!");
}
} catch (Exception e) {
log.error(e.getMessage(),e);
result.error500("操作失败");
}
return result;
}
/**
* 编辑
* @param joaOvertimeLeave
* @return
*/
@PutMapping(value = "/edit")
public Result<JoaOvertimeLeave> edit(@RequestBody JoaOvertimeLeave joaOvertimeLeave) {
Result<JoaOvertimeLeave> result = new Result<JoaOvertimeLeave>();
JoaOvertimeLeave joaOvertimeLeaveEntity = joaOvertimeLeaveService.getById(joaOvertimeLeave.getId());
if(joaOvertimeLeaveEntity==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaOvertimeLeaveService.updateById(joaOvertimeLeave);
//TODO 返回false说明什么?
if(ok) {
result.success("修改成功!");
}
}
return result;
}
/**
* 通过id删除
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<JoaOvertimeLeave> delete(@RequestParam(name="id",required=true) String id) {
Result<JoaOvertimeLeave> result = new Result<JoaOvertimeLeave>();
JoaOvertimeLeave joaOvertimeLeave = joaOvertimeLeaveService.getById(id);
if(joaOvertimeLeave==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaOvertimeLeaveService.removeById(id);
if(ok) {
result.success("删除成功!");
}
}
return result;
}
/**
* 批量删除
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<JoaOvertimeLeave> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
Result<JoaOvertimeLeave> result = new Result<JoaOvertimeLeave>();
if(ids==null || "".equals(ids.trim())) {
result.error500("参数不识别!");
}else {
this.joaOvertimeLeaveService.removeByIds(Arrays.asList(ids.split(",")));
result.success("删除成功!");
}
return result;
}
/**
* 通过id查询
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<JoaOvertimeLeave> queryById(@RequestParam(name="id",required=true) String id) {
Result<JoaOvertimeLeave> result = new Result<JoaOvertimeLeave>();
JoaOvertimeLeave joaOvertimeLeave = joaOvertimeLeaveService.getById(id);
if(joaOvertimeLeave==null) {
result.error500("未找到对应实体");
}else {
result.setResult(joaOvertimeLeave);
result.setSuccess(true);
}
return result;
}
/**
* 根据部门和申请人查询加班表信息
*/
@SuppressWarnings("unused")
@GetMapping(value = "/queryByAppUser")
public Result<List<JoaOvertime>> queryByAppUser(@RequestParam(name="applyUser",required=true) String applyUser,
@RequestParam(name="department",required=true)String department) {
Result<List<JoaOvertime>> result = new Result<List<JoaOvertime>>();
QueryWrapper<JoaOvertime> queryWrapper =new QueryWrapper<JoaOvertime>();
queryWrapper.eq("apply_user", applyUser);
queryWrapper.eq("department", department);
queryWrapper.notInSql("id", "select id from joa_overtime where days_unpaid=0 and hour_unpaid=0");
List<JoaOvertime> list=joaOvertimeService.list(queryWrapper);
if(list==null) {
result.error500("未找到对应实体");
}else {
result.setResult(list);
result.setSuccess(true);
}
return result;
}
}
@@ -0,0 +1,61 @@
package org.jeecg.modules.joa.datastatistics;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
/**
* @Title: Controller
* @Description: 图表data
* @author jeecg-boot
* @date 2019-04-09
* @version V1.0
*/
@RestController
@RequestMapping("/joa/dataStatistics")
@Slf4j
public class DataStatisticsController {
/**
* 获取图表数据
* @param req
* @return
*/
@GetMapping(value = "/getChartDate")
public String getChartDate(HttpServletRequest req) {
return readJson("classpath:org/jeecg/modules/joa/datastatistics/mockjson/getChartDateInfo.json");
}
/**
* 获取饼表数据
* @param req
* @return
*/
@GetMapping(value = "/getPieDate")
public String getPieDate(HttpServletRequest req) {
return readJson("classpath:org/jeecg/modules/joa/datastatistics/mockjson/getPieDateInfo.json");
}
/**
* 读取json格式文件
* @param jsonSrc
* @return
*/
private String readJson(String jsonSrc) {
String json = "";
try {
//File jsonFile = ResourceUtils.getFile(jsonSrc);
//json = FileUtils.re.readFileToString(jsonFile);
//换个写法,解决springboot读取jar包中文件的问题
InputStream stream = getClass().getClassLoader().getResourceAsStream(jsonSrc.replace("classpath:", ""));
json = IOUtils.toString(stream,"UTF-8");
} catch (IOException e) {
log.error(e.getMessage(),e);
}
return json;
}
}
@@ -0,0 +1,15 @@
{
"success": true,
"message": "查询成功",
"code": null,
"result": [
{ "State": "请假", "流转中": 2, "已归档": 8 },
{ "State": "出差", "流转中": 4, "已归档": 6 },
{ "State": "加班", "流转中": 6, "已归档": 4 },
{ "State": "用车", "流转中": 4, "已归档": 2 },
{ "State": "面试", "流转中": 2, "已归档": 1 },
{ "State": "录用", "流转中": 1, "已归档": 4 }
],
"timestamp": 1554285003594,
"xxx-sign": "NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY="
}
@@ -0,0 +1,29 @@
{
"success": true,
"message": "查询成功",
"code": null,
"result": [
{
"item": "资产管理类",
"count": 40
},
{
"item": "行政管理类",
"count": 10
},
{
"item": "人事管理类",
"count": 15
},
{
"item": "合同管理类",
"count": 30
},
{
"item": "公文管理类",
"count": 5
}
],
"timestamp": 1554285003594,
"xxx-sign": "NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY="
}

Some files were not shown because too many files have changed in this diff Show More