diff --git a/README.md b/README.md index 1779bdd5bab8b4891f07a0abf54fb6cfa67ba8f1..1f9610de59f6556ad16609a9192f47a9b7679d24 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ * 内置功能: * 目录结构: * 架构特点: +* 参数配置: * 开发规范: * 代码生成: @@ -141,7 +142,7 @@ ### 本地运行 -1. 环境准备:`JDK 17+`、`Maven 3.8+`、使用 `MySQL 5.7 or 8.x` 数据库、[其它数据库](https://jeesite.com/docs/technology/#_8、已支持数据库) +1. 环境准备:`JDK 17+`、`Maven 3.8+`、使用 `MySQL 8.0+` 数据库、[其它数据库](https://jeesite.com/docs/technology/#_8、已支持数据库) 2. 下载源码: 并解压 3. 打开文件:`/web/src/main/resources/config/application.yml` 配置JDBC连接(建立一个新库) 4. 执行脚本:`/web/bin/init-data.bat(sh)` 初始化数据库(自动往新库里创建表和初始数据) @@ -149,10 +150,11 @@ 6. 浏览器访问: 账号 system 密码 admin 7. 部署常见问题: 8. 分离端安装: +9. 分离端常见问题: ### 快速运行 -1. 环境准备:`JDK 17+`、`Maven 3.8+`、无需准备数据库(使用内嵌 H2 DB、Vue资源包) +1. 环境准备:`JDK 17+`、`Maven 3.8+`、无需准备数据库(使用内嵌 H2 DB、包含 Vue 和 全栈双版本) 2. 下载源码: 并解压 3. 执行脚本:`/web-fast/bin/run-tomcat.bat(sh)` 启动服务即可(无需手动建库,自动初始化数据库) 4. Vue分离版本地址: @@ -172,42 +174,50 @@ docker run --name jeesite-web -p 8980:8980 -d --restart unless-stopped \ -v ~/:/data thinkgem/jeesite-web && docker logs -f jeesite-web ``` - 浏览器访问: 账号 system 密码 admin -- 分离端安装: ### 开发环境 1. 部署运行文档: 2. 部署常见问题: 3. 分离端运行文档: -4. 分离端常见问题: +4. 分离端常见问题: ## 技术文章 -* 菜单和按钮权限: -* 强大的数据权限: -* 表结构数据字典: -* 持久层设计: -* 后端工具: +* 库表生成、代码生成:https://jeesite.com/docs/code-gen/> +* 菜单权限、按钮权限: +* 数据权限、库事务: +* 表结构、数据字典: +* 持久层框架、SQL: +* 后端常用工具: **分离版** +* 版本介绍: * 源码解析: * 表单组件: * 表格组件: +* 参数配置: * 常用组件: +* 前端权限: +* 图标组件: +* 前端样式库: +* 多语言国际化: **经典版** * 表单组件: * 表格组件: * 常用工具: +* 自定义主题: ## 专题文章 -* 自定义主题: -* 国际化多语言: -* 接口文档: +* 系统接口文档: +* 多语言国际化: * BPM工作流引擎: +* CMS内容管理: +* AI知识库助手: * 用户类型: * 消息推送: * 单点登录: @@ -228,20 +238,9 @@ docker run --name jeesite-web -p 8980:8980 -d --restart unless-stopped \ * Spring Cloud 微服务: * 分布式事务 Seata: * 读写分离、分库分表: - -## 前后分离版 - -* Vue 版介绍: -* Vue 安装部署: -* Vue 参数配置: -* Vue 前端权限: -* Vue 源码解析: -* Vue 表单组件: -* Vue 表格组件: -* Vue 常用组件: -* Vue 图标组件: -* Vue 国际化多语言: -* Vue 样式库: +* 监控系统集成: +* 追踪系统集成: +* ELK日志收集: ## 授权协议声明 diff --git a/common/pom.xml b/common/pom.xml index 7c4a7a1338aa08bd49be2f776e456e79d1ab7007..76e537842c35d4ce2b706e709d6b7946f3b66198 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ com.jeesite jeesite-parent - 5.11.1.springboot3-SNAPSHOT + 5.12.1.springboot3-SNAPSHOT ../parent/pom.xml @@ -242,6 +242,11 @@ spring-web true + + org.springframework + spring-webmvc + true + org.springframework.boot spring-boot diff --git a/common/src/main/java/com/jeesite/common/codec/DigestUtils.java b/common/src/main/java/com/jeesite/common/codec/DigestUtils.java index 56c1b806bb56eb94348df66dbd43e52c1cd0a3be..a73a12a28689a4b4c613e437f608e8faf47de12e 100644 --- a/common/src/main/java/com/jeesite/common/codec/DigestUtils.java +++ b/common/src/main/java/com/jeesite/common/codec/DigestUtils.java @@ -19,6 +19,7 @@ import java.security.*; public class DigestUtils { public static final String SHA1 = "SHA-1"; + public static final String SHA256 = "SHA-256"; public static final String MD5 = "MD5"; public static final String SM3 = "SM3"; diff --git a/common/src/main/java/com/jeesite/common/codec/EncodeUtils.java b/common/src/main/java/com/jeesite/common/codec/EncodeUtils.java index fb13350ba7b88a78b54d7c303a2bf5e4f0be69e3..8f69f0f1ed552a93081815ad770af4b7a002db29 100644 --- a/common/src/main/java/com/jeesite/common/codec/EncodeUtils.java +++ b/common/src/main/java/com/jeesite/common/codec/EncodeUtils.java @@ -192,7 +192,7 @@ public class EncodeUtils { private static final List xssPatterns = ListUtils.newArrayList( Pattern.compile("(<\\s*(script|link|style|iframe)([\\s\\S]*?)(>|<\\/\\s*\\1\\s*>))|()", Pattern.CASE_INSENSITIVE), Pattern.compile("\\s*(href|src)\\s*=\\s*(\"\\s*(javascript|vbscript):[^\"]+\"|'\\s*(javascript|vbscript):[^']+'|(javascript|vbscript):[^\\s]+)\\s*(?=>)", Pattern.CASE_INSENSITIVE), - Pattern.compile("\\s*on[a-z]+\\s*=\\s*(\"[^\"]+\"|'[^']+'|[^\\s]+)\\s*(?=>)", Pattern.CASE_INSENSITIVE), + Pattern.compile("\\s*/?\\s*on[a-zA-Z]+\\s*=\\s*(['\"]?)(.*?)\\1(?=\\s|>|/>)", Pattern.CASE_INSENSITIVE), Pattern.compile("(eval\\((.*?)\\)|expression\\((.*?)\\))", Pattern.CASE_INSENSITIVE), Pattern.compile("^(javascript:|vbscript:)", Pattern.CASE_INSENSITIVE) ); diff --git a/common/src/main/java/com/jeesite/common/codec/Sha1Utils.java b/common/src/main/java/com/jeesite/common/codec/Sha1Utils.java index dd7718dfb27fefb9be4477519c57300a0f466e6d..323c48c2cb129ef914ee86e3ecb190c13c7fe4df 100644 --- a/common/src/main/java/com/jeesite/common/codec/Sha1Utils.java +++ b/common/src/main/java/com/jeesite/common/codec/Sha1Utils.java @@ -4,79 +4,11 @@ */ package com.jeesite.common.codec; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - /** * SHA-1 加密工具类,散列加密,不可逆加密 * @author ThinkGem */ -public class Sha1Utils { - - /** - * 生成随机的 Byte[] 作为 salt 密钥. - * @param numBytes byte数组的大小 - */ - public static byte[] genSalt(int numBytes) { - return DigestUtils.genSalt(numBytes); - } - - /** - * 生成随机的 Byte[] 作为 salt 密钥,返回 HEX 值 - * @param numBytes byte 数组的大小 - */ - public static String genSaltString(int numBytes) { - return DigestUtils.genSaltString(numBytes); - } - - /** - * 对输入字符串进行 SHA-1 散列. - */ - public static byte[] sha1(byte[] input) { - return DigestUtils.digest(input, DigestUtils.SHA1, null, 1); - } - - /** - * 对输入字符串进行 SHA-1 散列. - */ - public static String sha1(String input) { - return EncodeUtils.encodeHex(sha1(input.getBytes(StandardCharsets.UTF_8))); - } - - /** - * 对输入字符串进行 SHA-1 散列. - */ - public static byte[] sha1(byte[] input, byte[] salt) { - return DigestUtils.digest(input, DigestUtils.SHA1, salt, 1); - } - - /** - * 对输入字符串进行 SHA-1 散列. - */ - public static String sha1(String data, String salt) { - return EncodeUtils.encodeHex(sha1(data.getBytes(StandardCharsets.UTF_8), EncodeUtils.decodeHex(salt))); - } - - /** - * 对输入字符串进行 SHA-1 散列. - */ - public static byte[] sha1(byte[] input, byte[] salt, int iterations) { - return DigestUtils.digest(input, DigestUtils.SHA1, salt, iterations); - } - - /** - * 对输入字符串进行 SHA-1 散列. - */ - public static String sha1(String input, String salt, int iterations) { - return EncodeUtils.encodeHex(sha1(input.getBytes(StandardCharsets.UTF_8), EncodeUtils.decodeHex(salt), iterations)); - } - - /** - * 对文件进行 SHA-1 散列. - */ - public static byte[] sha1(InputStream input) throws IOException { - return DigestUtils.digest(input, DigestUtils.SHA1); - } +@Deprecated +public class Sha1Utils extends ShaUtils { } diff --git a/common/src/main/java/com/jeesite/common/codec/ShaUtils.java b/common/src/main/java/com/jeesite/common/codec/ShaUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..b93c8a4976e6ba7120570fe797ffa138c3466ce3 --- /dev/null +++ b/common/src/main/java/com/jeesite/common/codec/ShaUtils.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2013-Now http://jeesite.com All rights reserved. + * No deletion without permission, or be held responsible to law. + */ +package com.jeesite.common.codec; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * SHA-1 和 SHA-256 加密工具类,散列加密,不可逆加密 + * @author ThinkGem + */ +public class ShaUtils { + + /** + * 生成随机的 Byte[] 作为 salt 密钥. + * @param numBytes byte数组的大小 + */ + public static byte[] genSalt(int numBytes) { + return DigestUtils.genSalt(numBytes); + } + + /** + * 生成随机的 Byte[] 作为 salt 密钥,返回 HEX 值 + * @param numBytes byte 数组的大小 + */ + public static String genSaltString(int numBytes) { + return DigestUtils.genSaltString(numBytes); + } + + /** + * 对输入字符串进行 SHA-1 散列. + */ + public static byte[] sha1(byte[] input) { + return DigestUtils.digest(input, DigestUtils.SHA1, null, 1); + } + + /** + * 对输入字符串进行 SHA-1 散列. + */ + public static String sha1(String input) { + return EncodeUtils.encodeHex(sha1(input.getBytes(StandardCharsets.UTF_8))); + } + + /** + * 对输入字符串进行 SHA-1 散列. + */ + public static byte[] sha1(byte[] input, byte[] salt) { + return DigestUtils.digest(input, DigestUtils.SHA1, salt, 1); + } + + /** + * 对输入字符串进行 SHA-1 散列. + */ + public static String sha1(String data, String salt) { + return EncodeUtils.encodeHex(sha1(data.getBytes(StandardCharsets.UTF_8), EncodeUtils.decodeHex(salt))); + } + + /** + * 对输入字符串进行 SHA-1 散列. + */ + public static byte[] sha1(byte[] input, byte[] salt, int iterations) { + return DigestUtils.digest(input, DigestUtils.SHA1, salt, iterations); + } + + /** + * 对输入字符串进行 SHA-1 散列. + */ + public static String sha1(String input, String salt, int iterations) { + return EncodeUtils.encodeHex(sha1(input.getBytes(StandardCharsets.UTF_8), EncodeUtils.decodeHex(salt), iterations)); + } + + /** + * 对文件进行 SHA-1 散列. + */ + public static byte[] sha1(InputStream input) throws IOException { + return DigestUtils.digest(input, DigestUtils.SHA1); + } + + /** + * 对输入字符串进行 SHA-256 散列. + */ + public static byte[] sha256(byte[] input) { + return DigestUtils.digest(input, DigestUtils.SHA256, null, 1); + } + + /** + * 对输入字符串进行 SHA-256 散列. + */ + public static String sha256(String input) { + return EncodeUtils.encodeHex(sha256(input.getBytes(StandardCharsets.UTF_8))); + } + + /** + * 对输入字符串进行 SHA-256 散列. + */ + public static byte[] sha256(byte[] input, byte[] salt) { + return DigestUtils.digest(input, DigestUtils.SHA256, salt, 1); + } + + /** + * 对输入字符串进行 SHA-256 散列. + */ + public static String sha256(String data, String salt) { + return EncodeUtils.encodeHex(sha256(data.getBytes(StandardCharsets.UTF_8), EncodeUtils.decodeHex(salt))); + } + + /** + * 对输入字符串进行 SHA-256 散列. + */ + public static byte[] sha256(byte[] input, byte[] salt, int iterations) { + return DigestUtils.digest(input, DigestUtils.SHA256, salt, iterations); + } + + /** + * 对输入字符串进行 SHA-256 散列. + */ + public static String sha256(String input, String salt, int iterations) { + return EncodeUtils.encodeHex(sha256(input.getBytes(StandardCharsets.UTF_8), EncodeUtils.decodeHex(salt), iterations)); + } + + /** + * 对文件进行 SHA-256 散列. + */ + public static byte[] sha256(InputStream input) throws IOException { + return DigestUtils.digest(input, DigestUtils.SHA256); + } + +} diff --git a/common/src/main/java/com/jeesite/common/io/IOUtils.java b/common/src/main/java/com/jeesite/common/io/IOUtils.java index 5c865e1e0cbd0051553e41bd8ce832f943e1adaf..95596ce3380de57bc823ddf0240673a303765240 100644 --- a/common/src/main/java/com/jeesite/common/io/IOUtils.java +++ b/common/src/main/java/com/jeesite/common/io/IOUtils.java @@ -103,5 +103,9 @@ public class IOUtils extends org.apache.commons.io.IOUtils { // ignore } } - + + @Deprecated + public IOUtils() { + // empty + } } \ No newline at end of file diff --git a/common/src/main/java/com/jeesite/common/io/PropertyLoader.java b/common/src/main/java/com/jeesite/common/io/PropertyLoader.java index 3863f52a884c79099e215f92ca6ffe030cfa7818..5c70fa847c821e3ebdf50dab11ad86122f0aff6c 100644 --- a/common/src/main/java/com/jeesite/common/io/PropertyLoader.java +++ b/common/src/main/java/com/jeesite/common/io/PropertyLoader.java @@ -4,7 +4,6 @@ */ package com.jeesite.common.io; -import com.alibaba.fastjson.parser.ParserConfig; import com.jeesite.common.lang.StringUtils; import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.boot.env.PropertiesPropertySourceLoader; @@ -39,12 +38,12 @@ public class PropertyLoader implements org.springframework.boot.env.PropertySour List> propertySources = new ArrayList<>(); if (!isLoadJeeSitePropertySource) { isLoadJeeSitePropertySource = true; - try { - // 默认开启 FastJSON 1.x 的,安全模式 - ParserConfig.getGlobalInstance().setSafeMode(true); - } catch (Throwable ignored) { - // 兼容 FastJSON 2.x 的调用,忽略异常 - } +// try { +// // 默认开启 FastJSON 1.x 的,安全模式 +// ParserConfig.getGlobalInstance().setSafeMode(true); +// } catch (Throwable ignored) { +// // 兼容 FastJSON 2.x 的调用,忽略异常 +// } Properties properties = PropertiesUtils.getInstance().getProperties(); propertySources.add(new OriginTrackedMapPropertySource("jeesite", properties)); } else { diff --git a/common/src/main/java/com/jeesite/common/lang/DateUtils.java b/common/src/main/java/com/jeesite/common/lang/DateUtils.java index 3051036e7eacfd9b24a06160921ceabcb4d96465..6a1f5e830221e06bf759b764552bc7b71a793899 100644 --- a/common/src/main/java/com/jeesite/common/lang/DateUtils.java +++ b/common/src/main/java/com/jeesite/common/lang/DateUtils.java @@ -4,10 +4,11 @@ */ package com.jeesite.common.lang; +import com.jeesite.common.utils.LocaleUtils; import org.apache.commons.lang3.time.FastDateFormat; import java.lang.management.ManagementFactory; -import java.text.ParseException; +import java.text.ParsePosition; import java.util.Calendar; import java.util.Date; @@ -17,14 +18,14 @@ import java.util.Date; * @version 2017-1-4 */ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { - + private static final String[] parsePatterns = { "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH", "yyyy-MM", "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM/dd HH", "yyyy/MM", - "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM.dd HH", "yyyy.MM", + "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM.dd HH", "yyyy.MM", "yyyy年MM月dd日", "yyyy年MM月dd日 HH时mm分ss秒", "yyyy年MM月dd日 HH时mm分", "yyyy年MM月dd日 HH时", "yyyy年MM月", - "yyyyMMdd", "yyyyMM", "yyyy"}; - + "yyyyMMdd", "yyyyMM", "yyyy", "yyyy-MM-dd'T'HH:mm:ss'Z'"}; + /** * 得到日期字符串 ,转换格式(yyyy-MM-dd) */ @@ -45,15 +46,12 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { public static String formatDate(Date date, String pattern) { String formatDate = null; if (date != null){ -// if (StringUtils.isNotBlank(pattern)) { -// formatDate = DateFormatUtils.format(date, pattern); -// } else { -// formatDate = DateFormatUtils.format(date, "yyyy-MM-dd"); -// } if (StringUtils.isBlank(pattern)) { pattern = "yyyy-MM-dd"; } - formatDate = FastDateFormat.getInstance(pattern).format(date); +// formatDate = DateFormatUtils.format(date, "yyyy-MM-dd"); + formatDate = FastDateFormat.getInstance(pattern, + LocaleUtils.getTimeZone(), LocaleUtils.getLocale()).format(date); } return formatDate; } @@ -77,7 +75,8 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { */ public static String getDate(String pattern) { // return DateFormatUtils.format(new Date(), pattern); - return FastDateFormat.getInstance(pattern).format(new Date()); + return FastDateFormat.getInstance(pattern, + LocaleUtils.getTimeZone(), LocaleUtils.getLocale()).format(new Date()); } /** @@ -88,11 +87,12 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { * @return */ public static String getDate(String pattern, int amont, int type) { - Calendar calendar = Calendar.getInstance(); + Calendar calendar = Calendar.getInstance(LocaleUtils.getTimeZone(), LocaleUtils.getLocale()); calendar.setTime(new Date()); calendar.add(type, amont); // return DateFormatUtils.format(calendar.getTime(), pattern); - return FastDateFormat.getInstance(pattern).format(calendar.getTime()); + return FastDateFormat.getInstance(pattern, + LocaleUtils.getTimeZone(), LocaleUtils.getLocale()).format(calendar.getTime()); } /** @@ -138,17 +138,43 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { } /** - * 日期型字符串转化为日期 格式 see to DateUtils#parsePatterns + * 日期型字符串转化为日期对象,使用默认格式集 */ public static Date parseDate(Object str) { if (str == null){ return null; } - try { - return parseDate(str.toString(), parsePatterns); - } catch (ParseException e) { + String dateStr = str.toString(); + if (StringUtils.isBlank(dateStr)){ return null; } + return parseDate(dateStr, parsePatterns); + } + + /** + * 日期型字符串转化为日期对象,指定日期解析格式 + */ + public static Date parseDate(final String str, final String... parsePatterns) { +// try { +// return DateUtils.parseDate(str, Locale.getDefault(), parsePatterns); +// } catch (ParseException e) { +// return null; +// } + ParsePosition pos = new ParsePosition(0); + Calendar calendar = Calendar.getInstance(LocaleUtils.getTimeZone(), LocaleUtils.getLocale()); + for (final String parsePattern : parsePatterns) { + FastDateFormat format = FastDateFormat.getInstance(parsePattern); + calendar.clear(); + try { + if (format.parse(str, pos, calendar) && pos.getIndex() == str.length()) { + return calendar.getTime(); + } + } catch (final IllegalArgumentException ignored) { + // leniency is preventing calendar from being set + } + pos.setIndex(0); + } + return null; } /** @@ -228,7 +254,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { * @return */ public static int getWeekOfYear(Date date){ - Calendar cal = Calendar.getInstance(); + Calendar cal = Calendar.getInstance(LocaleUtils.getTimeZone(), LocaleUtils.getLocale()); cal.setTime(date); return cal.get(Calendar.WEEK_OF_YEAR); } @@ -242,7 +268,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { if (date == null){ return null; } - Calendar calendar = Calendar.getInstance(); + Calendar calendar = Calendar.getInstance(LocaleUtils.getTimeZone(), LocaleUtils.getLocale()); calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); @@ -260,7 +286,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { if (date == null){ return null; } - Calendar calendar = Calendar.getInstance(); + Calendar calendar = Calendar.getInstance(LocaleUtils.getTimeZone(), LocaleUtils.getLocale()); calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, 23); calendar.set(Calendar.MINUTE, 59); @@ -315,5 +341,9 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { } return new Date[]{beginDate, endDate}; } - + + @Deprecated + public DateUtils() { + // empty + } } diff --git a/common/src/main/java/com/jeesite/common/lang/ExceptionUtils.java b/common/src/main/java/com/jeesite/common/lang/ExceptionUtils.java index 23b1f7dea2bd73b15a96febc7f6b172304f70bce..49242c50a615fa9f6ecf08dbb02061ab80478bd6 100644 --- a/common/src/main/java/com/jeesite/common/lang/ExceptionUtils.java +++ b/common/src/main/java/com/jeesite/common/lang/ExceptionUtils.java @@ -37,16 +37,19 @@ public class ExceptionUtils { public static String getExceptionMessage(Throwable ex){ String message = null; Throwable e = ex; - while (true){ - if (e == null){ + while (true) { + if (e == null) { break; } - if (StringUtils.startsWith(e.getMessage(), "msg:")){ + if (StringUtils.startsWith(e.getMessage(), "msg:")) { message = StringUtils.replace(e.getMessage(), "msg:", ""); - break; - }else if ("com.jeesite.common.service.ServiceException" - .equals(e.getClass().getName())){ + } else if ("com.jeesite.common.service.ServiceException".equals(e.getClass().getName())){ message = e.getMessage(); + } + if (StringUtils.isNotBlank(message)){ + if (e.getSuppressed() != null && e.getCause() != null){ + ex.addSuppressed(e.getCause()); + } break; } e = e.getCause(); diff --git a/common/src/main/java/com/jeesite/common/lang/NumberUtils.java b/common/src/main/java/com/jeesite/common/lang/NumberUtils.java index 0940ff3451e88868a014d9849f73dfe2a56a52b1..e47caf1980e77e8ae2a47301ee3394997dc978b9 100644 --- a/common/src/main/java/com/jeesite/common/lang/NumberUtils.java +++ b/common/src/main/java/com/jeesite/common/lang/NumberUtils.java @@ -119,5 +119,9 @@ public class NumberUtils extends org.apache.commons.lang3.math.NumberUtils { } return df.format(data); } - + + @Deprecated + public NumberUtils() { + // empty + } } diff --git a/common/src/main/java/com/jeesite/common/lang/ObjectUtils.java b/common/src/main/java/com/jeesite/common/lang/ObjectUtils.java index b76895f6d62664ab000e938eb8b185c5b7e08b6b..bd5617ad86ff9d8531f41ef47e6f557f8846e1ec 100644 --- a/common/src/main/java/com/jeesite/common/lang/ObjectUtils.java +++ b/common/src/main/java/com/jeesite/common/lang/ObjectUtils.java @@ -413,5 +413,9 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils { // inputPool.free(input); // } // } - + + @Deprecated + public ObjectUtils() { + // empty + } } diff --git a/common/src/main/java/com/jeesite/common/lang/StringUtils.java b/common/src/main/java/com/jeesite/common/lang/StringUtils.java index 6fec8b501758d2f2460604c6f0e0d361ba5a12e4..2140ede715712b0db6b66ddea17514874595c516 100644 --- a/common/src/main/java/com/jeesite/common/lang/StringUtils.java +++ b/common/src/main/java/com/jeesite/common/lang/StringUtils.java @@ -415,5 +415,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { return name; } } - + + @Deprecated + public StringUtils() { + // empty + } } diff --git a/common/src/main/java/com/jeesite/common/mapper/JsonMapper.java b/common/src/main/java/com/jeesite/common/mapper/JsonMapper.java index 567c47c273a533b2ddff2b62e92ab1f8f3fbbc66..6e3ee3b076a7e82f5d63c59c34daf1d5b31662fc 100644 --- a/common/src/main/java/com/jeesite/common/mapper/JsonMapper.java +++ b/common/src/main/java/com/jeesite/common/mapper/JsonMapper.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser.Feature; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.databind.introspect.AnnotatedClass; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; import com.fasterxml.jackson.databind.module.SimpleModule; @@ -19,14 +20,15 @@ import com.jeesite.common.codec.EncodeUtils; import com.jeesite.common.collect.ListUtils; import com.jeesite.common.io.PropertiesUtils; import com.jeesite.common.lang.DateUtils; +import org.apache.commons.lang3.LocaleUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import java.io.IOException; +import java.io.Serial; import java.lang.reflect.AnnotatedElement; -import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Map; @@ -39,6 +41,7 @@ import java.util.TimeZone; */ public class JsonMapper extends ObjectMapper { + @Serial private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(JsonMapper.class); @@ -51,65 +54,108 @@ public class JsonMapper extends ObjectMapper { } public JsonMapper() { - // Spring ObjectMapper 初始化配置,支持 @JsonView - new Jackson2ObjectMapperBuilder().configure(this); + // 日志类型格式化处理 + this.setLocaleTimeZoneDateFormat(); // 为Null时不序列化 this.setSerializationInclusion(Include.NON_NULL); // 允许单引号 this.configure(Feature.ALLOW_SINGLE_QUOTES, true); // 允许不带引号的字段名称 this.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); - // 设置默认时区 - this.setDefaultTimeZone(); - // 设置默认日期格式 - this.setDefaultDateFormat(); - // 遇到空值处理为空串 + // 遇到空值处理为空串 this.enabledNullValueToEmpty(); // 设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性 this.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + // Spring ObjectMapper 初始化配置,支持 @JsonView + new Jackson2ObjectMapperBuilder().configure(this); } /** - * 开启日期类型默认格式化 + * 日志类型格式化处理 * @author ThinkGem */ - public JsonMapper setDefaultTimeZone(){ + public JsonMapper setLocaleTimeZoneDateFormat(){ + this.setLocale(LocaleUtils.toLocale(PropertiesUtils.getInstance() + .getProperty("lang.defaultLocale", "zh_CN"))); this.setTimeZone(TimeZone.getTimeZone(PropertiesUtils.getInstance() .getProperty("lang.defaultTimeZone", "GMT+08:00"))); - return this; - } - - /** - * 开启日期类型默认格式化 - * @author ThinkGem - */ - public JsonMapper setDefaultDateFormat(){ - this.setDateFormat(new SimpleDateFormat(PropertiesUtils.getInstance() - .getProperty("web.json.defaultDateFormat", "yyyy-MM-dd HH:mm:ss"))); this.setAnnotationIntrospector(new JacksonAnnotationIntrospector() { + @Serial private static final long serialVersionUID = 1L; + private static final String[] pattern = new String[] {"yyyy", "MM", "dd", "HH", "mm", "ss", "SSS"}; @Override public Object findSerializer(Annotated a) { if (a instanceof AnnotatedMethod) { - AnnotatedElement m = a.getAnnotated(); - JsonFormat jf = m.getAnnotation(JsonFormat.class); - if (jf != null) { - return new JsonSerializer(){ - @Override - public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - if (value != null){ - jgen.writeString(DateUtils.formatDate(value, jf.pattern())); - } - } - }; + AnnotatedMethod am = (AnnotatedMethod) a; + if (am.getRawReturnType() == Date.class) { + JsonFormat jf = am.getAnnotation(JsonFormat.class); + if (jf != null && StringUtils.containsAnyIgnoreCase(jf.pattern(), pattern)) { + return new JeeSiteJsonSerializer(jf.pattern()); + } + return new JeeSiteJsonSerializer(null); + } + } else if (a instanceof AnnotatedClass) { + if (a.getRawType() == Date.class) { + return new JeeSiteJsonSerializer(null); } } return super.findSerializer(a); } + @Override + public Object findDeserializer(Annotated a) { + if (a instanceof AnnotatedMethod) { + AnnotatedMethod am = (AnnotatedMethod) a; + if (am.getParameterCount() > 0 && am.getParameterType(0).getRawClass() == Date.class) { + AnnotatedElement m = am.getAnnotated(); + JsonFormat jf = m.getAnnotation(JsonFormat.class); + if (jf != null && StringUtils.containsAnyIgnoreCase(jf.pattern(), pattern)) { + return new JeeSiteJsonDeserializer(jf.pattern()); + } + return new JeeSiteJsonDeserializer(null); + } + } else if (a instanceof AnnotatedClass) { + if (a.getRawType() == Date.class) { + return new JeeSiteJsonDeserializer(null); + } + } + return super.findDeserializer(a); + } }); return this; } + public static final class JeeSiteJsonSerializer extends JsonSerializer { + private final String pattern; + public JeeSiteJsonSerializer(String pattern) { + this.pattern = pattern; + } + @Override + public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) throws IOException { + if (value != null){ + if (StringUtils.isNotBlank(pattern)) { + gen.writeString(DateUtils.formatDate(value, pattern)); + } else { + gen.writeString(DateUtils.formatDateTime(value)); + } + } + } + } + + public static final class JeeSiteJsonDeserializer extends JsonDeserializer { + private final String pattern; + public JeeSiteJsonDeserializer(String pattern) { + this.pattern = pattern; + } + @Override + public Date deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (StringUtils.isNotBlank(pattern)) { + return DateUtils.parseDate(parser.getText(), pattern); + } else { + return DateUtils.parseDate(parser.getText()); + } + } + } + /** * 开启将空值转换为空字符串 * @author ThinkGem @@ -120,7 +166,7 @@ public class JsonMapper extends ObjectMapper { public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(StringUtils.EMPTY); } - }); + }); return this; } diff --git a/common/src/main/java/com/jeesite/common/mapper/XmlMapper.java b/common/src/main/java/com/jeesite/common/mapper/XmlMapper.java index c6ab69b2ca3addf6fffc6a99d22e1be598d68667..dcfdadbe0f38304aec2d339058ec229a787f5125 100644 --- a/common/src/main/java/com/jeesite/common/mapper/XmlMapper.java +++ b/common/src/main/java/com/jeesite/common/mapper/XmlMapper.java @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import java.io.IOException; +import java.io.Serial; import java.util.TimeZone; /** @@ -21,6 +22,7 @@ import java.util.TimeZone; */ public class XmlMapper extends com.fasterxml.jackson.dataformat.xml.XmlMapper{ + @Serial private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(XmlMapper.class); diff --git a/common/src/main/java/com/jeesite/common/utils/IdcardUtils.java b/common/src/main/java/com/jeesite/common/utils/IdcardUtils.java index a44b0ab1e88cc6307465773a242bb38efc51eaf5..4a723a016af1e385199d1485ddb5a0c93f48311d 100644 --- a/common/src/main/java/com/jeesite/common/utils/IdcardUtils.java +++ b/common/src/main/java/com/jeesite/common/utils/IdcardUtils.java @@ -1,7 +1,5 @@ package com.jeesite.common.utils; -import org.apache.commons.lang3.StringUtils; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -11,11 +9,10 @@ import java.util.Map; /** * 身份证工具类 - * * @author June - * @version 1.0, 2010-06-17 + * @version 2010-06-17 */ -public class IdcardUtils extends StringUtils { +public class IdcardUtils { /** 中国公民身份证号码最小长度。 */ public static final int CHINA_ID_MIN_LENGTH = 15; diff --git a/common/src/main/java/com/jeesite/common/utils/LocaleUtils.java b/common/src/main/java/com/jeesite/common/utils/LocaleUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..45836f6c2ff4404259421ed225ee0dcadf108870 --- /dev/null +++ b/common/src/main/java/com/jeesite/common/utils/LocaleUtils.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2013-Now http://jeesite.com All rights reserved. + * No deletion without permission, or be held responsible to law. + */ +package com.jeesite.common.utils; + +import com.jeesite.common.io.PropertiesUtils; +import com.jeesite.common.web.http.ServletUtils; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.context.i18n.TimeZoneAwareLocaleContext; +import org.springframework.core.NamedThreadLocal; +import org.springframework.web.servlet.LocaleContextResolver; + +import java.util.Locale; +import java.util.TimeZone; + +/** + * 本地化工具 + * @author ThinkGem + * @version 2025-05-21 + */ +public class LocaleUtils { + + private static final Boolean LANG_ENABLED = PropertiesUtils.getInstance().getPropertyToBoolean("lang.enabled", "false"); + private static final ThreadLocal timeZoneAwareLocaleContext = new NamedThreadLocal<>("TimeZoneAwareLocaleContext"); + private static LocaleContextResolver localeResolver; + + /** + * 获取当前 Locale 对象,获取顺序:请求 -> 会话 -> Cookie -> lang.defaultLocale + */ + public static Locale getLocale() { + return getTimeZoneAwareLocaleContext().getLocale(); + } + + /** + * 获取当前 TimeZone 对象,获取顺序:请求 -> 会话 -> Cookie -> lang.defaultTimeZone + */ + public static TimeZone getTimeZone() { + return getTimeZoneAwareLocaleContext().getTimeZone(); + } + + /** + * 获取 TimeZoneAwareLocaleContext + */ + public static TimeZoneAwareLocaleContext getTimeZoneAwareLocaleContext() { + TimeZoneAwareLocaleContext context = timeZoneAwareLocaleContext.get(); + if (context != null){ + return context; + } + Locale locale; + TimeZone timeZone; + if (LANG_ENABLED && localeResolver != null){ + HttpServletRequest request = ServletUtils.getRequest(); + if (request != null){ + context = (TimeZoneAwareLocaleContext)localeResolver.resolveLocaleContext(request); + } + } + if (context != null){ + locale = context.getLocale(); + timeZone = context.getTimeZone(); + } else { + locale = Locale.getDefault(); + timeZone = TimeZone.getDefault(); + } + context = new TimeZoneAwareLocaleContext() { + @Override + public Locale getLocale() { + return locale; + } + @Override + public TimeZone getTimeZone() { + return timeZone; + } + }; + setTimeZoneAwareLocaleContext(context); + return context; + } + + /** + * 设置 TimeZoneAwareLocaleContext + */ + public static void setTimeZoneAwareLocaleContext(TimeZoneAwareLocaleContext context) { + timeZoneAwareLocaleContext.set(context); + } + + /** + * 清理本地线程对象(请求结束时调用) + */ + public static void removeTimeZoneAwareLocaleContext() { + timeZoneAwareLocaleContext.remove(); + } + + /** + * 设置 LocaleContextResolver + */ + public static void setLocaleResolver(LocaleContextResolver localeResolver) { + LocaleUtils.localeResolver = localeResolver; + } + +} diff --git a/common/src/main/java/com/jeesite/common/utils/excel/ExcelException.java b/common/src/main/java/com/jeesite/common/utils/excel/ExcelException.java index 1e6fff8280e4f05f04b1a71f6cb0af5302d8f0ce..08dc1b502976c5e8e0063e5adff5e7c434e8aa11 100644 --- a/common/src/main/java/com/jeesite/common/utils/excel/ExcelException.java +++ b/common/src/main/java/com/jeesite/common/utils/excel/ExcelException.java @@ -4,12 +4,15 @@ */ package com.jeesite.common.utils.excel; +import java.io.Serial; + /** * Excel Exception * @author ThinkGem */ public class ExcelException extends RuntimeException { + @Serial private static final long serialVersionUID = 1L; public ExcelException() { diff --git a/common/src/main/java/com/jeesite/common/web/http/HttpClientUtils.java b/common/src/main/java/com/jeesite/common/web/http/HttpClientUtils.java index e5c81143ebc59b316ecd4253fc41532dde76f6d7..b09c9da87526c2cfdc50c270f04c5600074b392c 100644 --- a/common/src/main/java/com/jeesite/common/web/http/HttpClientUtils.java +++ b/common/src/main/java/com/jeesite/common/web/http/HttpClientUtils.java @@ -89,6 +89,7 @@ public class HttpClientUtils { * 构建表单数据,Map 转换 params,支持指定编码 */ private static String buildUrl(String url, Map dataMap, String charset) { + url = StringUtils.substringBefore(url, "#"); if (dataMap == null) { return url; } diff --git a/common/src/main/resources/static/adminlte/css/skins/skin-blue-light2.css b/common/src/main/resources/static/adminlte/css/skins/skin-blue-light2.css index c2ef18594c6157cfbab6a15503165a350430c701..c393338817dbc4bc7fff36c5a30863891f99639a 100644 --- a/common/src/main/resources/static/adminlte/css/skins/skin-blue-light2.css +++ b/common/src/main/resources/static/adminlte/css/skins/skin-blue-light2.css @@ -2,14 +2,14 @@ * http://jeesite.com */ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sortable { - color:#1890ff; + color:#1677ff; } .main-header .navbar { - background: #1890ff; - background: -webkit-gradient(linear, left bottom, right top, color-stop(0, #2684f5), color-stop(1, #1890ff)); - background: -ms-linear-gradient(left, #2684f5, #1890ff); - background: -moz-linear-gradient(left top, #2684f5 0%, #1890ff 100%); - background: -o-linear-gradient(#1890ff, #2684f5); + background: #1677ff; + background: -webkit-gradient(linear, left bottom, right top, color-stop(0, #2684f5), color-stop(1, #1677ff)); + background: -ms-linear-gradient(left, #2684f5, #1677ff); + background: -moz-linear-gradient(left top, #2684f5 0%, #1677ff 100%); + background: -o-linear-gradient(#1677ff, #2684f5); } .main-header .navbar .nav > li > a { color: #ffffff; @@ -54,10 +54,10 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor border-bottom: 0 solid transparent; } .main-header .logo:hover { - background-color: #1890ff; + background-color: #1677ff; } .main-header li.user-header { - background-color: #1890ff; + background-color: #1677ff; } .content-header { background: transparent; @@ -93,7 +93,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor background: #ffffff; } .sidebar-menu > li.active { - border-left-color: #1890ff; + border-left-color: #1677ff; } .sidebar-menu > li.active > a { font-weight: 600; @@ -154,7 +154,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor border-radius: 4px; } .skin-blue.layout-top-nav .main-header > .logo { - background-color: #1890ff; + background-color: #1677ff; color: #ffffff; border-bottom: 0 solid transparent; } @@ -164,7 +164,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor .sidebar-menu {padding:0 8px 0 7px;} .sidebar-menu li>a>.pull-right-container {left:0;} -.sidebar-menu .treeview-item.active>a {color:#1890ff;background-color:#e7f4ff;border-right:0;border-radius:6px;} +.sidebar-menu .treeview-item.active>a {color:#1677ff;background-color:#e7f4ff;border-right:0;border-radius:6px;} .content-wrapper, .right-side, body {background-color:#f0f2f5;} @@ -189,12 +189,13 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor .select2-container--default .select2-results__option--highlighted[aria-selected], .pagination>.active>a, .pagination>.active>a:focus, .pagination>.active>a:hover, .pagination>.active>span, .pagination>.active>span:focus, .pagination>.active>span:hover, -.wup_container .placeholder .webuploader-pick {background-color:#1890ff!important;border-color:#1890ff;} -.form-unit, th[aria-selected=true] .ui-jqgrid-sortable {color:#1890ff;} +.wup_container .placeholder .webuploader-pick {background-color:#1677ff!important;border-color:#1677ff;} +.form-unit, th[aria-selected=true] .ui-jqgrid-sortable {color:#1677ff;} .form-unit {border-bottom:1px solid #eee;} +.form-unit:before {background-color:#1677ff;} .box-main>.box-header {border-bottom-color:#eeeeee;} -.box-main>.box-header .box-title .fa {color:#1890ff;} +.box-main>.box-header .box-title .fa {color:#1677ff;} .nav-tabs-custom>.nav-tabs>li.active {border-top-color:#3aa0ff;} .form-control:focus,.select2-container--default.select2-container--focus .select2-selection--multiple, .select2-container--default .select2-search--dropdown .select2-search__field, diff --git a/common/src/main/resources/static/adminlte/css/skins/skin-blue-light3.css b/common/src/main/resources/static/adminlte/css/skins/skin-blue-light3.css index 4260d5a0c521de3e38437fce6d5fcebf138e6afe..233993d357cd167f0053e91fc1adb98c8690f47c 100644 --- a/common/src/main/resources/static/adminlte/css/skins/skin-blue-light3.css +++ b/common/src/main/resources/static/adminlte/css/skins/skin-blue-light3.css @@ -2,17 +2,37 @@ * http://jeesite.com */ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sortable { - color:#1890ff; + color:#1677ff; } .main-header .navbar { - background: #1890ff; - background: -webkit-gradient(linear, left bottom, right top, color-stop(0, #2684f5), color-stop(1, #1890ff)); - background: -ms-linear-gradient(left, #2684f5, #1890ff); - background: -moz-linear-gradient(left top, #2684f5 0%, #1890ff 100%); - background: -o-linear-gradient(#1890ff, #2684f5); + background: #1677ff; + background: -webkit-gradient(linear, left bottom, right top, color-stop(0, #2684f5), color-stop(1, #1677ff)); + background: -ms-linear-gradient(left, #2684f5, #1677ff); + background: -moz-linear-gradient(left top, #2684f5 0%, #1677ff 100%); + background: -o-linear-gradient(#1677ff, #2684f5); } .main-header .navbar .nav > li > a { color: #ffffff; + margin: 4.5px 2px 0; + padding: 10px 13px; + border-radius: 6px; +} +.main-header .navbar .nav > li > a > i { + font-size: 16px; + vertical-align: bottom; + line-height: 20px; +} +.navbar-nav>.user-menu>.dropdown-menu { + margin-top: 7px; + border-radius: 6px; +} +.navbar-nav .treeview-menu, +.navbar-custom-menu>.navbar-nav>li>.dropdown-menu { + margin-top: 5px; + border-radius: 6px; +} +.navbar-nav .treeview-menu a { + border-radius: 6px; } .main-header .navbar .nav > li > a:hover, .main-header .navbar .nav > li > a:active, @@ -54,10 +74,10 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor border-bottom: 0 solid transparent; } .main-header .logo:hover { - background-color: #1890ff; + background-color: #1677ff; } .main-header li.user-header { - background-color: #1890ff; + background-color: #1677ff; } .content-header { background: transparent; @@ -93,7 +113,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor background: #ffffff; } .sidebar-menu > li.active { - border-left-color: #1890ff; + border-left-color: #1677ff; } .sidebar-menu > li.active > a { font-weight: 600; @@ -154,7 +174,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor border-radius: 4px; } .skin-blue.layout-top-nav .main-header > .logo { - background-color: #1890ff; + background-color: #1677ff; color: #ffffff; border-bottom: 0 solid transparent; } @@ -164,12 +184,12 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor .sidebar-menu {padding:0 8px 0 7px;} .sidebar-menu li>a>.pull-right-container {left:0;} -.sidebar-menu .treeview-item.active>a {color:#1890ff;background-color:#e7f4ff;border-right:0;border-radius:6px;} +.sidebar-menu .treeview-item.active>a {color:#1677ff;background-color:#e7f4ff;border-right:0;border-radius:6px;} /* 页签添加内边距 */ .content-wrapper, .tabpanel_content, .tabpanel_content .html_content, body {background-color:#f0f2f5;} .box-main, .nav-main, .ui-layout-pane, iframe {border-radius:5px;} -.tabpanel_content .html_content {padding:13px 14px 13px 15px;} +.tabpanel_content .html_content {padding:12px 14px 12px 15px;} .tabpanel_tab_content {border-bottom:0;} .ui-layout-resizer {background:none;} .content {padding:0!important} @@ -211,12 +231,13 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor .select2-container--default .select2-results__option--highlighted[aria-selected], .pagination>.active>a, .pagination>.active>a:focus, .pagination>.active>a:hover, .pagination>.active>span, .pagination>.active>span:focus, .pagination>.active>span:hover, -.wup_container .placeholder .webuploader-pick {background-color:#1890ff!important;border-color:#1890ff!important;} -.form-unit, th[aria-selected=true] .ui-jqgrid-sortable {color:#1890ff;} +.wup_container .placeholder .webuploader-pick {background-color:#1677ff!important;border-color:#1677ff!important;} +.form-unit, th[aria-selected=true] .ui-jqgrid-sortable {color:#1677ff;} .form-unit {border-bottom:1px solid #eee;} +.form-unit:before {background-color:#1677ff;} .box-main>.box-header {border-bottom-color:#eeeeee;} -.box-main>.box-header .box-title .fa {color:#1890ff;} +.box-main>.box-header .box-title .fa {color:#1677ff;} .nav-tabs-custom>.nav-tabs>li.active {border-top-color:#3aa0ff;} .form-control:focus,.select2-container--default.select2-container--focus .select2-selection--multiple, .select2-container--default .select2-search--dropdown .select2-search__field, diff --git a/common/src/main/resources/static/adminlte/css/skins/skin-blue2.css b/common/src/main/resources/static/adminlte/css/skins/skin-blue2.css index 4ca3d2febe006b001203753d993f8bc130ba78c9..7865b9ddd739ef65bacf87aeb9b37df2aada9378 100644 --- a/common/src/main/resources/static/adminlte/css/skins/skin-blue2.css +++ b/common/src/main/resources/static/adminlte/css/skins/skin-blue2.css @@ -192,6 +192,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor .wup_container .placeholder .webuploader-pick {background-color:#1e5edb!important;border-color:#1e5edb!important;} .form-unit, th[aria-selected=true] .ui-jqgrid-sortable {color:#1e5edb;} .form-unit {border-bottom:1px solid #eee;} +.form-unit:before {background-color:#1e5edb;} .box-main>.box-header {border-bottom-color:#eeeeee;} .box-main>.box-header .box-title .fa {color:#1e5edb;} diff --git a/common/src/main/resources/static/adminlte/css/skins/skin-blue3.css b/common/src/main/resources/static/adminlte/css/skins/skin-blue3.css index d8c699e163e9cf53e677f65219b5128153e67c67..a6d13632980ec959763ef350a42168786b07fd23 100644 --- a/common/src/main/resources/static/adminlte/css/skins/skin-blue3.css +++ b/common/src/main/resources/static/adminlte/css/skins/skin-blue3.css @@ -13,6 +13,26 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor } .main-header .navbar .nav > li > a { color: #ffffff; + margin: 4.5px 2px 0; + padding: 10px 13px; + border-radius: 6px; +} +.main-header .navbar .nav > li > a > i { + font-size: 16px; + vertical-align: bottom; + line-height: 20px; +} +.navbar-nav .treeview-menu, +.navbar-nav>.user-menu>.dropdown-menu { + margin-top: 7px; + border-radius: 6px; +} +.navbar-custom-menu>.navbar-nav>li>.dropdown-menu { + margin-top: 5px; + border-radius: 6px; +} +.navbar-nav .treeview-menu a { + border-radius: 6px; } .main-header .navbar .nav > li > a:hover, .main-header .navbar .nav > li > a:active, @@ -169,7 +189,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor /* 页签添加内边距 */ .content-wrapper, .tabpanel_content, .tabpanel_content .html_content, body {background-color:#f0f2f5;} .box-main, .nav-main, .ui-layout-pane, iframe {border-radius:5px;} -.tabpanel_content .html_content {padding:13px 14px 13px 15px;} +.tabpanel_content .html_content {padding:12px 14px 12px 15px;} .tabpanel_tab_content {border-bottom:0;} .ui-layout-resizer {background:none;} .content {padding:0!important} @@ -214,6 +234,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor .wup_container .placeholder .webuploader-pick {background-color:#1e5edb!important;border-color:#1e5edb!important;} .form-unit, th[aria-selected=true] .ui-jqgrid-sortable {color:#1e5edb;} .form-unit {border-bottom:1px solid #eee;} +.form-unit:before {background-color:#1e5edb;} .box-main>.box-header {border-bottom-color:#eeeeee;} .box-main>.box-header .box-title .fa {color:#1e5edb;} diff --git a/common/src/main/resources/static/adminlte/css/skins/skin-dark.css b/common/src/main/resources/static/adminlte/css/skins/skin-dark.css index b04d8f4ca524cb150d8e94f92fd1f2e48974ae0f..64cd8315654fc7da61e24724ed756a04b486397b 100644 --- a/common/src/main/resources/static/adminlte/css/skins/skin-dark.css +++ b/common/src/main/resources/static/adminlte/css/skins/skin-dark.css @@ -167,7 +167,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor /* 页签添加内边距 */ .content-wrapper, .tabpanel_content, .tabpanel_content .html_content, body {background-color:#000;} .box-main, .nav-main, .ui-layout-pane, iframe {border-radius:5px;} -.tabpanel_content .html_content {padding:13px 14px 13px 15px;} +.tabpanel_content .html_content {padding:12px 14px 12px 15px;} .tabpanel_tab_content {border-bottom:0;} .ui-layout-resizer {background:none;} .content {padding:0!important} @@ -227,6 +227,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor .wup_container .placeholder .webuploader-pick {background-color:#3aa0ff!important;border-color:#3aa0ff!important;} .form-unit, th[aria-selected=true] .ui-jqgrid-sortable {color:#2975bc;} .form-unit {border-bottom:1px solid #4e4e4e;} +.form-unit:before {background-color:#2975bc;} .form-inline .form-more {background-color:#1a1a1a;border-bottom-color:#393939;} .form-control, .input-group .input-group-addon, input, select, textarea, pre {background-color:#1a1a1a;border-color:#414141!important;color:#ddd} diff --git a/common/src/main/resources/static/jquery-ztree/3.5/js/jquery.ztree.all-3.5.js b/common/src/main/resources/static/jquery-ztree/3.5/js/jquery.ztree.all-3.5.js index d94af41fad8f570069d7ee534f8451b735de7a17..bcd7802108be1bb4aa7afa89529237151dd5824f 100644 --- a/common/src/main/resources/static/jquery-ztree/3.5/js/jquery.ztree.all-3.5.js +++ b/common/src/main/resources/static/jquery-ztree/3.5/js/jquery.ztree.all-3.5.js @@ -435,7 +435,7 @@ }, getNodeTitle: function(setting, node) { var t = setting.data.key.title === "" ? setting.data.key.name : setting.data.key.title; - return "" + node[t]; + return "" + (node[t] || node[setting.data.key.name]); }, getNodes: function(setting) { return data.getRoot(setting)[setting.data.key.children]; diff --git a/common/src/main/resources/static/jquery-ztree/3.5/js/jquery.ztree.core-3.5.js b/common/src/main/resources/static/jquery-ztree/3.5/js/jquery.ztree.core-3.5.js index a07818e5f42bb477f7b75659ce93eb4a06027360..64c4077b3a81750280ba52991c2e779c7c6a88aa 100644 --- a/common/src/main/resources/static/jquery-ztree/3.5/js/jquery.ztree.core-3.5.js +++ b/common/src/main/resources/static/jquery-ztree/3.5/js/jquery.ztree.core-3.5.js @@ -434,7 +434,7 @@ }, getNodeTitle: function(setting, node) { var t = setting.data.key.title === "" ? setting.data.key.name : setting.data.key.title; - return "" + node[t]; + return "" + (node[t] || node[setting.data.key.name]); }, getNodes: function(setting) { return data.getRoot(setting)[setting.data.key.children]; diff --git a/common/src/test/java/com/jeesite/test/codec/EncodeUtilsTest.java b/common/src/test/java/com/jeesite/test/codec/EncodeUtilsTest.java index de24d4f0a11efeec133107b53dda6ae581fd4c55..73039d3f12db71b95a253c1765d9bd288ff95325 100644 --- a/common/src/test/java/com/jeesite/test/codec/EncodeUtilsTest.java +++ b/common/src/test/java/com/jeesite/test/codec/EncodeUtilsTest.java @@ -14,37 +14,49 @@ import com.jeesite.common.codec.EncodeUtils; public class EncodeUtilsTest { public static void main(String[] args) { - EncodeUtils.xssFilter("1 你好 我还在。"); - EncodeUtils.xssFilter("2 你好 加粗文字我还在。"); - EncodeUtils.xssFilter("3 你好 \">加粗文字我还在。"); - EncodeUtils.xssFilter("4 你好 加粗文字我还在。"); - EncodeUtils.xssFilter("5 你好 我还在。"); - EncodeUtils.xssFilter("14 你好 eval(abc)我还在。"); - EncodeUtils.xssFilter("15 你好 expression(abc)我还在。"); - EncodeUtils.xssFilter("16 你好 我还在。"); - EncodeUtils.xssFilter("17 你好 我还在。"); - EncodeUtils.xssFilter("18 你好 我还在。"); - EncodeUtils.xssFilter("19 你好 hello我还在。"); - EncodeUtils.xssFilter("20 你好 hello我还在。"); - EncodeUtils.xssFilter("21 你好 hello我还在。"); - EncodeUtils.xssFilter("22 你好 hello我还在。"); - EncodeUtils.xssFilter("23 你好 hello我还在。"); - EncodeUtils.xssFilter("24 你好 ?abc=def&hello=123&world={\"a\":1}我还在。"); - EncodeUtils.xssFilter("25 你好 ?abc=def&hello=123&world={'a':1}我还在。"); - EncodeUtils.sqlFilter("1 你好 select * from xxx where abc=def and 1=1我还在。"); - EncodeUtils.sqlFilter("2 你好 insert into xxx values(1,2,3,4,5)我还在。"); - EncodeUtils.sqlFilter("3 你好 delete from xxx我还在。"); - EncodeUtils.sqlFilter("4 a.audit_result asc,case when 1 like case when length(database())=6 then 1 else exp(111) end then 1 else 1/0 end", "orderBy"); - EncodeUtils.sqlFilter("5 if(1=2,1,SLEEP(10)), if(mid(database(),{},1)=\\\"{}\\\",a.id,a.login_name)", "orderBy"); - EncodeUtils.sqlFilter("6 a.audit_result asc, b.audit_result2 desc, b.AuditResult3 desc", "orderBy"); + int i = 0; + xssFilter(i++, "你好 我还在。"); + xssFilter(i++, "你好 加粗文字我还在。"); + xssFilter(i++, "你好 \">加粗文字我还在。"); + xssFilter(i++, "你好 加粗文字我还在。"); + xssFilter(i++, "你好 我还在。"); + xssFilter(i++, "你好 eval(abc)我还在。"); + xssFilter(i++, "你好 expression(abc)我还在。"); + xssFilter(i++, "你好 我还在。"); + xssFilter(i++, "你好 我还在。"); + xssFilter(i++, "你好 我还在。"); + xssFilter(i++, "你好 hello我还在。"); + xssFilter(i++, "你好 hello我还在。"); + xssFilter(i++, "你好 hello我还在。"); + xssFilter(i++, "你好 hello我还在。"); + xssFilter(i++, "你好 hello我还在。"); + xssFilter(i++, "你好 ?abc=def&hello=123&world={\"a\":1}我还在。"); + xssFilter(i++, "你好 ?abc=def&hello=123&world={'a':1}我还在。"); + xssFilter(i++, "\">"); + sqlFilter(i++, "你好 select * from xxx where abc=def and 1=1我还在。", "common"); + sqlFilter(i++, "你好 insert into xxx values(1,2,3,4,5)我还在。", "common"); + sqlFilter(i++, "你好 delete from xxx我还在。", "common"); + sqlFilter(i++, "a.audit_result asc,case when 1 like case when length(database())=6 then 1 else exp(111) end then 1 else 1/0 end", "orderBy"); + sqlFilter(i++, "if(1=2,1,SLEEP(10)), if(mid(database(),{},1)=\\\"{}\\\",a.id,a.login_name)", "orderBy"); + sqlFilter(i++, "a.audit_result asc, b.audit_result2 desc, b.AuditResult3 desc", "orderBy"); + } + + private static void xssFilter(int num, String text) { + String text2 = EncodeUtils.xssFilter(text); + System.out.println(num + ". " + text + "\t ==> \t" + text2 + "\t ==> \t" + text.equals(text2)); + } + + private static void sqlFilter(int num, String text, String source) { + String text2 = EncodeUtils.sqlFilter(text, source); + System.out.println(num + ". " + text + "\t ==> \t" + text2 + "\t ==> \t" + text.equals(text2)); } } diff --git a/common/src/test/java/com/jeesite/test/codec/Sha1UtilsTest.java b/common/src/test/java/com/jeesite/test/codec/ShaUtilsTest.java similarity index 49% rename from common/src/test/java/com/jeesite/test/codec/Sha1UtilsTest.java rename to common/src/test/java/com/jeesite/test/codec/ShaUtilsTest.java index 02656dc079ec1acee3f9329987d246f468c85e9e..6e5cbb99462472e6c1947420ff83ecb004595208 100644 --- a/common/src/test/java/com/jeesite/test/codec/Sha1UtilsTest.java +++ b/common/src/test/java/com/jeesite/test/codec/ShaUtilsTest.java @@ -4,25 +4,33 @@ */ package com.jeesite.test.codec; -import com.jeesite.common.codec.Sha1Utils; +import com.jeesite.common.codec.ShaUtils; /** * SHA-1 加密工具类,散列加密,不可逆加密 * @author ThinkGem * @version 2024-07-22 */ -public class Sha1UtilsTest { +public class ShaUtilsTest { + + public static final int HASH_ITERATIONS = 1024; + public static final int SALT_SIZE = 8; public static void main(String[] args) { String s = "Hello word! 你好,中文!"; System.out.println(s); - String salt = Sha1Utils.genSaltString(8); + String salt = ShaUtils.genSaltString(SALT_SIZE); System.out.println(salt); - String data = Sha1Utils.sha1(s, salt); + String data = ShaUtils.sha1(s, salt, HASH_ITERATIONS); System.out.println(data); + String salt2 = ShaUtils.genSaltString(SALT_SIZE); + System.out.println(salt2); + String data2 = ShaUtils.sha256(s, salt2, HASH_ITERATIONS); + System.out.println(data2); + } } \ No newline at end of file diff --git a/common/src/test/java/com/jeesite/test/lang/DateUtilsTest.java b/common/src/test/java/com/jeesite/test/lang/DateUtilsTest.java index b1dffc0113a4566d3a456c5e5b7242c973a3b29d..37c9f30195516ae7e20947e244158617fbc4abcd 100644 --- a/common/src/test/java/com/jeesite/test/lang/DateUtilsTest.java +++ b/common/src/test/java/com/jeesite/test/lang/DateUtilsTest.java @@ -18,6 +18,7 @@ public class DateUtilsTest { public static void main(String[] args) throws ParseException { System.out.println(DateUtils.formatDate(DateUtils.parseDate("2023/3/6"))); + System.out.println(DateUtils.formatDateTime(DateUtils.parseDate("2023-3-6 12:30:15"))); System.out.println(DateUtils.getDate("yyyy年MM月dd日 E")); long time = new Date().getTime()-DateUtils.parseDate("2023-11-19").getTime(); System.out.println(time/(24*60*60*1000)); diff --git a/common/src/test/java/com/jeesite/test/mapper/JsonMapperTest.java b/common/src/test/java/com/jeesite/test/mapper/JsonMapperTest.java index 5d2dec70326bbc27b0b3ba1d668e283daa9624f6..4efbb872d1f699b5b1211b0857c14a59e3d34a67 100644 --- a/common/src/test/java/com/jeesite/test/mapper/JsonMapperTest.java +++ b/common/src/test/java/com/jeesite/test/mapper/JsonMapperTest.java @@ -8,6 +8,7 @@ import com.jeesite.common.collect.ListUtils; import com.jeesite.common.collect.MapUtils; import com.jeesite.common.mapper.JsonMapper; +import java.util.Date; import java.util.List; import java.util.Map; @@ -30,6 +31,7 @@ public class JsonMapperTest { map.put("pId", 1); map.put("name", "你好"); map.put("open", true); + map.put("date", new Date()); list.add(map); String json = JsonMapper.toJson(list); System.out.println(json); diff --git a/modules/app/pom.xml b/modules/app/pom.xml index 94c903f5856405cab98a3a33e21409ecace5fce3..8eb892ca333ef0d2c7015194b77da68f82222582 100644 --- a/modules/app/pom.xml +++ b/modules/app/pom.xml @@ -6,7 +6,7 @@ com.jeesite jeesite-parent - 5.11.1.springboot3-SNAPSHOT + 5.12.1.springboot3-SNAPSHOT ../../parent/pom.xml diff --git a/modules/app/src/main/java/com/jeesite/modules/app/entity/AppComment.java b/modules/app/src/main/java/com/jeesite/modules/app/entity/AppComment.java index 9fe3e793b7b4bc6cc933937b9129cc6fd3423566..5f5cd227bc49e4ecd53ba1390892c0307fc35ec8 100644 --- a/modules/app/src/main/java/com/jeesite/modules/app/entity/AppComment.java +++ b/modules/app/src/main/java/com/jeesite/modules/app/entity/AppComment.java @@ -12,6 +12,7 @@ import com.jeesite.common.mybatis.mapper.query.QueryType; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +import java.io.Serial; import java.util.Date; /** @@ -36,6 +37,7 @@ import java.util.Date; ) public class AppComment extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String category; // 问题分类 private String content; // 问题和意见 diff --git a/modules/app/src/main/java/com/jeesite/modules/app/entity/AppUpgrade.java b/modules/app/src/main/java/com/jeesite/modules/app/entity/AppUpgrade.java index 1016af12870f4f7bb17577327f3acca5f7805566..01c26e5e403b52aefc24616c3695d5412dfd2bfa 100644 --- a/modules/app/src/main/java/com/jeesite/modules/app/entity/AppUpgrade.java +++ b/modules/app/src/main/java/com/jeesite/modules/app/entity/AppUpgrade.java @@ -4,6 +4,7 @@ */ package com.jeesite.modules.app.entity; +import java.io.Serial; import java.util.Date; import jakarta.validation.constraints.Size; @@ -34,6 +35,7 @@ import com.jeesite.common.mybatis.mapper.query.QueryType; ) public class AppUpgrade extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String appCode; // 应用编号 private String upTitle; // 升级标题 diff --git a/modules/app/src/main/resources/db/upgrade/app/versions b/modules/app/src/main/resources/db/upgrade/app/versions index 30a971d44b41def405c9f8b3d6cc11dbcd831ca1..00e5430b090e841beabf9b4b7e749d0f23b5c9b4 100644 --- a/modules/app/src/main/resources/db/upgrade/app/versions +++ b/modules/app/src/main/resources/db/upgrade/app/versions @@ -26,4 +26,6 @@ 5.10.0 5.10.1 5.11.0 -5.11.1 \ No newline at end of file +5.11.1 +5.12.0 +5.12.1 \ No newline at end of file diff --git a/modules/cms-ai/README.md b/modules/cms-ai/README.md index af19fbd0082160b92a6e3947175369e1e8393bca..fd6ea2cc49a86d4fccac40a96900b4b248e27836 100644 --- a/modules/cms-ai/README.md +++ b/modules/cms-ai/README.md @@ -35,6 +35,11 @@ * 模型类型包括:聊天对话模型和嵌入式向量库模型,需注意 dimensions 维度参数,要和模型要求的匹配。 +```yml +# 向量库类型:openai、ollama +spring.ai.model.chat: openai +``` + 具体配置项详见 [jeesite-cms-ai.yml](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml) 文件,有注释。 ## 向量数据库配置 @@ -47,12 +52,17 @@ * Milvus * ... +```yml +# 向量库类型:chroma、pgvector、elasticsearch、milvus、指定 none 表示不使用向量库 +spring.ai.vectorstore.type: none +``` + 具体配置项详见 [jeesite-cms-ai.yml](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml) 文件,有注释。 ### 安装 Chroma ```sh -docker run --name chroma -p 8000:8000 ghcr.io/chroma-core/chroma:0.5.20 +docker run -d --name chroma -p 8000:8000 ghcr.io/chroma-core/chroma:1.0.0 ``` ### 安装 PGVector @@ -125,6 +135,32 @@ CREATE INDEX ON vector_store_1024 USING HNSW (embedding vector_cosine_ops); 实例代码,详见 [CmsAiTools.java](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/tools/CmsAiTools.java) 让 AI 调用你的 java 实现你的业务联动。 +## 支持结构化输出 + +对于依赖可靠解析输出值的下游应用程序来说,LLM 产生结构化输出的能力很重要。开发人员希望将 AI 模型的结果快速转换为数据类型,如 JSON、XML 或 Java 类,这些类可以传递给其他应用程序函数和方法。 + +pom.xml 中注释掉 `spring-ai-starter-model-openai` +打开注释 `spring-ai-starter-model-ollama` +启用 `Ollama` 本地模型,测试类:`AiChatServiceTest.java`,或测试地址: + +* 文本格式输出 + - 源码位置:CmsAiChatService.chatText(message) + - 访问地址: + - 输出结果:`你好!有什么问题或需要帮助的吗?` +* JSON 类型输出 + - 源码位置:CmsAiChatService.chatJson(message) + - 访问地址: + - 输出结果:`{"sex":"男","name":"张三","age":"17"}` +* 结合 Tool Calling 结构化输出 + - 开启参数:`spring.ai.tool-calls: true` + - 源码位置:CmsAiChatService.chatJson(message) + - 访问地址: + - 输出结果:`{"message":"客厅房间里的灯被打开","roomName":"客厅","on":true}` +* Java 对象类型输出: + - 源码位置:CmsAiChatService.chatArea(message) + - 访问地址: + - 输出结果:`[{"id":"110000","pageNo":1,"pageSize":10,"orderBy":"","isNewRecord":false,"dataMap":{},"status":"0","createBy":"system","createDate":"2025-01-01 19:25","updateBy":"system","updateDate":"2025-01-01 19:25","remarks":"","lastUpdateDateTime":1677843300000,"parentCodes":"0,","treeSort":110000,"treeSorts":"0000110000,","treeLeaf":"0","treeLevel":0,"treeNames":"北京市","childList":[{"id":"110100","pageNo":1,"pageSize":10,"orderBy":"","isNewRecord":false,"dataMap":{},"status":"0","createBy":"system","createDate":"2025-01-01 19:25","updateBy":"system","updateDate":"2025-01-01 19:25","remarks":"","lastUpdateDateTime":1677843300000,"parentCodes":"0,110000,","treeSort":110100,"treeSorts":"0000110000,0000110100,","treeLeaf":"0","treeLevel":1,"treeNames":"北京城区","childList":[{"id":"110101","isNewRecord":false,"areaCode":"110101","areaName":"东城区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110102","isNewRecord":false,"areaCode":"110102","areaName":"西城区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110105","isNewRecord":false,"areaCode":"110105","areaName":"朝阳区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110106","isNewRecord":false,"areaCode":"110106","areaName":"丰台区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110107","isNewRecord":false,"areaCode":"110107","areaName":"石景山区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110108","isNewRecord":false,"areaCode":"110108","areaName":"海淀区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110109","isNewRecord":false,"areaCode":"110109","areaName":"门头沟区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110111","isNewRecord":false,"areaCode":"110111","areaName":"房山区","areaType":"3","isRoot":true,"isTreeLeaf":false}],"isQueryChildren":true,"areaCode":"110100","areaName":"北京城区","areaType":"2","isRoot":false,"parentCode":"110000","isTreeLeaf":false,"parentName":"北京市"}],"isQueryChildren":true,"areaCode":"110000","areaName":"北京市","areaType":"1","isRoot":true,"isTreeLeaf":false}]` + ## 授权协议声明 1. 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款。 diff --git a/modules/cms-ai/pom.xml b/modules/cms-ai/pom.xml index 548c053282f2d02967ff78f10a5a8cb45d0570bd..d89cc68da5b120299009d4486fe075c449250195 100644 --- a/modules/cms-ai/pom.xml +++ b/modules/cms-ai/pom.xml @@ -6,7 +6,7 @@ com.jeesite jeesite-parent - 5.11.1.springboot3-SNAPSHOT + 5.12.1.springboot3-SNAPSHOT ../../parent/pom.xml @@ -19,7 +19,7 @@ - 1.0.0-M7 + 1.0.0-RC1 @@ -43,11 +43,11 @@ spring-ai-starter-model-openai - org.springframework.ai spring-ai-starter-model-ollama - --> + @@ -65,19 +65,19 @@ httpclient5 - org.springframework.ai spring-ai-starter-vector-store-pgvector - --> + - org.springframework.ai spring-ai-starter-vector-store-elasticsearch - --> + - org.springframework.ai spring-ai-starter-vector-store-milvus @@ -92,7 +92,7 @@ io.netty netty-resolver-dns-native-macos osx-aarch_64 - --> + diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/config/CmsAiChatConfig.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/config/CmsAiChatConfig.java index ceeff2860bd06840d6107415ce359080e838024d..250ac5a48d29d2f17ba381a840ab5c378eabef1b 100644 --- a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/config/CmsAiChatConfig.java +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/config/CmsAiChatConfig.java @@ -7,8 +7,11 @@ package com.jeesite.modules.cms.ai.config; import com.jeesite.common.datasource.DataSourceHolder; import com.jeesite.common.lang.StringUtils; import com.jeesite.modules.cms.ai.properties.CmsAiProperties; +import com.jeesite.modules.cms.ai.service.CacheChatMemoryRepository; import com.jeesite.modules.cms.ai.tools.CmsAiTools; import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -28,16 +31,28 @@ public class CmsAiChatConfig { * 聊天对话客户端 * @author ThinkGem */ - @Bean - public ChatClient chatClient(ChatClient.Builder builder, CmsAiProperties properties) { + @Bean + public ChatClient chatClient(ChatClient.Builder builder, CmsAiProperties properties) { if (StringUtils.isNotBlank(properties.getDefaultSystem())) { builder.defaultSystem(properties.getDefaultSystem()); } if (properties.getToolCalls()) { builder.defaultTools(new CmsAiTools()); } - return builder.build(); - } + return builder.build(); + } + + /** + * 聊天对话数据存储 + * @author ThinkGem + */ + @Bean + public ChatMemory chatMemory(CacheChatMemoryRepository cacheChatMemoryRepository) { + return MessageWindowChatMemory.builder() + .chatMemoryRepository(cacheChatMemoryRepository) + .maxMessages(1024) + .build(); + } // @Bean // public BatchingStrategy batchingStrategy() { diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/config/CmsAiWebMvcConfig.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/config/CmsAiWebMvcConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..44e5ef1b49f2534a7376fa195fcd17025bae3121 --- /dev/null +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/config/CmsAiWebMvcConfig.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2013-Now http://jeesite.com All rights reserved. + * No deletion without permission, or be held responsible to law. + */ +package com.jeesite.modules.cms.ai.config; + +import com.jeesite.common.config.Global; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * MVC 异步任务池定义 + * @author ThinkGem + */ +@Configuration +public class CmsAiWebMvcConfig implements WebMvcConfigurer { + + @Override + public void configureAsyncSupport(AsyncSupportConfigurer configurer) { + configurer.setTaskExecutor(webMvcAsyncTaskExecutor()); + } + + @Bean + public ThreadPoolTaskExecutor webMvcAsyncTaskExecutor() { + ThreadPoolTaskExecutor bean = new ThreadPoolTaskExecutor(); + bean.setCorePoolSize(Global.getPropertyToInteger("web.taskPool.corePoolSize", "8")); + bean.setMaxPoolSize(Global.getPropertyToInteger("web.taskPool.maxPoolSize", "20")); + bean.setKeepAliveSeconds(Global.getPropertyToInteger("web.taskPool.keepAliveSeconds", "60")); + bean.setQueueCapacity(Global.getPropertyToInteger("web.taskPool.queueCapacity", String.valueOf(Integer.MAX_VALUE))); + bean.setThreadNamePrefix("web-async-"); + return bean; + } +} \ No newline at end of file diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/config/WebClientThinkConfig.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/config/WebClientThinkConfig.java index 70885aa02ef07aae23462114694ae63cac8c4dc8..7fe193a8a382ab408d29bc447bf1aa2c1699b078 100644 --- a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/config/WebClientThinkConfig.java +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/config/WebClientThinkConfig.java @@ -36,18 +36,18 @@ public class WebClientThinkConfig { private final Logger logger = LoggerFactory.getLogger(WebClientThinkConfig.class); - @Bean - @ConditionalOnMissingBean - public WebClientCustomizer webClientCustomizerThink() { - return webClientBuilder -> { - ExchangeFilterFunction requestFilter = ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { - logger.trace("Request url: {}: {}", clientRequest.method(), clientRequest.url()); - return Mono.just(clientRequest); - }); - ExchangeFilterFunction responseFilter = ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { - logger.trace("Response status: {}", clientResponse.statusCode()); - AtomicBoolean thinkingFlag = new AtomicBoolean(false); - Flux modifiedBody = clientResponse.bodyToFlux(DataBuffer.class) + @Bean + @ConditionalOnMissingBean + public WebClientCustomizer webClientCustomizerThink() { + return webClientBuilder -> { + ExchangeFilterFunction requestFilter = ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { + logger.trace("Request url: {}: {}", clientRequest.method(), clientRequest.url()); + return Mono.just(clientRequest); + }); + ExchangeFilterFunction responseFilter = ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { + logger.trace("Response status: {}", clientResponse.statusCode()); + AtomicBoolean thinkingFlag = new AtomicBoolean(false); + Flux modifiedBody = clientResponse.bodyToFlux(DataBuffer.class) .map(buf -> { byte[] bytes = new byte[buf.readableByteCount()]; buf.read(bytes); @@ -92,7 +92,7 @@ public class WebClientThinkConfig { } String reasoningContent = (String) delta.get("reasoning_content"); String content = (String) delta.get("content"); - if (reasoningContent != null) { + if (StringUtils.isNotEmpty(reasoningContent) && StringUtils.isEmpty(content)) { if (!thinkingFlag.get()) { thinkingFlag.set(true); delta.put("content", "\n" + reasoningContent); @@ -102,7 +102,7 @@ public class WebClientThinkConfig { } else { if (thinkingFlag.get()) { thinkingFlag.set(false); - delta.put("content", "" + (content == null ? "" : content)); + delta.put("content", "\n" + (content == null ? "" : content)); } } } @@ -117,13 +117,13 @@ public class WebClientThinkConfig { byte[] bytes = str.getBytes(StandardCharsets.UTF_8); return new DefaultDataBufferFactory().wrap(bytes); }); - ClientResponse modifiedResponse = ClientResponse.from(clientResponse) + ClientResponse modifiedResponse = ClientResponse.from(clientResponse) .headers(headers -> headers.remove(HttpHeaders.CONTENT_LENGTH)) .body(modifiedBody) .build(); - return Mono.just(modifiedResponse); - }); - webClientBuilder.filter(requestFilter).filter(responseFilter); - }; - } + return Mono.just(modifiedResponse); + }); + webClientBuilder.filter(requestFilter).filter(responseFilter); + }; + } } diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/properties/CmsAiProperties.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/properties/CmsAiProperties.java index d9c268471c1d08eb17c31bce15a36deeb8f4263b..fd924f2592be88bbab32eb549dd44370de45b12f 100644 --- a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/properties/CmsAiProperties.java +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/properties/CmsAiProperties.java @@ -1,14 +1,32 @@ package com.jeesite.modules.cms.ai.properties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; @ConfigurationProperties("spring.ai") public class CmsAiProperties { + /** + * 是否启用 Tool calling 工具调用 + */ private Boolean toolCalls = false; + /** + * 默认系统提示词 + */ private String defaultSystem = ""; + /** + * 默认问题模板格式 + */ + private String defaultPromptTemplate = ""; + + /** + * 向量数据库设置 + */ + @NestedConfigurationProperty + private final Vectorstore vectorstore = new Vectorstore(); + public Boolean getToolCalls() { return toolCalls; } @@ -24,4 +42,32 @@ public class CmsAiProperties { public void setDefaultSystem(String defaultSystem) { this.defaultSystem = defaultSystem; } + + public String getDefaultPromptTemplate() { + return defaultPromptTemplate; + } + + public void setDefaultPromptTemplate(String defaultPromptTemplate) { + this.defaultPromptTemplate = defaultPromptTemplate; + } + + public Vectorstore getVectorstore() { + return vectorstore; + } + + public static class Vectorstore { + + /** + * 向量库类型选择:chroma、pgvector、elasticsearch、milvus + */ + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + } } diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/ArticleVectorStoreImpl.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/ArticleVectorStoreImpl.java index 07d24ee2f78aa1b789062f31615319e687ece768..97f0e33282021fa682e7e3167722afcc8fb0208d 100644 --- a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/ArticleVectorStoreImpl.java +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/ArticleVectorStoreImpl.java @@ -12,6 +12,7 @@ import com.jeesite.common.lang.StringUtils; import com.jeesite.common.lang.TimeUtils; import com.jeesite.common.utils.PageUtils; import com.jeesite.common.web.http.HttpClientUtils; +import com.jeesite.common.web.http.ServletUtils; import com.jeesite.modules.cms.entity.Article; import com.jeesite.modules.cms.service.ArticleVectorStore; import com.jeesite.modules.cms.utils.CmsUtils; @@ -21,11 +22,14 @@ import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; import com.vladsch.flexmark.html2md.converter.HtmlLinkResolver; import com.vladsch.flexmark.html2md.converter.HtmlLinkResolverFactory; import com.vladsch.flexmark.html2md.converter.HtmlNodeConverterContext; +import jakarta.servlet.http.HttpServletRequest; import org.apache.tika.Tika; import org.apache.tika.config.TikaConfig; import org.apache.tika.exception.TikaException; +import org.apache.tika.io.TikaInputStream; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.mime.MediaType; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.document.Document; @@ -37,6 +41,7 @@ import org.springframework.stereotype.Service; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Set; @@ -50,7 +55,7 @@ public class ArticleVectorStoreImpl implements ArticleVectorStore { protected Logger logger = LoggerFactory.getLogger(getClass()); - @Autowired + @Autowired(required = false) private VectorStore vectorStore; /** @@ -59,6 +64,7 @@ public class ArticleVectorStoreImpl implements ArticleVectorStore { */ @Override public void save(Article article) { + if (vectorStore == null) return; Map metadata = MapUtils.newHashMap(); metadata.put("id", article.getId()); metadata.put("siteCode", article.getCategory().getSite().getSiteCode()); @@ -75,13 +81,33 @@ public class ArticleVectorStoreImpl implements ArticleVectorStore { metadata.put("updateBy", article.getUpdateBy()); metadata.put("updateDate", article.getUpdateDate()); List attachmentList = ListUtils.newArrayList(); - HtmlLinkResolverFactory linkResolverFactory = new HtmlLinkResolverFactory() { + String content = article.getTitle() + ", " + article.getKeywords() + ", " + + article.getDescription() + ", " + FlexmarkHtmlConverter.builder() + .linkResolverFactory(getHtmlLinkResolverFactory(attachmentList)).build() + .convert(article.getArticleData().getContent()) + + ", attachment: " + attachmentList; + List documents = List.of(new Document(article.getId(), content, metadata)); + List splitDocuments = new TokenTextSplitter().apply(documents); + this.delete(article); // 删除原数据 + ListUtils.pageList(splitDocuments, 10, params -> { + vectorStore.add((List)params[0]); // 增加新数据 + return null; + }); + } + + /** + * 解析文章中的连接并提取内容 + * @author ThinkGem + */ + private @NotNull HtmlLinkResolverFactory getHtmlLinkResolverFactory(List attachmentList) { + HttpServletRequest request = ServletUtils.getRequest(); + return new HtmlLinkResolverFactory() { @Override - public @Nullable Set> getAfterDependents() { + public @NotNull Set> getAfterDependents() { return Set.of(); } @Override - public @Nullable Set> getBeforeDependents() { + public @NotNull Set> getBeforeDependents() { return Set.of(); } @Override @@ -94,11 +120,16 @@ public class ArticleVectorStoreImpl implements ArticleVectorStore { if ("a".equalsIgnoreCase(node.nodeName())) { String href = node.attributes().get("href"); String url = href; if (StringUtils.contains(url, "://")) { - try (InputStream is = HttpClientUtils.getInputStream(url, null)) { - String text = getDocumentText(is); - attachmentList.add(url + text); - } catch (IOException | TikaException e) { - logger.error(e.getMessage(), e); + // 只提取系统允许跳转的附件内容,外部网站内容不进行提取,shiro.allowRedirects 参数设置范围 + if (ServletUtils.isAllowRedirects(request, url)) { + try (InputStream is = HttpClientUtils.getInputStream(url, null)) { + if (is != null) { + String text = getDocumentText(is); + attachmentList.add(url + text); + } + } catch (IOException | TikaException e) { + logger.error(e.getMessage(), e); + } } } else { String ctxPath = Global.getCtxPath(); @@ -106,8 +137,10 @@ public class ArticleVectorStoreImpl implements ArticleVectorStore { url = url.substring(ctxPath.length()); } try (InputStream is = IOUtils.getFileInputStream(Global.getUserfilesBaseDir(url))){ - String text = getDocumentText(is); - attachmentList.add(url + text); + if (is != null) { + String text = getDocumentText(is); + attachmentList.add(url + text); + } } catch (IOException | TikaException e) { logger.error(e.getMessage(), e); } @@ -123,25 +156,25 @@ public class ArticleVectorStoreImpl implements ArticleVectorStore { */ private static @NotNull String getDocumentText(InputStream is) throws IOException, TikaException { TikaConfig config = TikaConfig.getDefaultConfig(); - String content = new Tika(config).parseToString(is); + Tika tika = new Tika(config); + Metadata metadata = new Metadata(); + TikaInputStream stream = TikaInputStream.get(is); + MediaType mimetype = tika.getDetector().detect(stream, metadata); + if (mimetype != null && StringUtils.equals(mimetype.getType(), "text")) { + String text = IOUtils.toString(stream, StandardCharsets.UTF_8); + if (StringUtils.isNotBlank(text)) { + return FlexmarkHtmlConverter.builder().build().convert(text); + } else { + return text; + } + } + String content = tika.parseToString(stream, metadata); return content.lines() .map(String::strip).filter(line -> !line.isEmpty()) .reduce((a, b) -> a + System.lineSeparator() + b) .orElse(StringUtils.EMPTY); } }; - String content = article.getTitle() + ", " + article.getKeywords() + ", " - + article.getDescription() + ", " + FlexmarkHtmlConverter.builder() - .linkResolverFactory(linkResolverFactory).build() - .convert(article.getArticleData().getContent()) - + ", attachment: " + attachmentList; - List documents = List.of(new Document(article.getId(), content, metadata)); - List splitDocuments = new TokenTextSplitter().apply(documents); - this.delete(article); // 删除原数据 - ListUtils.pageList(splitDocuments, 64, params -> { - vectorStore.add((List)params[0]); // 增加新数据 - return null; - }); } /** @@ -150,6 +183,7 @@ public class ArticleVectorStoreImpl implements ArticleVectorStore { */ @Override public void delete(Article article) { + if (vectorStore == null) return; if (StringUtils.isNotBlank(article.getId())) { vectorStore.delete(new FilterExpressionBuilder().eq("id", article.getId()).build()); } @@ -160,6 +194,7 @@ public class ArticleVectorStoreImpl implements ArticleVectorStore { * @author ThinkGem */ public String rebuild(Article article) { + if (vectorStore == null) return null; logger.debug("开始重建向量库。 siteCode: {}, categoryCode: {}", article.getCategory().getSite().getSiteCode(), article.getCategory().getCategoryCode()); diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CacheChatMemory.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CacheChatMemoryRepository.java similarity index 48% rename from modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CacheChatMemory.java rename to modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CacheChatMemoryRepository.java index 3255204c8a674083c03691f5dd73a09834215f6b..d8d51d313805438a75a44d7eef7a1e9b917c5221 100644 --- a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CacheChatMemory.java +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CacheChatMemoryRepository.java @@ -5,8 +5,8 @@ package com.jeesite.modules.cms.ai.service; import com.jeesite.common.cache.CacheUtils; -import com.jeesite.common.collect.ListUtils; -import org.springframework.ai.chat.memory.ChatMemory; +import org.jetbrains.annotations.NotNull; +import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.messages.Message; import org.springframework.stereotype.Service; @@ -17,29 +17,28 @@ import java.util.List; * @author ThinkGem */ @Service -public class CacheChatMemory implements ChatMemory { +public class CacheChatMemoryRepository implements ChatMemoryRepository { private static final String CMS_CHAT_MSG_CACHE = "cmsChatMsgCache"; @Override - public void add(String conversationId, List messages) { - List conversationHistory = CacheUtils.get(CMS_CHAT_MSG_CACHE, conversationId); - if (conversationHistory == null) { - conversationHistory = ListUtils.newArrayList(); - } - conversationHistory.addAll(messages); - CacheUtils.put(CMS_CHAT_MSG_CACHE, conversationId, conversationHistory); + public @NotNull List findConversationIds() { + return CacheUtils.getCache(CMS_CHAT_MSG_CACHE).keys().stream().map(Object::toString).toList(); } @Override - public List get(String conversationId, int lastN) { + public @NotNull List findByConversationId(@NotNull String conversationId) { List all = CacheUtils.get(CMS_CHAT_MSG_CACHE, conversationId); - return all != null ? all.stream().skip(Math.max(0, all.size() - lastN)).toList() : List.of(); + return all != null ? all : List.of(); } @Override - public void clear(String conversationId) { - CacheUtils.remove(CMS_CHAT_MSG_CACHE, conversationId); + public void saveAll(@NotNull String conversationId, @NotNull List messages) { + CacheUtils.put(CMS_CHAT_MSG_CACHE, conversationId, messages); } + @Override + public void deleteByConversationId(@NotNull String conversationId) { + CacheUtils.remove(CMS_CHAT_MSG_CACHE, conversationId); + } } diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CmsAiChatService.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CmsAiChatService.java index 3bc3cdc2f926c8876aae21cadfc500e097d43be3..a60b63eab668c655e610cc966d70ed54bd6f2da7 100644 --- a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CmsAiChatService.java +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CmsAiChatService.java @@ -5,11 +5,16 @@ package com.jeesite.modules.cms.ai.service; import com.jeesite.common.cache.CacheUtils; +import com.jeesite.common.collect.ListUtils; import com.jeesite.common.collect.MapUtils; import com.jeesite.common.idgen.IdGen; import com.jeesite.common.lang.DateUtils; import com.jeesite.common.lang.StringUtils; +import com.jeesite.common.mapper.JsonMapper; import com.jeesite.common.service.BaseService; +import com.jeesite.modules.cms.ai.properties.CmsAiProperties; +import com.jeesite.modules.sys.entity.Area; +import com.jeesite.modules.sys.utils.AreaUtils; import com.jeesite.modules.sys.utils.UserUtils; import jakarta.servlet.http.HttpServletRequest; import org.springframework.ai.chat.client.ChatClient; @@ -18,12 +23,20 @@ import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvi import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.ai.content.Media; +import org.springframework.ai.converter.AbstractMessageOutputConverter; +import org.springframework.ai.converter.BeanOutputConverter; +import org.springframework.ai.converter.MapOutputConverter; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClientResponseException; import reactor.core.publisher.Flux; @@ -44,18 +57,23 @@ public class CmsAiChatService extends BaseService { private static final String[] USER_MESSAGE_REPLACE = new String[]{"\\{", "\\}"}; @Autowired - private ChatClient chatClient; - @Autowired - private ChatMemory chatMemory; + private ChatClient chatClient; @Autowired + private ChatMemory chatMemory; + @Autowired(required = false) private VectorStore vectorStore; + @Autowired + private CmsAiProperties properties; /** * 获取聊天对话消息 * @author ThinkGem */ public List getChatMessage(String conversationId) { - return chatMemory.get(conversationId, 100); + if (StringUtils.isBlank(conversationId)) { + return List.of(); + } + return chatMemory.get(conversationId); } private static String getChatCacheKey() { @@ -110,17 +128,28 @@ public class CmsAiChatService extends BaseService { * @author ThinkGem */ public Flux chatStream(String conversationId, String message, HttpServletRequest request) { - return chatClient.prompt() - .messages( - new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE)) - ) - .advisors( - new MessageChatMemoryAdvisor(chatMemory, conversationId, 1024), - new QuestionAnswerAdvisor(vectorStore, SearchRequest.builder().similarityThreshold(0.6F).topK(6).build()) - ) - .stream() + String text = StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE); + List media = ListUtils.newArrayList(); +// List fileUploadList = FileUploadUtils.findFileUpload(conversationId, "cms-chat"); +// for (FileUpload fileUpload : fileUploadList) { +// File file = new File(fileUpload.getFileEntity().getFileRealPath()); +// MediaType mediaType = MediaType.parseMediaType(FileUtils.getContentType(file.getName())); +// media.add(Media.builder().mimeType(mediaType).data(file).build()); +// } + UserMessage userMessage = UserMessage.builder().text(text).media(media).build(); + ChatClient.ChatClientRequestSpec spec = chatClient.prompt().messages(userMessage) + .advisors(MessageChatMemoryAdvisor.builder(chatMemory) + .conversationId(conversationId) + .build()); + if (vectorStore != null) { + spec.advisors(QuestionAnswerAdvisor.builder(vectorStore) + .searchRequest(SearchRequest.builder().similarityThreshold(0.6F).topK(6).build()) + .promptTemplate(new PromptTemplate(properties.getDefaultPromptTemplate())) + .build()); + } + return spec.stream() .chatResponse() - .doOnNext(response -> { + .doOnNext(response -> { if (response.getResult() != null && StringUtils.isNotBlank(response.getResult().getOutput().getText())) { AssistantMessage assistantMessage = (AssistantMessage)request.getAttribute("assistantMessage"); AssistantMessage currAssistantMessage = response.getResult().getOutput(); @@ -155,6 +184,85 @@ public class CmsAiChatService extends BaseService { .generations(List.of(new Generation(assistantMessage))) .build()); }); + } + + /** + * 聊天对话,文本输出 + * @author ThinkGem + */ + public String chatText(String message) { + return chatClient.prompt() + .messages( + new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE)) + ) + .call() + .content(); } + /** + * 聊天对话,结构化输出(Map) + * @author ThinkGem + */ + public Map chatJson(String message) { + return chatClient.prompt() + .messages( + new SystemMessage(""" + [{name:'张三', sex:'男', age:'17'}, {name:'李四', sex:'女', age:'18'}],返回 json。 + """), + new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE)) + ) + .call() + .responseEntity( + new AbstractMessageOutputConverter>( + new MappingJackson2MessageConverter(JsonMapper.getInstance()) + ) { + final MapOutputConverter mapOutputConverter = new MapOutputConverter(); + @Override + public Map convert(String source) { + return mapOutputConverter.convert(source); + } + @Override + public String getFormat() { + return mapOutputConverter.getFormat(); + } + } + ) + .getEntity(); + } + + /** + * 聊天对话,结构化输出(Area) + * @author ThinkGem + */ + public List chatArea(String message) { + List list = AreaUtils.getAreaAllList(); + if (list.size() > 10) list = list.subList(0, 10); + ChatClient.ChatClientRequestSpec spec = chatClient.prompt() + .messages( + new SystemMessage(JsonMapper.toJson(list)), + new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE)) + ); + if (vectorStore != null) { + spec.advisors(QuestionAnswerAdvisor.builder(vectorStore) + .searchRequest(SearchRequest.builder().similarityThreshold(0.6F).topK(6).build()) + .promptTemplate(new PromptTemplate(properties.getDefaultPromptTemplate())) + .build()); + } + return spec.call() + .responseEntity(new BeanOutputConverter<>(new ParameterizedTypeReference>() {}, + JsonMapper.getInstance())) + .getEntity(); + } + +// public static void main(String[] args) { +// String s = """ +// [{"id":"110000","isNewRecord":false,"createBy":"system","createDate":"2025-01-01T19:25:11Z","updateBy":"system","updateDate":"2025-01-01 19:25","childList":[{"id":"110100","isNewRecord":false,"createBy":"system","createDate":"2025-01-01 19:25","updateBy":"system","updateDate":"2025-01-01 19:25","childList":[{"id":"110101","isNewRecord":false,"areaCode":"110101","areaName":"东城区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110102","isNewRecord":false,"areaCode":"110102","areaName":"西城区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110105","isNewRecord":false,"areaCode":"110105","areaName":"朝阳区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110106","isNewRecord":false,"areaCode":"110106","areaName":"丰台区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110107","isNewRecord":false,"areaCode":"110107","areaName":"石景山区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110108","isNewRecord":false,"areaCode":"110108","areaName":"海淀区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110109","isNewRecord":false,"areaCode":"110109","areaName":"门头沟区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110111","isNewRecord":false,"areaCode":"110111","areaName":"房山区","areaType":"3","isRoot":true,"isTreeLeaf":false}],"areaCode":"110100","areaName":"北京城区","areaType":"2","isRoot":true,"isTreeLeaf":false}],"areaCode":"110000","areaName":"北京市","areaType":"1","isRoot":true,"isTreeLeaf":false}] +// """; +// JsonMapper jsonMapper = JsonMapper.getInstance(); +// ParameterizedTypeReference> p = new ParameterizedTypeReference>() {}; +// List entity = jsonMapper.fromJsonString(s, jsonMapper.constructType(p.getType())); +// System.out.println(entity); +// String json = jsonMapper.toJsonString(entity); +// System.out.println(json); +// } } diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/tools/CmsAiTools.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/tools/CmsAiTools.java index b323eb6ad8cf5e74140044b02f6c373003f22377..8f53fcfd3ba52032cde0ae1cb27098280affc370 100644 --- a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/tools/CmsAiTools.java +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/tools/CmsAiTools.java @@ -33,8 +33,14 @@ public class CmsAiTools { * 你可以询问:打开客厅的灯,关闭卧室的灯(需创建新对话) */ @Tool(description = "房间里的灯打开或关闭") - public void turnLight(@ToolParam(description = "房间") String roomName, @ToolParam(description = "开关") boolean on) { + public String turnLight(@ToolParam(description = "房间") String roomName, @ToolParam(description = "开关") boolean on) { String message = roomName + " 房间里的灯被 " + (on ? "打开" : "关闭"); logger.info(message + " ============== "); + return String.format(""" + message: %s + roomName: %s + on: %s + """, + roomName, on, message); } } \ No newline at end of file diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/web/CmsAiChatController.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/web/CmsAiChatController.java index 1431461128697a752a1f5c183bc7cab849f63441..0cdd1f5b08aff7aba05f0bd63f955d02ef5ba789 100644 --- a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/web/CmsAiChatController.java +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/web/CmsAiChatController.java @@ -7,12 +7,14 @@ package com.jeesite.modules.cms.ai.web; import com.jeesite.common.config.Global; import com.jeesite.common.web.BaseController; import com.jeesite.modules.cms.ai.service.CmsAiChatService; +import com.jeesite.modules.sys.entity.Area; import jakarta.servlet.http.HttpServletRequest; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; @@ -37,49 +39,81 @@ public class CmsAiChatController extends BaseController { * 获取聊天对话消息 * @author ThinkGem */ - @RequestMapping("/message") - public List message(String id) { - return cmsAiChatService.getChatMessage(id); - } + @RequestMapping("/message") + public List message(String id) { + return cmsAiChatService.getChatMessage(id); + } /** * 聊天对话列表 * @author ThinkGem */ - @RequestMapping("/list") - public Collection> list() { + @RequestMapping("/list") + public Collection> list() { return cmsAiChatService.getChatCacheMap().values().stream() - .sorted(Comparator.comparing(map -> (String) map.get("id"), - Comparator.reverseOrder())).collect(Collectors.toList()); - } + .sorted(Comparator.comparing(map -> (String) map.get("id"), + Comparator.reverseOrder())).collect(Collectors.toList()); + } /** * 新建或更新聊天对话 * @author ThinkGem */ @RequestMapping("/save") - public String save(String id, String title) { + public String save(String id, String title) { Map map = cmsAiChatService.saveChatConversation(id, title); - return renderResult(Global.TRUE, "保存成功", map); - } + return renderResult(Global.TRUE, "保存成功", map); + } /** * 删除聊天对话 * @author ThinkGem */ - @RequestMapping("/delete") - public String delete(String id) { + @RequestMapping("/delete") + public String delete(@RequestParam String id) { cmsAiChatService.deleteChatConversation(id); - return renderResult(Global.TRUE, "删除成功", id); - } + return renderResult(Global.TRUE, "删除成功", id); + } /** * 聊天对话,流输出 * @author ThinkGem + * http://127.0.0.1:8980/js/a/cms/chat/stream?id=1&message=你好 */ - @RequestMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - public Flux stream(String id, String message, HttpServletRequest request) { + @RequestMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux stream(@RequestParam String id, @RequestParam String message, HttpServletRequest request) { return cmsAiChatService.chatStream(id, message, request); - } + } + + /** + * 聊天对话,文本输出 + * @author ThinkGem + * http://127.0.0.1:8980/js/a/cms/chat/text?message=你好 + */ + @RequestMapping(value = "/text") + public String text(@RequestParam String message) { + return cmsAiChatService.chatText(message); + } + + /** + * 聊天对话,结构化输出 JSON + * @author ThinkGem + * http://127.0.0.1:8980/js/a/cms/chat/json?message=张三 + * http://127.0.0.1:8980/js/a/cms/chat/json?message=打开客厅的灯 + */ + @RequestMapping(value = "/json") + public Map json(@RequestParam String message) { + return cmsAiChatService.chatJson(message); + } + + /** + * 聊天对话,结构化输出 Entity + * @author ThinkGem + * http://127.0.0.1:8980/js/a/cms/chat/entity?message=北京 + */ + @RequestMapping(value = "/entity") + public List entity(@RequestParam String message) { + return cmsAiChatService.chatArea(message); + } } diff --git a/modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml b/modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml index ffc6bf4337f93566b9da3020ddacd295d6aaf3d9..d93b906a033c1d54b22e2bd54272c1d1a817f4c3 100644 --- a/modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml +++ b/modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml @@ -3,33 +3,68 @@ spring: ai: + # 模型选择:openai、ollama + model: + chat: openai + embedding: ${spring.ai.model.chat} + image: ${spring.ai.model.chat} + audio: ${spring.ai.model.chat} + # 在线大模型【请在 pom.xml 中打开 openai 的注释,并注释上其它模型】 openai: + + # 硅基流动 base-url: https://api.siliconflow.cn api-key: ${SFLOW_APP_KEY} - #base-url: https://ai.gitee.com - #api-key: ${GITEE_APP_KEY} - #base-url: https://dashscope.aliyuncs.com/compatible-mode - #api-key: ${BAILIAN_APP_KEY} # 聊天对话模型 chat: options: model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B - #model: DeepSeek-R1-Distill-Qwen-14B - #model: deepseek-r1-distill-llama-8b max-tokens: 1024 temperature: 0.6 top-p: 0.9 frequency-penalty: 0 - #logprobs: true # 向量库知识库模型(注意:不同的模型维度不同) embedding: options: model: BAAI/bge-m3 - #model: bge-large-zh-v1.5 dimensions: 512 - #model: text-embedding-v3 - #dimensions: 1024 + +# # 模力方舟 +# base-url: https://ai.gitee.com +# api-key: ${GITEE_APP_KEY} +# # 聊天对话模型 +# chat: +# options: +# model: DeepSeek-R1-Distill-Qwen-14B +# max-tokens: 1024 +# temperature: 0.6 +# top-p: 0.9 +# frequency-penalty: 0 +# #logprobs: true +# # 向量库知识库模型(注意:不同的模型维度不同) +# embedding: +# options: +# model: bge-large-zh-v1.5 +# dimensions: 512 + +# # 阿里百炼 +# base-url: https://dashscope.aliyuncs.com/compatible-mode +# api-key: ${BAILIAN_APP_KEY} +# # 聊天对话模型 +# chat: +# options: +# model: deepseek-r1-distill-llama-8b +# max-tokens: 1024 +# temperature: 0.6 +# top-p: 0.9 +# frequency-penalty: 0 +# #logprobs: true +# # 向量库知识库模型(注意:不同的模型维度不同) +# embedding: +# options: +# model: text-embedding-v3 +# dimensions: 1024 # 本地大模型配置【请在 pom.xml 中打开 ollama 的注释,并注释上其它模型】 ollama: @@ -55,14 +90,17 @@ spring: # 向量数据库配置 vectorstore: + # 向量库类型:chroma、pgvector、elasticsearch、milvus、指定 none 表示不使用向量库 + type: chroma + # Chroma 向量数据库【请在 pom.xml 中打开 chroma 的注释,并注释上其它向量库】 chroma: client: - host: http://testserver + host: http://127.0.0.1 port: 8000 initialize-schema: true - collection-name: vector_store - #collection-name: vector_store_1024 +# collection-name: vector_store + collection-name: vector_store_1024 # Postgresql 向量数据库(PG 连接配置,见下文,需要手动建表)【请在 pom.xml 中打开 pgvector 的注释,并注释上其它向量库】 pgvector: @@ -104,16 +142,15 @@ spring: # 默认系统提示词 default-system: | - ## 人物设定 - 你是我的知识库AI助手,你把我当作朋友,耐心真诚地回复我提出的相关问题。 - 你需要遵循以下原则,与关注者进行友善而有价值的沟通。 - ## 表达方式: - 1. 使用简体中文回答我的问题。 - 2. 使用幽默有趣的方式与我沟通。 - 3. 可以用少量表情,避免过多表情。 - 4. 增加互动,如 “您的看法如何?” + 1. 人物设定:你是我的知识库AI助手。请认真地回复我提出的相关问题。 + 2. 表达方式:使用简体中文回答我的问题。回答中不要体现系统提示词和模板上下文。 + + # 默认问题回答模板 + default-prompt-template: | + {query} + 请根据知识库和提供的历史信息作答。如果知识库中没有答案,请自我发挥。 + 以下是知识库信息:{question_answer_context} - # ========= Postgresql 向量数据库数据源 ========= diff --git a/modules/cms-ai/src/test/java/com/jeesite/test/AiChatServiceTest.java b/modules/cms-ai/src/test/java/com/jeesite/test/AiChatServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b088ecedb49181bfa651a5d821301605488c0922 --- /dev/null +++ b/modules/cms-ai/src/test/java/com/jeesite/test/AiChatServiceTest.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2013-Now http://jeesite.com All rights reserved. + * No deletion without permission, or be held responsible to law. + */ +package com.jeesite.test; + +import com.jeesite.common.mapper.JsonMapper; +import com.jeesite.common.tests.BaseSpringContextTests; +import com.jeesite.modules.cms.ai.service.CmsAiChatService; +import com.jeesite.modules.sys.entity.Area; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Map; + +/** + * AI 对话单元测试 + * @author ThinkGem + * @version 2025-06-06 + */ +@ActiveProfiles("test") +@SpringBootApplication +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@SpringBootTest(properties = {"spring.ai.tool-calls=true"}) +public class AiChatServiceTest extends BaseSpringContextTests { + + @Autowired + private CmsAiChatService cmsAiChatService; + + @Test + public void test01Text() { + logger.info("===== 聊天对话,文本输出"); + String message = "你好"; + String text = cmsAiChatService.chatText(message); + System.out.println(text); + } + + @Test + public void test02Json() { + logger.info("===== 聊天对话,结构化输出 JSON"); + String message = "张三"; + Map map = cmsAiChatService.chatJson(message); + System.out.println(JsonMapper.toJson(map)); + } + + @Test + public void test03Tool() { + logger.info("===== 聊天对话,结构化输出 Tool Calling"); + String message = "打开客厅的灯"; + Map map = cmsAiChatService.chatJson(message); + System.out.println(JsonMapper.toJson(map)); + message = "关闭客厅的灯"; + map = cmsAiChatService.chatJson(message); + System.out.println(JsonMapper.toJson(map)); + } + + @Test + public void test04Entity() { + logger.info("===== 聊天对话,结构化输出 Entity"); + String message = "北京"; + List list = cmsAiChatService.chatArea(message); + System.out.println(JsonMapper.toJson(list)); + } + + +} diff --git a/modules/cms-ai/src/test/resources/application.yml b/modules/cms-ai/src/test/resources/application.yml new file mode 100644 index 0000000000000000000000000000000000000000..f03254a30a3df3971b878d2e9c893c0cf930f63f --- /dev/null +++ b/modules/cms-ai/src/test/resources/application.yml @@ -0,0 +1,28 @@ + +# 产品或项目名称、软件开发公司名称 +productName: JeeSite Demo +companyName: ThinkGem + +# 产品版本、版权年份 +productVersion: V5.12 +copyrightYear: 2025 + + +# 数据库连接 +jdbc: + + # Mysql 数据库配置 + type: mysql + driver: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/jeesite_v5?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai + username: root + password: 123456 + testSql: SELECT 1 + +# 日志配置 +logging: + config: classpath:logback-test.xml + +# 消息推送 +msg: + enabled: true diff --git a/modules/cms-ai/src/test/resources/logback-test.xml b/modules/cms-ai/src/test/resources/logback-test.xml new file mode 100644 index 0000000000000000000000000000000000000000..a9a15b7f75424f229563ae6aad8a8010b30ad537 --- /dev/null +++ b/modules/cms-ai/src/test/resources/logback-test.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + %d{HH:mm:ss.SSS} %clr(%-5p) %clr([%-39logger{39}]){cyan} - %m%n%wEx + + + + + + + + + \ No newline at end of file diff --git a/modules/cms/pom.xml b/modules/cms/pom.xml index 92cff54efacbdbf0c9525365c8a5f64b0ab1c32b..52baafd7fb1cbccd779c7d405ad73423a5cd1a4e 100644 --- a/modules/cms/pom.xml +++ b/modules/cms/pom.xml @@ -6,7 +6,7 @@ com.jeesite jeesite-parent - 5.11.1.springboot3-SNAPSHOT + 5.12.1.springboot3-SNAPSHOT ../../parent/pom.xml diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Article.java b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Article.java index 8e279935b1cf6f59c5ac1987f989fd2a9d0755d1..0c243de261e0611f7c6802b9d14188e609124045 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Article.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Article.java @@ -16,6 +16,8 @@ import com.jeesite.modules.cms.utils.CmsUtils; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; + +import java.io.Serial; import java.util.Date; /** @@ -66,6 +68,7 @@ import java.util.Date; public class Article extends DataEntity
{ public static final String DEFAULT_TEMPLATE = "viewArticle"; // 默认文章内容模板 + @Serial private static final long serialVersionUID = 1L; private Category category; // 栏目编码 diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticleData.java b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticleData.java index d4440bbd35b11281f0e92334176d54778b17119b..a297d71019c88e1bf2968a6f32dc555b35c4766b 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticleData.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticleData.java @@ -11,6 +11,8 @@ import com.jeesite.common.entity.Extend; import com.jeesite.common.mybatis.annotation.Column; import com.jeesite.common.mybatis.annotation.Table; +import java.io.Serial; + /** * 文章详情表Entity * @author 长春叭哥、ThinkGem @@ -26,6 +28,7 @@ import com.jeesite.common.mybatis.annotation.Table; ) public class ArticleData extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String content; // 文章内容 private String relation; // 相关文章 diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticlePosid.java b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticlePosid.java index 76b14336868e90704a07d5757de56f5714dcd562..5973af7b8ca9cb0725121ab72b3217d1d1ee8de0 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticlePosid.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticlePosid.java @@ -8,6 +8,8 @@ import com.jeesite.common.entity.DataEntity; import com.jeesite.common.mybatis.annotation.Column; import com.jeesite.common.mybatis.annotation.Table; +import java.io.Serial; + /** * 文章推荐位Entity * @author 长春叭哥、ThinkGem @@ -20,6 +22,7 @@ import com.jeesite.common.mybatis.annotation.Table; ) public class ArticlePosid extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String articleId; // 内容编号 private String postid; // 推荐位置(1轮播图 2首页推荐 3栏目页面) diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticleTag.java b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticleTag.java index 6f307240426286e3c81aae2bedcd51e7d24b204e..0e6435e5112e595e03d86894e80fbb5c3fc9973e 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticleTag.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/ArticleTag.java @@ -8,6 +8,8 @@ import com.jeesite.common.entity.DataEntity; import com.jeesite.common.mybatis.annotation.Column; import com.jeesite.common.mybatis.annotation.Table; +import java.io.Serial; + /** * 文章与标签关系Entity * @author 长春叭哥、ThinkGem @@ -20,6 +22,7 @@ import com.jeesite.common.mybatis.annotation.Table; ) public class ArticleTag extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String articleId; // 内容编号 private String tagName; // 标签名称 diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Category.java b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Category.java index 55fa7b20f3f43d764658017fb4219ed5187e938d..4d952f6dcb51933ed137f9cc19eec1b4f316c846 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Category.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Category.java @@ -16,6 +16,8 @@ import com.jeesite.modules.cms.utils.CmsUtils; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; + +import java.io.Serial; import java.util.List; /** @@ -59,6 +61,7 @@ public class Category extends TreeEntity { public static final String SHOW_MODES_CENTENT_LIST = "2"; // 首栏目内容列表 public static final String SHOW_MODES_FIRST_CONTENT = "3"; // 简介类栏目,栏目第一条内容 + @Serial private static final long serialVersionUID = 1L; private String categoryCode; // 栏目编码 private String categoryName; // 栏目名称 diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Comment.java b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Comment.java index a5ad74a99744fe43bc5cc3a5d0c9a2bc8dbe35cc..06a61c32ffa243c758479b0ea8b625c0e3ba231c 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Comment.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Comment.java @@ -4,6 +4,7 @@ */ package com.jeesite.modules.cms.entity; +import java.io.Serial; import java.util.Date; import jakarta.validation.constraints.NotBlank; @@ -43,6 +44,7 @@ import com.jeesite.common.mybatis.mapper.query.QueryType; ) public class Comment extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private Category category;// 分类编号 diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/FileTemplete.java b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/FileTemplete.java index c18d6665ccda1240ee9e060bba0bc58e00ac2a7d..6e995798b3d4832ccb0d22e6f79c8dbbf54c8430 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/FileTemplete.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/FileTemplete.java @@ -6,6 +6,7 @@ package com.jeesite.modules.cms.entity; import java.io.IOException; import java.io.InputStream; +import java.io.Serial; import java.io.Serializable; import java.util.Objects; @@ -23,6 +24,7 @@ import com.jeesite.common.lang.ExceptionUtils; */ public class FileTemplete implements Comparable, Serializable { + @Serial private static final long serialVersionUID = 1L; private Resource resource; private String fileName; diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Report.java b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Report.java index 4e3efc3850ed0470a4c27b072a0260ea31059e3c..4fda06fb4e59a01c8c0e62c1709c8b3206eac9c8 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Report.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Report.java @@ -10,6 +10,8 @@ import com.jeesite.common.entity.DataEntity; import com.jeesite.common.mybatis.annotation.Column; import com.jeesite.common.mybatis.annotation.Table; +import java.io.Serial; + /** * 内容举报表Entity * @author 长春叭哥、ThinkGem @@ -26,6 +28,7 @@ import com.jeesite.common.mybatis.annotation.Table; ) public class Report extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String reportSource; // 举报来源(1文章、2评论) private String reportContent; // 举报内容(文章标题 评论内容) diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Site.java b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Site.java index 8933c02a427737b0c9f6d51de47bd4ed75e4994f..44b24bcb9b42b217068ff6bcf5a879686078eded 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Site.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Site.java @@ -16,6 +16,8 @@ import com.jeesite.modules.sys.utils.UserUtils; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; + +import java.io.Serial; import java.util.List; /** @@ -53,6 +55,7 @@ public class Site extends DataEntity { */ public static final String DEFAULT_TEMPLATE = "index"; + @Serial private static final long serialVersionUID = 1L; private String siteCode; // 站点编码 private String siteName; // 站点名称 diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Tag.java b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Tag.java index 524011d39605e985677134ea252cb6bd4d90aebd..493dde2f6bdddcf32f8d27b6f698db346839d2f7 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Tag.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/Tag.java @@ -10,6 +10,8 @@ import com.jeesite.common.entity.DataEntity; import com.jeesite.common.mybatis.annotation.Column; import com.jeesite.common.mybatis.annotation.Table; +import java.io.Serial; + /** * 内容标签Entity * @author 长春叭哥、ThinkGem @@ -22,6 +24,7 @@ import com.jeesite.common.mybatis.annotation.Table; ) public class Tag extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String tagName; // 标签名称 private Integer clicknum; // 点击次数 diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/VisitLog.java b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/VisitLog.java index 81bb916d5ce812427c56da489b0d160ebe16a5bb..7f8c719db51af8a2dda4183d63786f143eb257c5 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/entity/VisitLog.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/entity/VisitLog.java @@ -4,6 +4,7 @@ */ package com.jeesite.modules.cms.entity; +import java.io.Serial; import java.util.Date; import jakarta.validation.constraints.Size; @@ -57,6 +58,7 @@ import com.jeesite.common.mybatis.mapper.query.QueryType; ) public class VisitLog extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String requestUrl; // 请求的URL地址 private String requestUrlHost; // 受访域名 diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/service/CategoryService.java b/modules/cms/src/main/java/com/jeesite/modules/cms/service/CategoryService.java index 3dc2277045ec8e5f8129134012891c6453b9a617..94b8ce5a3ab0fa427f8660f698593ab708b99c76 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/service/CategoryService.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/service/CategoryService.java @@ -133,7 +133,7 @@ public class CategoryService extends TreeService { */ public String rebuildVectorStore(Category category) { if (articleVectorStore == null) { - return text("您好,系统未安装全文检索模块"); + return text("您好,系统未配置向量数据库"); } return articleVectorStore.rebuild(new Article(category)); } diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/service/SiteService.java b/modules/cms/src/main/java/com/jeesite/modules/cms/service/SiteService.java index 8ff3b909667e7e6d2d479117ffbafa8729c622b7..b638078a81cbd888d9a65eca2fd72bced4e83489 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/service/SiteService.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/service/SiteService.java @@ -129,7 +129,7 @@ public class SiteService extends CrudService { */ public String rebuildVectorStore(Site site) { if (articleVectorStore == null) { - return text("您好,系统未安装内容管理AI模块"); + return text("您好,系统未配置向量数据库"); } return articleVectorStore.rebuild(new Article(new Category(site))); } diff --git a/modules/cms/src/main/java/com/jeesite/modules/cms/utils/FileTempleteUtils.java b/modules/cms/src/main/java/com/jeesite/modules/cms/utils/FileTempleteUtils.java index 899574b5685db8f9256d99e72e249f3adf0e6212..31d7114c58b2b4e84c12e4bf991def40ecbd2dc2 100644 --- a/modules/cms/src/main/java/com/jeesite/modules/cms/utils/FileTempleteUtils.java +++ b/modules/cms/src/main/java/com/jeesite/modules/cms/utils/FileTempleteUtils.java @@ -4,16 +4,16 @@ */ package com.jeesite.modules.cms.utils; -import java.io.IOException; -import java.util.List; -import java.util.Set; - -import org.springframework.core.io.Resource; - import com.jeesite.common.collect.ListUtils; import com.jeesite.common.collect.SetUtils; import com.jeesite.common.io.ResourceUtils; +import com.jeesite.common.lang.StringUtils; import com.jeesite.modules.cms.entity.FileTemplete; +import org.springframework.core.io.Resource; + +import java.io.IOException; +import java.util.List; +import java.util.Set; /** * 模板文件公共类库 @@ -26,7 +26,10 @@ public class FileTempleteUtils { * 获取模版文件 * @param fileName */ - public static FileTemplete getFileTempleteByResource(String fileName) throws IOException { + public static FileTemplete getFileTempleteByResource(String fileName) { + if (!StringUtils.startsWith(fileName, "views/modules/cmsfront")) { + fileName = "views/modules/cmsfront/themes/default/index.html"; + } Resource resource = ResourceUtils.getResource(fileName); return new FileTemplete(resource, fileName); } diff --git a/modules/cms/src/main/resources/config/beetl-cms.properties b/modules/cms/src/main/resources/config/beetl-cms.properties index e27fdd333cf4954281d089e1bd85f0dc1b10c743..da983c62d9f2c925a7d5ee8b230e7008e8f7ad7d 100644 --- a/modules/cms/src/main/resources/config/beetl-cms.properties +++ b/modules/cms/src/main/resources/config/beetl-cms.properties @@ -12,10 +12,10 @@ IMPORT_PACKAGE_cms=\ #FNP.strutil = org.beetl.ext.fn.StringUtil ##内置的格式化函数 -#FT.dateFormat = org.beetl.ext.format.DateFormat +#FT.dateFormat = com.jeesite.common.beetl.ext.format.DateFormat ##内置的默认格式化函数 -#FTC.java.util.Date = org.beetl.ext.format.DateFormat +#FTC.java.util.Date = com.jeesite.common.beetl.ext.format.DateFormat ## 标签类 #TAG.include= org.beetl.ext.tag.IncludeTag diff --git a/modules/cms/src/main/resources/db/upgrade/cms/versions b/modules/cms/src/main/resources/db/upgrade/cms/versions index dfba06d344f58422e869be0000994cbfe6d5994c..e24a7341a7a97acff90161fef44d3b3eb56f749c 100644 --- a/modules/cms/src/main/resources/db/upgrade/cms/versions +++ b/modules/cms/src/main/resources/db/upgrade/cms/versions @@ -34,4 +34,6 @@ 5.10.0 5.10.1 5.11.0 -5.11.1 \ No newline at end of file +5.11.1 +5.12.0 +5.12.1 \ No newline at end of file diff --git a/modules/cms/src/main/resources/views/modules/cms/articleList.html b/modules/cms/src/main/resources/views/modules/cms/articleList.html index 464822aac76afaa6eefc2a5b929efca4e658d568..f471ade29dd3c2d4d958417ba683ad3000833bc2 100644 --- a/modules/cms/src/main/resources/views/modules/cms/articleList.html +++ b/modules/cms/src/main/resources/views/modules/cms/articleList.html @@ -9,7 +9,7 @@ ${text('查询')} ${text('访问网站')} <% if(hasPermi('cms:article:edit')){ %> - ${text('新增')} + ${text('新增')} <% } %> diff --git a/modules/cms/src/main/resources/views/modules/cms/categoryIndex.html b/modules/cms/src/main/resources/views/modules/cms/categoryIndex.html index 8257e93d4806fb0810c1b8c904f7ef40ae89a581..215d1a4311c2a47e8c08c60c2508feb03e6c7a04 100644 --- a/modules/cms/src/main/resources/views/modules/cms/categoryIndex.html +++ b/modules/cms/src/main/resources/views/modules/cms/categoryIndex.html @@ -41,7 +41,7 @@ var setting = {view:{selectedMulti:false},data:{key:{title:"title"},simpleData:{ tree.expandNode(treeNode); //win.$('button[type=reset]').click(); win.$('#categoryCode').val(treeNode.id); - win.page(); + win.page(1); }} }, tree, loadTree = function(){ js.ajaxSubmit(setting.async.url+"?___t="+new Date().getTime(), { diff --git a/modules/cms/src/main/resources/views/modules/cms/cmsIndex.html b/modules/cms/src/main/resources/views/modules/cms/cmsIndex.html index dfa374a7ebd53e9dd99d0d05536c49d99617d5c3..c439c8858d101a6475bf9dd51cd11ae6b7ca850e 100644 --- a/modules/cms/src/main/resources/views/modules/cms/cmsIndex.html +++ b/modules/cms/src/main/resources/views/modules/cms/cmsIndex.html @@ -58,13 +58,13 @@ var setting = { && ((src.indexOf("article") > 0 && adminUrl.indexOf("article") > 0) || (src.indexOf("link") > 0 && adminUrl.indexOf("link") > 0))){ var win = ifr[0].contentWindow, conts = ifr.contents(); - conts.find('input[type=reset]').click(); + //conts.find('input[type=reset]').click(); conts.find('#categoryCode').val(treeNode.id); conts.find('#outline').val(adminUrl.indexOf("outline=true") != -1); // 文章模型是否显示大纲视图 conts.find('#fileDown').val(adminUrl.indexOf("fileDown=true") != -1); // 链接模型是否是下载栏目 var caption = conts.find('.portlet-title .caption'); caption.html(caption.find('i').prop("outerHTML") + " " + treeNode.name); - win.page(); + win.page(1); }else{ $('#mainFrame').attr("src", adminUrl); } diff --git a/modules/core/pom.xml b/modules/core/pom.xml index 79044bac338123f3d15f6932ecacf5a32d969aa8..2899bd22103fdd7163f86bd7f7eaf3ad533ff167 100644 --- a/modules/core/pom.xml +++ b/modules/core/pom.xml @@ -6,7 +6,7 @@ com.jeesite jeesite-parent - 5.11.1.springboot3-SNAPSHOT + 5.12.1.springboot3-SNAPSHOT ../../parent/pom.xml @@ -30,19 +30,19 @@ runtime - + + com.oracle.database.jdbc ojdbc8 runtime - --> + - com.dameng - Dm8JdbcDriver18 - 8.1.1.49 - --> + DmJdbcDriver18 + 8.1.3.62 + runtime + - - com.kingbase - kingbasejdbc8 - 8.6.0 - --> + cn.com.kingbase + kingbase8 + 8.6.1 + runtime + @@ -140,6 +142,13 @@ logstash-logback-encoder ${logstash-logback.version} + + + + org.springframework.boot + spring-boot-devtools + true + diff --git a/modules/core/src/main/java/com/jeesite/common/shiro/authc/LdapToken.java b/modules/core/src/main/java/com/jeesite/common/shiro/authc/LdapToken.java index 1dd4a97ca03fe95d166b94c1949ed4c04c151c16..db3b6c998b05964e91589c9009a6c522eaa1a92a 100644 --- a/modules/core/src/main/java/com/jeesite/common/shiro/authc/LdapToken.java +++ b/modules/core/src/main/java/com/jeesite/common/shiro/authc/LdapToken.java @@ -4,6 +4,7 @@ */ package com.jeesite.common.shiro.authc; +import java.io.Serial; import java.util.Map; /** @@ -13,6 +14,7 @@ import java.util.Map; */ public class LdapToken extends FormToken { + @Serial private static final long serialVersionUID = 1L; public LdapToken() { diff --git a/modules/core/src/main/java/com/jeesite/common/shiro/filter/FormFilter.java b/modules/core/src/main/java/com/jeesite/common/shiro/filter/FormFilter.java index 1726a7c46039d5cb743407f9be280225942a7aff..3aa510468bdab565053d8b60b426f27dcbb93d17 100644 --- a/modules/core/src/main/java/com/jeesite/common/shiro/filter/FormFilter.java +++ b/modules/core/src/main/java/com/jeesite/common/shiro/filter/FormFilter.java @@ -431,6 +431,8 @@ public class FormFilter extends org.apache.shiro.web.filter.authc.FormAuthentica data.put("company", Global.getProperty("companyName")); data.put("version", Global.getProperty("productVersion")); data.put("year", Global.getProperty("copyrightYear")); + data.put("lang", Global.getLang(request)); + data.put("timeZone", Global.getTimeZone(request)); } /** @@ -458,7 +460,8 @@ public class FormFilter extends org.apache.shiro.web.filter.authc.FormAuthentica data.put("company", Global.getProperty("companyName")); data.put("version", Global.getProperty("productVersion")); data.put("year", Global.getProperty("copyrightYear")); - data.put("lang", Global.getLang()); + data.put("lang", Global.getLang(request)); + data.put("timeZone", Global.getTimeZone(request)); List> roleList = ListUtils.newArrayList(); String desktopUrl = null; String roleCode = (String)session.getAttribute("roleCode"); Set roleCodes = roleCode != null ? SetUtils.newHashSet(StringUtils.splitComma(roleCode)) : null; diff --git a/modules/core/src/main/java/com/jeesite/common/shiro/realm/AuthorizingRealm.java b/modules/core/src/main/java/com/jeesite/common/shiro/realm/AuthorizingRealm.java index 03beb4449d3f1878c2af2eaeb259ee6e6d75280e..ceadbc00dbb1341cb7d8f86679d1180fccfa3ce9 100644 --- a/modules/core/src/main/java/com/jeesite/common/shiro/realm/AuthorizingRealm.java +++ b/modules/core/src/main/java/com/jeesite/common/shiro/realm/AuthorizingRealm.java @@ -6,7 +6,7 @@ package com.jeesite.common.shiro.realm; import com.jeesite.common.codec.EncodeUtils; import com.jeesite.common.codec.SM3Utils; -import com.jeesite.common.codec.Sha1Utils; +import com.jeesite.common.codec.ShaUtils; import com.jeesite.common.config.Global; import com.jeesite.common.shiro.authc.FormToken; import com.jeesite.common.utils.SpringUtils; @@ -30,7 +30,6 @@ import org.apache.shiro.subject.Subject; */ public class AuthorizingRealm extends BaseAuthorizingRealm { - public static final String HASH_ALGORITHM = "SHA-1"; public static final int HASH_ITERATIONS = 1024; public static final int SALT_SIZE = 8; @@ -88,7 +87,7 @@ public class AuthorizingRealm extends BaseAuthorizingRealm { String data = SM3Utils.sm3(plain, salt, HASH_ITERATIONS); return salt + data; } - String data = Sha1Utils.sha1(plain, salt, HASH_ITERATIONS); + String data = ShaUtils.sha1(plain, salt, HASH_ITERATIONS); return salt + data; } @@ -107,7 +106,7 @@ public class AuthorizingRealm extends BaseAuthorizingRealm { String data = SM3Utils.sm3(plain, salt, HASH_ITERATIONS); return password.equals(salt + data); } - String data = Sha1Utils.sha1(plain, salt, HASH_ITERATIONS); + String data = ShaUtils.sha1(plain, salt, HASH_ITERATIONS); return password.equals(salt + data); }catch(Exception e){ return false; diff --git a/modules/core/src/main/java/com/jeesite/common/ueditor/ActionEnter.java b/modules/core/src/main/java/com/jeesite/common/ueditor/ActionEnter.java index d9bee6e403c50c7274bc372961393815cc708358..77fe384325192e06d42fc6e27e4bea64d261611d 100644 --- a/modules/core/src/main/java/com/jeesite/common/ueditor/ActionEnter.java +++ b/modules/core/src/main/java/com/jeesite/common/ueditor/ActionEnter.java @@ -6,7 +6,6 @@ import com.jeesite.common.ueditor.define.AppInfo; import com.jeesite.common.ueditor.define.BaseState; import com.jeesite.common.ueditor.define.State; import com.jeesite.common.ueditor.hunter.FileManager; -import com.jeesite.common.ueditor.hunter.ImageHunter; import com.jeesite.common.ueditor.upload.Uploader; import jakarta.servlet.http.HttpServletRequest; @@ -72,13 +71,14 @@ public class ActionEnter { state = new Uploader(request, conf).doExec(); break; case ActionMap.CATCH_IMAGE: - if (Global.isDemoMode()) { - state = new BaseState(false, "演示模式,不允许操作!"); - break; - } - conf = configManager.getConfig(actionCode); - String[] list = this.request.getParameterValues((String) conf.get("fieldName")); - state = new ImageHunter(request, conf).capture(list); +// if (Global.isDemoMode()) { +// state = new BaseState(false, "演示模式,不允许操作!"); +// break; +// } +// conf = configManager.getConfig(actionCode); +// String[] list = this.request.getParameterValues((String) conf.get("fieldName")); +// state = new ImageHunter(request, conf).capture(list); + state = new BaseState(false, "该功能暂不提供支持"); break; case ActionMap.LIST_IMAGE: case ActionMap.LIST_FILE: diff --git a/modules/core/src/main/java/com/jeesite/modules/biz/entity/BizCategory.java b/modules/core/src/main/java/com/jeesite/modules/biz/entity/BizCategory.java index 6e09564fc66e0f73399548f4d8ac5fd4a5abb7bd..0442065d4a1e29e25f5b3073384ea7c0b0501945 100644 --- a/modules/core/src/main/java/com/jeesite/modules/biz/entity/BizCategory.java +++ b/modules/core/src/main/java/com/jeesite/modules/biz/entity/BizCategory.java @@ -14,6 +14,8 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; +import java.io.Serial; + /** * 业务分类Entity * @author ThinkGem @@ -30,6 +32,7 @@ import jakarta.validation.constraints.Size; ) public class BizCategory extends TreeEntity { + @Serial private static final long serialVersionUID = 1L; private String categoryCode; // 分类编码 private String viewCode; // 分类代码(作为显示用,多租户内唯一) diff --git a/modules/core/src/main/java/com/jeesite/modules/config/web/IpAddrFilterConfig.java b/modules/core/src/main/java/com/jeesite/modules/config/web/IpAddrFilterConfig.java index 3f10aa3e3d3ddc9eeadec465673c40aa874a10e7..e3fe80a79838365afd2e2fe69696197bd6b8ad48 100644 --- a/modules/core/src/main/java/com/jeesite/modules/config/web/IpAddrFilterConfig.java +++ b/modules/core/src/main/java/com/jeesite/modules/config/web/IpAddrFilterConfig.java @@ -6,6 +6,7 @@ package com.jeesite.modules.config.web; import com.jeesite.common.config.Global; import com.jeesite.common.lang.StringUtils; +import com.jeesite.common.utils.LocaleUtils; import com.jeesite.common.web.http.ServletUtils; import jakarta.servlet.Filter; import jakarta.servlet.ServletRequest; @@ -15,6 +16,7 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; +import org.springframework.web.servlet.LocaleContextResolver; /** * IP地址黑白名单过滤器配置 @@ -27,7 +29,7 @@ public class IpAddrFilterConfig { private static String[] denyPrefixes; @Bean - public FilterRegistrationBean ipAddrFilter() { + public FilterRegistrationBean ipAddrFilter(LocaleContextResolver localeResolver) { FilterRegistrationBean bean = new FilterRegistrationBean<>(); bean.setName("ipAddrFilter"); bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 10); @@ -39,7 +41,9 @@ public class IpAddrFilterConfig { response.setStatus(403); ServletUtils.renderString(response, Global.getText("访问拒绝")); } + LocaleUtils.removeTimeZoneAwareLocaleContext(); }); + LocaleUtils.setLocaleResolver(localeResolver); bean.addUrlPatterns("/*"); return bean; } diff --git a/modules/core/src/main/java/com/jeesite/modules/msg/entity/MsgInner.java b/modules/core/src/main/java/com/jeesite/modules/msg/entity/MsgInner.java index da94a4306241078f7c1bbe03add6f67aee2d3fb7..ef365d0e1d0836c76523504bb4d35c9c86432b7f 100644 --- a/modules/core/src/main/java/com/jeesite/modules/msg/entity/MsgInner.java +++ b/modules/core/src/main/java/com/jeesite/modules/msg/entity/MsgInner.java @@ -4,6 +4,7 @@ */ package com.jeesite.modules.msg.entity; +import java.io.Serial; import java.util.Date; import jakarta.validation.constraints.NotBlank; @@ -53,6 +54,7 @@ public class MsgInner extends DataEntity { public static final String CONTENT_LEVEL_2 = "2"; public static final String CONTENT_LEVEL_3 = "3"; + @Serial private static final long serialVersionUID = 1L; private String msgTitle; // 消息标题 private String contentLevel; // 内容等级(1普通 2一般 3紧急) diff --git a/modules/core/src/main/java/com/jeesite/modules/msg/entity/MsgInnerRecord.java b/modules/core/src/main/java/com/jeesite/modules/msg/entity/MsgInnerRecord.java index b90d5bf6b70cd73951313bb6bfbea2fd65238bb5..3e441036117be1586a7237dec09df63ab010a366 100644 --- a/modules/core/src/main/java/com/jeesite/modules/msg/entity/MsgInnerRecord.java +++ b/modules/core/src/main/java/com/jeesite/modules/msg/entity/MsgInnerRecord.java @@ -4,6 +4,7 @@ */ package com.jeesite.modules.msg.entity; +import java.io.Serial; import java.util.Date; import jakarta.validation.constraints.NotBlank; @@ -37,6 +38,7 @@ public class MsgInnerRecord extends DataEntity { public static final String READ_STATUS_READ = "1"; public static final String READ_STATUS_UNREAD = "2"; + @Serial private static final long serialVersionUID = 1L; private String msgInnerId; // 所属消息 private String receiveUserCode; // 接受者用户编码 diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Area.java b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Area.java index 45e06e61ad6c7f88507420075a215e2f9ff7791f..0edb30a11193e83c578be670d1387f39652b0d13 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Area.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Area.java @@ -13,6 +13,8 @@ import com.jeesite.common.mybatis.annotation.Column; import com.jeesite.common.mybatis.annotation.Table; import com.jeesite.common.mybatis.mapper.query.QueryType; +import java.io.Serial; + /** * 行政区划Entity * @author ThinkGem @@ -28,6 +30,7 @@ import com.jeesite.common.mybatis.mapper.query.QueryType; ) public class Area extends TreeEntity { + @Serial private static final long serialVersionUID = 1L; private String areaCode; // 区域代码 private String areaName; // 区域名称 diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Company.java b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Company.java index c1d230ef7108a36e1d1bb1cb150ca37cfaba4f07..08d172f912c0de330ca5fe1381c83e64c1ee30c3 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Company.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Company.java @@ -19,6 +19,8 @@ import com.jeesite.common.mybatis.mapper.query.QueryType; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; + +import java.io.Serial; import java.util.List; /** @@ -49,6 +51,7 @@ import java.util.List; ) public class Company extends TreeEntity { + @Serial private static final long serialVersionUID = 1L; private String companyCode; // 公司编码 private String viewCode; // 公司代码 diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/entity/CompanyOffice.java b/modules/core/src/main/java/com/jeesite/modules/sys/entity/CompanyOffice.java index 0c6b38654bc3cacdaf3016fa56b73a36d61a56cf..1b9b9f871d68664be6be733d68011d1771fc0771 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/entity/CompanyOffice.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/entity/CompanyOffice.java @@ -9,6 +9,8 @@ import com.jeesite.common.entity.DataEntity; import com.jeesite.common.mybatis.annotation.Column; import com.jeesite.common.mybatis.annotation.Table; +import java.io.Serial; + /** * 公司机构Entity * @author ThinkGem @@ -21,6 +23,7 @@ import com.jeesite.common.mybatis.annotation.Table; ) public class CompanyOffice extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String companyCode; // 公司编码 private String officeCode; // 机构编码 diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmpUser.java b/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmpUser.java index c9a5c5611a226196c3f6b54e4e0557dd43b6fdcb..c7cdeac43b0f21325bd20bb26a36a0d838588245 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmpUser.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmpUser.java @@ -20,6 +20,8 @@ import com.jeesite.common.utils.excel.fieldtype.OfficeType; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; +import java.io.Serial; + /** * 员工用户管理Entity * @author ThinkGem @@ -84,6 +86,7 @@ import jakarta.validation.Valid; ) public class EmpUser extends User { + @Serial private static final long serialVersionUID = 1L; private String[] codes; // 查询用 diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Employee.java b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Employee.java index ab8b950ba8a731a4d6dd9e4c51e3e8e3ed0fb1a8..9df9aa99df9912b40ada3ae91deb1774c3fa6bde 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Employee.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Employee.java @@ -17,6 +17,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; + +import java.io.Serial; import java.util.List; import java.util.stream.Collectors; @@ -50,6 +52,7 @@ import java.util.stream.Collectors; ) public class Employee extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String empCode; // 员工编码 private String empNo; // 员工工号 diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmployeeOffice.java b/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmployeeOffice.java index 6e3e4d6a3b6926ceaf2d0f6fa8362432759f18b1..8018ed3ce82f57caaf2acbe47401d7de3865f624 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmployeeOffice.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmployeeOffice.java @@ -12,6 +12,8 @@ import com.jeesite.common.mybatis.annotation.Table; import com.jeesite.common.mybatis.mapper.query.QueryType; import jakarta.validation.constraints.Size; +import java.io.Serial; + /** * 附属机构Entity * @author ThinkGem @@ -43,6 +45,7 @@ import jakarta.validation.constraints.Size; ) public class EmployeeOffice extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String empCode; // 员工编码 private String officeCode; // 机构编码 diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmployeePost.java b/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmployeePost.java index 8b413c361561625a5e596b738ca3d4bb2f393f30..1d899af687867e1f7b4247d2f02deac7648f3211 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmployeePost.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/entity/EmployeePost.java @@ -9,6 +9,8 @@ import com.jeesite.common.mybatis.annotation.Column; import com.jeesite.common.mybatis.annotation.JoinTable; import com.jeesite.common.mybatis.annotation.Table; +import java.io.Serial; + /** * 员工岗位Entity * @author ThinkGem @@ -34,6 +36,7 @@ import com.jeesite.common.mybatis.annotation.Table; ) public class EmployeePost extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String empCode; // 员工编码 private String postCode; // 岗位编码 diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Log.java b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Log.java index 8911a80adc37e5299d54542a0cd009ba7cec655c..d16b736c8b4e5912c6ed7bb0e6f6046c14bddc3a 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Log.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Log.java @@ -18,6 +18,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; + +import java.io.Serial; import java.util.Date; import java.util.Map; @@ -58,6 +60,7 @@ public class Log extends DataEntity { public static final String TYPE_SELECT = "select"; public static final String TYPE_LOGIN_LOGOUT = "loginLogout"; + @Serial private static final long serialVersionUID = 1L; private String logType; // 日志类型 diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Office.java b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Office.java index f5bfd3e454f5984bafb507cdbaa31b1f11c64f09..d7811b3dfedb67f37154f5fdada1fa617b445065 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Office.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Office.java @@ -22,6 +22,8 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; +import java.io.Serial; + /** * 组织机构Entity * @author ThinkGem @@ -47,6 +49,7 @@ import jakarta.validation.constraints.Size; @Schema public class Office extends TreeEntity { + @Serial private static final long serialVersionUID = 1L; private String officeCode; // 机构编码 private String viewCode; // 机构代码(作为显示用,多租户内唯一) diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Post.java b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Post.java index f831530830cc2bb54c1c43a569b3ce7030aabc8d..40b4403e5b6771dd7c3f8bf8decff33226dfd5af 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/entity/Post.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/entity/Post.java @@ -15,6 +15,8 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; +import java.io.Serial; + @Table(name="${_prefix}sys_post", alias="a", label="岗位信息", columns={ @Column(includeEntity=BaseEntity.class), @Column(includeEntity=DataEntity.class), @@ -27,6 +29,7 @@ import jakarta.validation.constraints.Size; ) public class Post extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String postCode; // 岗位编码 private String viewCode; // 岗位代码(作为显示用,多租户内唯一) diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/entity/PostRole.java b/modules/core/src/main/java/com/jeesite/modules/sys/entity/PostRole.java index caf32298a7cfd62441db20184e8b00be9f807c84..c01bc27dfa90815681cc2b049d45c4ca67890f13 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/entity/PostRole.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/entity/PostRole.java @@ -10,6 +10,8 @@ import com.jeesite.common.mybatis.annotation.JoinTable; import com.jeesite.common.mybatis.annotation.Table; import com.jeesite.common.mybatis.mapper.query.QueryType; +import java.io.Serial; + /** * 岗位角色Entity * @author ThinkGem @@ -31,6 +33,7 @@ import com.jeesite.common.mybatis.mapper.query.QueryType; ) public class PostRole extends DataEntity { + @Serial private static final long serialVersionUID = 1L; private String postCode; // 岗位编码 private String roleCode; // 角色编码 diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/web/ModuleController.java b/modules/core/src/main/java/com/jeesite/modules/sys/web/ModuleController.java index 9e909ba430ba60509ea0725930e26f004ee99f10..49503c2f9dfa04848c5f68605cba924e4e17b2ac 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/web/ModuleController.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/web/ModuleController.java @@ -33,7 +33,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; - /** * 模块管理Controller * @author ThinkGem diff --git a/modules/core/src/main/java/com/jeesite/modules/sys/web/SwitchController.java b/modules/core/src/main/java/com/jeesite/modules/sys/web/SwitchController.java index ada09a157097a4dc43a83bb6260329f02ea334ab..08a0f9bf2cab8a875d352df0471b460e1d4e8ead 100644 --- a/modules/core/src/main/java/com/jeesite/modules/sys/web/SwitchController.java +++ b/modules/core/src/main/java/com/jeesite/modules/sys/web/SwitchController.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import java.util.Set; @@ -136,13 +137,14 @@ public class SwitchController extends BaseController{ */ //@RequiresPermissions("user") @RequestMapping(value = "switchSkin/{skinName}") - public String switchSkin(@PathVariable String skinName, HttpServletRequest request, HttpServletResponse response) { + public String switchSkin(@PathVariable String skinName, @RequestParam(defaultValue="${adminPath}/index") String url, + HttpServletRequest request, HttpServletResponse response) { if (StringUtils.isNotBlank(skinName) && !"select".equals(skinName)){ CookieUtils.setCookie(response, "skinName", EncodeUtils.encodeUrl(EncodeUtils.xssFilter(skinName, request))); if (ServletUtils.isAjaxRequest(request)) { return renderResult(response, Global.TRUE, text("主题切换成功")); } - return REDIRECT + adminPath + "/index"; + return REDIRECT + EncodeUtils.decodeUrl2(url); } return "modules/sys/switchSkin"; } diff --git a/modules/core/src/main/resources/config/beetl-core.properties b/modules/core/src/main/resources/config/beetl-core.properties index f54015ad1bef83a855d7323f94d5932393120ab3..7731b75a98125a0bc8f25598072b9ccdf9014196 100644 --- a/modules/core/src/main/resources/config/beetl-core.properties +++ b/modules/core/src/main/resources/config/beetl-core.properties @@ -117,15 +117,15 @@ FNP.array = org.beetl.ext.fn.ArrayUtil FNP.dict = com.jeesite.common.beetl.ext.fn.DictUtil ##内置的格式化函数 -FT.dateFormat = org.beetl.ext.format.DateFormat +FT.dateFormat = com.jeesite.common.beetl.ext.format.DateFormat FT.numberFormat = com.jeesite.common.beetl.ext.format.NumberFormat FT.xss = com.jeesite.common.beetl.ext.format.XssFormat ##内置的默认格式化函数 -FTC.java.util.Date = org.beetl.ext.format.DateFormat -FTC.java.sql.Date = org.beetl.ext.format.DateFormat -FTC.java.sql.Time = org.beetl.ext.format.DateFormat -FTC.java.sql.Timestamp = org.beetl.ext.format.DateFormat +FTC.java.util.Date = com.jeesite.common.beetl.ext.format.DateFormat +FTC.java.sql.Date = com.jeesite.common.beetl.ext.format.DateFormat +FTC.java.sql.Time = com.jeesite.common.beetl.ext.format.DateFormat +FTC.java.sql.Timestamp = com.jeesite.common.beetl.ext.format.DateFormat FTC.java.lang.Short = com.jeesite.common.beetl.ext.format.NumberFormat FTC.java.lang.Long = com.jeesite.common.beetl.ext.format.NumberFormat FTC.java.lang.Integer = com.jeesite.common.beetl.ext.format.NumberFormat diff --git a/modules/core/src/main/resources/config/jeesite-core.yml b/modules/core/src/main/resources/config/jeesite-core.yml index aac7c486a5e32d0fed1321d5608f14742becd576..c752f4203f588903df9e233e0b317f4f0eadb47f 100644 --- a/modules/core/src/main/resources/config/jeesite-core.yml +++ b/modules/core/src/main/resources/config/jeesite-core.yml @@ -85,6 +85,10 @@ spring: cache: # 缓存及会话共享(专业版) isClusterMode: false + # 给缓存Key增加数据源名称前缀 v5.6.1 + keyPrefixWithDsName: false + # 指定全局Key,不加DsName前缀,当keyPrefixWithDsName设置为true的时有效(cacheName:key|*)v5.12.1 + globalKeyNames: sysCache:configMap,sysCache:moduleMap,sysCache:areaAllList # 清理全部缓存按钮所清理的缓存列表 clearNames: sysCache,corpCache,userCache,roleCache,fileUploadCache,msgPcPoolCache,cmsCache,bpmFormCache # 用户缓存 @@ -102,6 +106,11 @@ spring: main: bannerMode: "off" + # 热部署工具 + devtools: + restart: + additional-exclude: "**/*.log" + # 日志配置(fatal、error、warn、info、debug) logging: config: classpath:config/logback-spring.xml @@ -130,13 +139,19 @@ mybatis: # 批量插入和更新的分批默认大小(防止库一次性接受不了太大的sql语句) defaultBatchSize: 500 - # 执行逻辑删除的时候,同时修改主键字段值,方便再次使用这个主键值 v5.4.0+ + # 执行逻辑删除的时候,同时修改主键字段值,方便再次使用这个主键值(调用 entity.sqlMap().markIdDelete() 时生效) v5.4.0+ # 案例分析(角色管理场景): # 1.如果是逻辑删除数据,并非物理删除,所以删除了角色 abc 再次新增时,会提示 abc 编号已存在 # 2.使用方法为:在 super.delete(entity); 前调用:entity.sqlMap().markIdDelete(); # 3.一般在手动填写主键业务中使用,启用后将会在删除后,修改 ID 值数据,例如:abc__del_随机串 markIdDeleteFlag: __del_ + # 允许 @Table orderBy 排序 设置为空,否则默认使用 主键 排序 v4.5.0 v5.1.0 + allowOrderEmpty: true + + # 排序字段 SQL 过滤,该参数仅对 sqlMap.getOrder().setOrderBy 内部调用方法有效 page.setOrderBy 和 entity.setOrderBy 必须经过过滤 v5.12.0 + orderBySqlFilter: false + # Mapper文件刷新线程 mapper: refresh: @@ -495,7 +510,7 @@ shiro: #allowReferers: http://127.0.0.1,http://localhost # 允许重定向的地址,不设置为全部允许,设置this只允许本项目内部跳转,多个用逗号隔开,例如:this,http://*.jeesite.com - #allowRedirects: ~ + allowRedirects: this # 是否在登录后生成新的Session(默认false) isGenerateNewSessionAfterLogin: false @@ -537,6 +552,8 @@ shiro: /oauth2/authorize = user /druid/** = perms[sys:state:druid] /bpm/modeler/** = perms[bpm:modeler] + /ureport/designer/** = perms[ureport] + /ureport/datasource/** = perms[ureport] ${adminPath}/login-cas = cas ${adminPath}/login-ldap = ldap ${adminPath}/login = authc @@ -713,10 +730,6 @@ web: id: '[a-zA-Z0-9_\-/#\u4e00-\u9fa5]{0,64}' user.loginCode: '[a-zA-Z0-9_\u4e00-\u9fa5]{4,20}' - # 默认的日期格式(JsonMapper) - json: - defaultDateFormat: yyyy-MM-dd HH:mm:ss - # 默认不启用(为兼用旧版保留,建议使用 CORS) jsonp: enabled: false @@ -755,7 +768,7 @@ file: # 设置允许上传的文件后缀(全局设置) imageAllowSuffixes: .gif,.bmp,.jpeg,.jpg,.ico,.png,.tif,.tiff,.webp, mediaAllowSuffixes: .flv,.swf,.mkv,webm,.mid,.mov,.mp3,.mp4,.m4v,.mpc,.mpeg,.mpg,.swf,.wav,.wma,.wmv,.avi,.rm,.rmi,.rmvb,.aiff,.asf,.ogg,.ogv, - fileAllowSuffixes: .doc,.docx,.rtf,.xls,.xlsx,.csv,.ppt,.pptx,.pdf,.vsd,.txt,.md,.xml,.rar,.zip,.7z,.tar,.tgz,.jar,.gz,.gzip,.bz2,.cab,.iso,.ipa,.apk, + fileAllowSuffixes: .doc,.docx,.rtf,.xls,.xlsx,.csv,.ppt,.pptx,.pdf,.ofd,.vsd,.txt,.md,.xml,.rar,.zip,.7z,.tar,.tgz,.jar,.gz,.gzip,.bz2,.cab,.iso,.ipa,.apk, # 允许上传的文件内容类型(图片、word、excel、ppt)防止修改后缀恶意上传文件(默认不启用验证) # allowContentTypes: image/jpeg,image/gif,image/bmp,image/png,image/x-png, diff --git a/modules/core/src/main/resources/config/logger-core.xml b/modules/core/src/main/resources/config/logger-core.xml index 457b1c4454b5db0af9132a6b30f561e356b655e5..1ccb043ea8b6e28e0af6d397d0f051e045c5684b 100644 --- a/modules/core/src/main/resources/config/logger-core.xml +++ b/modules/core/src/main/resources/config/logger-core.xml @@ -21,6 +21,7 @@ + diff --git a/modules/core/src/main/resources/i18n/core/sys/i18n_en.properties b/modules/core/src/main/resources/i18n/core/sys/i18n_en.properties index 7447d100b15ffceb3ca11d21e71b4a872b9f777a..b0d74d37f23a425eed1257065eccdd12712bc6d9 100644 --- a/modules/core/src/main/resources/i18n/core/sys/i18n_en.properties +++ b/modules/core/src/main/resources/i18n/core/sys/i18n_en.properties @@ -33,7 +33,7 @@ 公司管理=Company manage 岗位管理=Position manage -权限管理=Rights Manage +权限管理=Rights manage 角色管理=Role manage 二级管理员=Secondary admin 系统管理员=System admin @@ -56,6 +56,7 @@ 在线用户=Online user 在线文档=Online doc +数据管理=Data manage 研发工具=Develop Tools 用户选择=User select @@ -547,6 +548,7 @@ 主类全名=Main class name 模块描述=Module description 当前版本=Current version +升级信息=Upgrade info 版本=version 未知=Unknown 未安装=Uninstalled diff --git a/modules/core/src/main/resources/i18n/core/sys/i18n_ja_JP.properties b/modules/core/src/main/resources/i18n/core/sys/i18n_ja_JP.properties index b180fb0cc90aeec5028450015f893c179d7eafdd..f35ad3b5449e2dfa99ae7531cca4fc924a954cc1 100644 --- a/modules/core/src/main/resources/i18n/core/sys/i18n_ja_JP.properties +++ b/modules/core/src/main/resources/i18n/core/sys/i18n_ja_JP.properties @@ -56,6 +56,7 @@ 在线用户=オンラインユーザー 在线文档=オンラインドキュメント +数据管理=データかんり 研发工具=開発ツール 用户选择=ユーザー選択 @@ -460,6 +461,7 @@ 主类全名=主類のフルネーム 模块描述=モジュール記述 当前版本=現行版 +升级信息=アップグレードします情報 版本=バージョン 未知=未知 未安装=未実装 diff --git a/modules/core/src/main/resources/mappings/modules/sys/EmpUserDao.xml b/modules/core/src/main/resources/mappings/modules/sys/EmpUserDao.xml index bad1c37f4138dd7665f7f2c12788aeb56eae1a74..eedbe2e56dc26234ce3881c986d3a58419a3441d 100644 --- a/modules/core/src/main/resources/mappings/modules/sys/EmpUserDao.xml +++ b/modules/core/src/main/resources/mappings/modules/sys/EmpUserDao.xml @@ -13,7 +13,7 @@