Compare commits

..

168 Commits

Author SHA1 Message Date
thinkgem
3c2fa62f8b update README.md 2025-10-27 17:50:59 +08:00
thinkgem
ec298a0c53 update Dockerfile 2025-10-27 12:05:52 +08:00
thinkgem
f18c00e2e1 5.14.0 2025-10-27 10:02:41 +08:00
thinkgem
037918e8e5 优化 Upload 文件上传组件一些参数,默认从后台获取 2025-10-27 09:15:22 +08:00
thinkgem
854591cc2e 默认文档上传类型增加.dmg格式 2025-10-27 09:14:57 +08:00
thinkgem
c9985c97d2 logback 1.5.19 2025-10-23 11:39:31 +08:00
thinkgem
a41a918b84 新增一个参数的调度任务测试方法 2025-10-23 11:39:14 +08:00
thinkgem
a167331477 将root/pom移动到根目录下 2025-10-22 14:06:31 +08:00
thinkgem
5594969e9a 新增Excel导入导出实体单元测试类 2025-10-22 10:49:09 +08:00
thinkgem
d30435ae8e 修正ExcelField声明在get方法上时提示wrong number of arguments 2025-10-22 10:47:47 +08:00
thinkgem
10d1db9ade 任务调度目标串,增加 JobContext 对象接收,可获取 JobExecutionContext、任务中断状态、任务中断回调等;当当暂停或运行一次的时候,会发起中断任务通知; 2025-10-21 12:51:50 +08:00
thinkgem
9ae8f34160 代码优化 2025-10-21 12:46:49 +08:00
thinkgem
7369be99cb update README.md 2025-10-20 16:08:30 +08:00
thinkgem
3b0cc66347 新增 AI MCP 服务端和客户端调用,展示远程工具调用示例 2025-10-19 19:19:18 +08:00
thinkgem
c7cd1d20cb 优化 Application 启动完成输出的提示信息 2025-10-19 13:24:51 +08:00
thinkgem
49d67f7b81 优化 Application 启动完成输出的提示信息 2025-10-19 13:24:42 +08:00
thinkgem
fd8d036cb1 拆分 jeesite-ai-tools 模块,工具调用保持会话控制权限,如当前用户只能查询有权限的数据 2025-10-19 13:21:33 +08:00
thinkgem
6dadf4c774 支持 新版本 ollama 的深度思考输出 2025-10-19 13:21:00 +08:00
thinkgem
afcce5db7b 重命名 tool-calls to tools.enabled 2025-10-19 13:20:27 +08:00
thinkgem
5dfb735186 重构 jeesite-ai 代码目录,新增 parent-ai,重命名 cms-ai 为 ai-cms 2025-10-19 13:18:33 +08:00
thinkgem
ac4d05cda7 update productName 2025-10-18 00:21:12 +08:00
thinkgem
3d8237ce01 合并 PostConstruct 2025-10-16 20:20:16 +08:00
thinkgem
9a7f6d9137 update README.md 2025-10-16 16:34:52 +08:00
thinkgem
6a6e38eba7 update logger-core.xml 2025-10-16 16:34:45 +08:00
thinkgem
e59c3510b9 remove config mvc.pathmatch.matching-strategy 2025-10-16 13:38:29 +08:00
thinkgem
fb093bac08 完善模型返回异常输出消息,将消息返回给客户端(仅用于调试) 2025-10-16 11:01:33 +08:00
thinkgem
317294ed64 聊天模型和嵌入模型使用不同的供应商 2025-10-16 10:56:29 +08:00
thinkgem
45482a9ae7 spring ai 1.0.3 2025-10-16 10:54:52 +08:00
thinkgem
666bb2e763 删除或保留 ueditor.json 配置文件,删除后将使用默认 ueditor-core.json 2025-10-16 10:53:20 +08:00
thinkgem
5d98e0ff8c 默认加载 mybatis-default.xml,可通过 mybatis.configLocation 参数指定 mybatis-config.xml 文件 2025-10-16 10:50:20 +08:00
thinkgem
e4d4b7057d application-prod.yml 增加 mybatis.mapper.refresh.enabled: false 2025-10-16 10:40:02 +08:00
thinkgem
4c0e6f4e50 update application.yml 2025-10-16 10:33:36 +08:00
thinkgem
c3d170b475 给迷你服务去掉一些依赖,缩减打包大小 2025-10-15 21:37:02 +08:00
thinkgem
0415d7aef8 update pom.xml 2025-10-15 19:53:01 +08:00
thinkgem
9464599e3a 增加 logger-default-cloud.xml 默认配置,可自定义覆盖默认参数 2025-10-15 16:29:46 +08:00
thinkgem
5c4edd0bfd 增加 beetl-default.properties 默认配置,可自定义覆盖默认参数 2025-10-15 16:24:24 +08:00
thinkgem
9cd638ba34 update logging config 2025-10-15 16:16:30 +08:00
thinkgem
5d79f9eeff update logger-core.xml 2025-10-15 12:25:03 +08:00
thinkgem
bac893fb78 增加默认文件 logger-default.xml、mybatis-default.xml 方便 junit、mini服务引用 2025-10-15 11:30:57 +08:00
thinkgem
8efa457299 update StringUtils.java 2025-10-15 11:26:06 +08:00
thinkgem
5f7356bb26 update logback config 2025-10-15 11:23:37 +08:00
thinkgem
b062493503 update robust 2025-10-13 15:28:15 +08:00
thinkgem
976b7a4e83 update version 2025-10-13 12:16:30 +08:00
thinkgem
5426668cdb 更改为构造注入并完善方法注释 2025-10-12 20:34:18 +08:00
thinkgem
67b3649c15 更改为构造注入并完善方法注释 2025-10-12 14:44:21 +08:00
thinkgem
10820bf441 登录成功和退出成功接口,增加返回值 2025-10-12 14:07:28 +08:00
thinkgem
d1f429e93b 错误提示页面,增加 刷新页面 按钮 2025-10-11 21:56:15 +08:00
thinkgem
c5741a488d 小窗口下,侧边栏未完全展开问题 2025-10-11 21:55:45 +08:00
thinkgem
2c4b339983 优化体验,当关闭多地登录,且被挤下时,给予用户提示,而不是直接跳转登录页 2025-10-11 21:55:05 +08:00
thinkgem
0019e72f39 优化登录逻辑,性能提升 2025-10-11 21:52:58 +08:00
thinkgem
9608ff2962 系统缓存 sysCache 延长存活时间 2025-10-11 08:43:10 +08:00
thinkgem
3a40652fbb 登录设备合法性验证(根据业务需要自行添加) 2025-10-10 21:03:38 +08:00
thinkgem
75f51fa221 代码优化 2025-10-10 20:56:06 +08:00
thinkgem
91bfc7a7c9 登录优化 2025-10-10 20:55:08 +08:00
thinkgem
d9b554547b 登录 subject.isPermitted("user") 调用2次,优化为1次 2025-10-10 20:52:22 +08:00
thinkgem
d475e154a6 StringUtils 更新一些标为弃用的方法 2025-10-10 19:25:41 +08:00
thinkgem
1199d947ff CMS 给 Category 栏目添加缓存 2025-10-10 16:45:18 +08:00
thinkgem
d8787ca3e9 update clearCache 2025-10-10 16:43:50 +08:00
thinkgem
e4388c0603 update logger 2025-10-10 16:43:31 +08:00
thinkgem
273e478ee2 CMS 当栏目为显示第一篇文章的时候,点击保存,不关闭窗口 2025-10-10 16:41:02 +08:00
thinkgem
3dd0bdcd62 缓存工具 CacheUtils 增加 computeIfAbsent 简化方法 2025-10-10 11:12:59 +08:00
thinkgem
92d4161294 code optimization 2025-10-09 23:35:12 +08:00
thinkgem
9b85753948 StringUtils 更新一些标为弃用的方法 2025-10-09 22:56:43 +08:00
thinkgem
d8be91f8eb SpringUtils 增加 getBeanIfAvailable 方法,如果可用则返回Bean,不抛出异常 2025-10-09 22:52:18 +08:00
thinkgem
490ef7c69d update logger-core.xml 2025-10-09 17:04:38 +08:00
thinkgem
2367e70e5d lazy-initialization: true 2025-10-09 16:53:54 +08:00
thinkgem
b9a87a883a 优化 FeignClient 生成的 Bean 名称,直接使用 类名 小写开头 2025-09-30 20:04:36 +08:00
thinkgem
9dbcb1adf1 springdoc 升级为 2.8.13;web.swagger.enabled 替换为 springdoc.api-docs.enabled 和 springdoc.swagger-ui.enabled 2025-09-30 16:25:29 +08:00
thinkgem
893eab3ad8 add fury-extensions 2025-09-30 16:16:00 +08:00
thinkgem
0348b5a02d 新增 分组聚合查询 sqlMap.getGroup().setGroupBy(分组字段).setHaving(分组条件);优化 字段权限排除方法 setExcludeAttrNames,支持设置 * 排除所有列; 2025-09-29 11:39:17 +08:00
thinkgem
62f45c5f45 新增 toOrderSql() 和 toGroupSql() 方法,当为空的时候不输出 ORDER BY 和 GROUP BY 关键字 2025-09-29 11:37:35 +08:00
thinkgem
95d2a62443 commons-io 3.18、commons-io 2.20.0、twelvemonkeys 3.12.0、fury 0.10.3、bcprov 1.80、snakeyaml 2.4、logstash-logback 8.1、mybatis-spring 3.0.5、swagger3 2.2.36 2025-09-27 19:55:05 +08:00
thinkgem
ee9bea2473 next version 2025-09-27 19:53:13 +08:00
thinkgem
956507128b Merge branch 'refs/heads/v5.springboot3' into v5.springboot3.temp 2025-09-27 19:50:13 +08:00
thinkgem
34cc937dae update README.md 2025-09-27 19:47:59 +08:00
thinkgem
5130783373 初始化isShow为空问题修复 2025-09-27 19:38:54 +08:00
thinkgem
60bf5c2ce1 spring boot 3.5.6 2025-09-26 19:32:55 +08:00
thinkgem
128a327024 update README.md 2025-09-24 23:36:01 +08:00
thinkgem
3995397ef9 幂等注解 Idempotent 增加 sessionId(是否为会话级别的幂等验证)和 cacheName(可以自定义缓存名)参数 2025-09-24 22:06:55 +08:00
thinkgem
5cc48c44e5 优化 ThreadUtils.sleep 增加 currentThread().interrupt(); 2025-09-24 22:02:49 +08:00
thinkgem
e2b7db9668 新增 锁管理器,支持本地锁和分布式锁,当开启集群时,自动使用分布式锁 2025-09-24 22:01:36 +08:00
thinkgem
4741e75975 当指定语言和时区的时候再进行更改默认 2025-09-24 13:33:19 +08:00
thinkgem
3d0455192c 5.13.1 2025-09-22 21:47:04 +08:00
thinkgem
1c51398e60 新增 -Dspring.config.additional-location 方式读取配置文件 2025-09-22 09:54:12 +08:00
thinkgem
20ff2cebfe 启动脚本 startup.bat(sh) 支持接受参数 2025-09-22 09:35:36 +08:00
thinkgem
d714d70b69 更新 docker 部署 2025-09-20 19:19:17 +08:00
thinkgem
23c65b6e8e update docker plugin 2025-09-20 19:11:22 +08:00
thinkgem
bfe0f7536d 移除用不到的文件h5fix.min.js 2025-09-18 21:57:23 +08:00
thinkgem
cb4640cbb7 驼峰命名法工具优化,使用下划线开头的时候忽略它 2025-09-18 21:47:28 +08:00
thinkgem
23ea3e5020 update yml 2025-09-18 13:40:35 +08:00
thinkgem
63a6d5c762 5.13.1 2025-09-15 08:48:32 +08:00
thinkgem
c06f2e4ff1 解决 ExcelImport 数值类型为负数的时候小数问题 2025-09-13 16:01:37 +08:00
thinkgem
69011d80a4 新增 QueryType.BETWEEN、QueryType.NOT_BETWEEN 查询类型 2025-09-12 09:50:45 +08:00
thinkgem
4a5a279cd5 update 2025-09-10 23:22:59 +08:00
thinkgem
8c749eae52 IpAddrFilter重命名为GlobalFilter全局过滤器,并将MDC移动到进来 2025-09-08 11:22:05 +08:00
thinkgem
104e59349d 候还是后,稍后:说明后面还需有动作,需再进行尝试;稍候:说明只需等待就可以了 2025-09-05 10:47:33 +08:00
thinkgem
9a40ab254d poi 5.4.1 2025-09-03 10:19:01 +08:00
thinkgem
d409e73324 update upbw 2025-09-02 10:08:21 +08:00
thinkgem
fcf9229849 没有生成导入功能时,缺少 ref 导入 2025-09-02 10:07:54 +08:00
thinkgem
a4ff87a6c1 update application.yml 2025-08-29 20:52:13 +08:00
thinkgem
6267e3c74e 更新名称 生成模块代码(Maven) 2025-08-29 20:51:49 +08:00
thinkgem
26563cd80a TestData 子表增加图片上传组件 2025-08-27 23:51:49 +08:00
thinkgem
c74d5823dc 代码优化 2025-08-27 23:51:33 +08:00
thinkgem
4ff2e23780 @Idempotent 幂等注解,支持 key 指定多个,使用逗号分隔 2025-08-26 15:45:50 +08:00
thinkgem
1e156116ee 更新vue实例菜单 2025-08-25 15:08:46 +08:00
thinkgem
cd07b1a3c8 改进单元测试类配置 2025-08-25 10:37:59 +08:00
thinkgem
c47be09f51 update 2025-08-22 19:14:46 +08:00
thinkgem
ed8e08da05 代码优化 2025-08-22 18:41:03 +08:00
thinkgem
2ab09e38e5 Merge branch 'v5.springboot3' into v5.springboot3.temp 2025-08-22 18:39:15 +08:00
thinkgem
73c52bbd1f update README.md 2025-08-19 00:27:09 +08:00
thinkgem
63773c97a5 完善xss过滤表达式,避免出现data: 2025-08-18 23:06:15 +08:00
thinkgem
4a881825e9 Merge branch 'v5.springboot3' into v5.springboot3.temp 2025-08-18 17:14:14 +08:00
thinkgem
6b75fe67af 5.13.0 2025-08-18 17:10:05 +08:00
thinkgem
35b0411198 5.13.0 2025-08-18 11:45:25 +08:00
thinkgem
65aa023c92 优化 toArray 预设大小的数组替换为零长度数组 2025-08-14 21:52:00 +08:00
thinkgem
7f77715cc0 update logger 2025-08-14 21:13:23 +08:00
thinkgem
c146e0e101 取消 sys:stste 权限 2025-08-14 12:53:01 +08:00
thinkgem
11478921a6 拼写更正 Templete to Template 2025-08-14 12:47:22 +08:00
thinkgem
c5bd0bc597 增加子表类型生成;表格组件和表单组件,完善泛型支持; 2025-08-13 21:48:55 +08:00
thinkgem
b797bb99d0 代码优化 2025-08-08 17:44:31 +08:00
thinkgem
fc37b649b1 form:fileupload 组件 preview 参数优化,默认使用 file.preview 配置文件参数,方便全局设置。 2025-08-07 18:14:57 +08:00
thinkgem
fd3a334e32 更新jdbc驱动版本 2025-08-06 16:04:03 +08:00
thinkgem
f48a2b2aa8 spring ai 1.0.0 release 2025-08-04 18:20:41 +08:00
thinkgem
dd7a1bd539 spring 3.5.4 2025-08-04 17:52:28 +08:00
thinkgem
0f2c22dbd9 update productVersion 2025-08-04 17:50:14 +08:00
thinkgem
b6fff119e0 优化提示信息 2025-08-01 10:51:25 +08:00
thinkgem
1d3b3a5f9d spring boot 3.4.7 shiro 2.0.5 druid 1.2.27 2025-07-31 12:07:46 +08:00
thinkgem
7e6426adea next version 2025-07-29 09:52:45 +08:00
thinkgem
daae9f05a6 vue生成模版增加list查询条件初始化 2025-07-28 12:02:59 +08:00
thinkgem
615448c2ce 优化 spring boot 3.4 jta 事务 2025-07-28 12:01:43 +08:00
thinkgem
5bf6c026f0 模版生成增加application.assistant.yml文件 2025-07-21 18:53:40 +08:00
thinkgem
c887260f40 update README.md 2025-07-10 10:05:33 +08:00
thinkgem
bc05da3bd2 完善beetl的xss格式化,默认使用非html文本处理 2025-07-09 13:38:25 +08:00
thinkgem
3585737d21 完善xss正则表达式,处理on前面是/的问题;完善beetl的xss格式化,默认使用非html文本处理 2025-07-09 13:03:17 +08:00
thinkgem
da999aa4e6 Recover 修改个人信息后跳转目标页面不对问题 2025-07-09 11:03:26 +08:00
thinkgem
8d62eb25b2 5.12.1 2025-07-08 00:30:49 +08:00
thinkgem
3d06b8d009 调整默认的允许重定向地址,只允许项目内部跳转 2025-07-08 00:22:45 +08:00
thinkgem
1c5e49b081 禁用UEditor图片抓取器,对CMS模版查看的地址进行限定 2025-07-08 00:22:44 +08:00
thinkgem
b522b3ff1e 5.12.1 2025-07-07 18:29:36 +08:00
thinkgem
743dcc6f4c 新增 SHA-256 工具,使用 ShaUtils 替代 Sha1Utils 2025-07-07 08:55:36 +08:00
thinkgem
7de53c00ef 代码生成模版,优化 useI18n 参数 2025-07-05 16:31:07 +08:00
thinkgem
c0f02b4599 左树右表,切换树节点的时候定向到列表第一页 2025-07-04 15:01:33 +08:00
thinkgem
278f67fe72 代码生成使用简化请求路径时,api文件名中不含模块名。 2025-07-03 16:49:44 +08:00
thinkgem
d41545f2b0 update yml 2025-07-03 14:01:17 +08:00
thinkgem
bbbace28ea 默认使用oracle ojdbc8驱动 2025-07-03 13:47:34 +08:00
thinkgem
27447c2479 update 2025-07-01 12:34:13 +08:00
thinkgem
3107ab8f3a 代码生成模版支持多级模块设置,如模块管理:ai-core;功能生成模块名:ai.core;新增几个模版参数:moduleMinus、modulePath、subModuleNameDot、subModulePath 2025-06-30 11:19:48 +08:00
thinkgem
63dd5c161d 修正beetl修改密码后再登录系统,没有跳转到首页问题 2025-06-30 11:19:38 +08:00
thinkgem
511d76e03f 生成模版优化 2025-06-27 17:18:04 +08:00
thinkgem
a683a9ed5c vue主键为输入框的时候,不验证主键重复问题 2025-06-27 17:13:56 +08:00
thinkgem
e64b1d3b49 update 2025-06-27 14:53:51 +08:00
thinkgem
3b7ef10492 ztree 当没有title数据的时候,使用name作为title显示 2025-06-27 09:22:38 +08:00
thinkgem
b17c58e792 优化 默认系统提示词和模板 2025-06-23 14:21:25 +08:00
thinkgem
6faa39ebea 代码优化 2025-06-23 13:59:02 +08:00
thinkgem
32f4d7e0ae 增加 spring.cache.globalKeyNames 参数,指定哪些为全局缓存,节省资源 2025-06-23 09:37:24 +08:00
thinkgem
fbf25775b3 update README.md 2025-06-23 08:28:13 +08:00
thinkgem
0732be618e 修正请求对象被回收提示 2025-06-21 19:29:34 +08:00
thinkgem
3e16c27442 update ai README.md 2025-06-20 19:16:21 +08:00
thinkgem
2dea278a45 Merge branch 'v5.springboot3' into v5.springboot3.temp 2025-06-19 15:39:05 +08:00
thinkgem
02adbd3af3 update 2025-06-19 15:04:00 +08:00
thinkgem
f1281b73cc 新增vue前端模块代码生成模板 2025-06-18 11:03:01 +08:00
thinkgem
5fc096ab0a 通过yml开关控制使用哪些模型和向量库 2025-06-18 10:59:55 +08:00
thinkgem
28293383dd 代码优化 2025-06-10 11:47:00 +08:00
thinkgem
7459bc220c lint type 2025-06-08 10:42:18 +08:00
thinkgem
a81297c2be 代码生成模板默认替换为 Monorepo 2025-06-08 10:38:42 +08:00
thinkgem
cb5957ee61 add AiChatServiceTest 2025-06-07 16:11:53 +08:00
thinkgem
b55467312f 增加ureport接口权限配置 2025-06-06 15:03:21 +08:00
thinkgem
a567c82ef2 增加JsonFormat条件防止出现类型转换问题 2025-06-05 13:04:51 +08:00
449 changed files with 5859 additions and 4327 deletions

156
README.md
View File

@@ -4,7 +4,7 @@
</p>
<h3 align="center" style="margin:30px 0 30px;font-weight:bold;font-size:30px;">快速开发平台 - Spring Boot 3</h3>
<p align="center">
<a href="https://spring.io/projects/spring-boot" target="__blank"><img alt="SpringBoot-V2.7/3.4" src="https://img.shields.io/badge/SpringBoot-V2.7/3.4-blue.svg"></a>
<a href="https://spring.io/projects/spring-boot" target="__blank"><img alt="SpringBoot-V3.5 or 2.7" src="https://img.shields.io/badge/SpringBoot-V3.5 or 2.7-blue.svg"></a>
<a href="https://v3.cn.vuejs.org/" target="__blank"><img alt="TypeScript-Vue3" src="https://img.shields.io/badge/TypeScript-Vue3-green.svg"></a>
<a href="https://jeesite.com/docs/upgrade/" target="__blank"><img alt="JeeSite-V5.x" src="https://img.shields.io/badge/JeeSite-V5.x-success.svg"></a>
<a href="https://gitee.com/thinkgem/jeesite5" target="__blank"><img alt="star" src="https://gitee.com/thinkgem/jeesite5/badge/star.svg?theme=dark"></a>
@@ -51,7 +51,7 @@
[GitHub](https://github.com/thinkgem/jeesite-vue)
* 源码合集仓库地址:
[GVP](https://gitee.com/thinkgem/jeesite/tree/v5.springboot3)、
[G-Star](https://gitcode.com/thinkgem/jeesite/overview?ref=v5.springboot3)、
[G-Star](https://gitcode.com/thinkgem/jeesite/tree/v5.springboot3)、
[GitHub](https://github.com/thinkgem/jeesite/tree/v5.springboot3)
## 平台介绍
@@ -70,9 +70,11 @@
* 2021 年终发布 Vue3 的前后分离版本,使得 JeeSite 拥有同一个后台服务 Web 来支撑分离版和全栈版两套前端技术栈。
* 对接常见 AI 大模型(OpenAPI、Ollama、DeepSeek等),支持检索增强生成 RAG 技术,实现企业知识库智能对话。
* 对接 OpenAPI、Ollama、DeepSeek 等热门 AI 大模型,凭借检索增强生成 RAG 技术,企业知识库打造专属智能对话。
* 支持国产化软硬件环境,如国产芯片、操作系统、数据库、中间件、国密算法等
* 提供大模型 Tool 本地工具调用及 MCP 服务端和客户端工具调用,助力大模型与您的业务深度融合,实现高效交互
* 支持国产化软件和硬件环境,如国产芯片、操作系统、数据库、中间件、国密算法等。
## 核心优势
@@ -90,48 +92,35 @@
* 至今 JeeSite 平台架构已经非常稳定,我们持续升级,并不失架构的先进性。
* JeeSite 精益求精,用心打磨每一个细节,界面 UI 操作便捷,体验性好。
* JeeSite 是一个专业的平台,是一个可以让您使用放心的平台。
* JeeSite 是一个专业的平台,是一个可以让您,用着省心的平台。
* 社区版基于 Apache License 2.0 开源协议,永久免费使用。
### 架构特点及安全方面的优势:<https://jeesite.com/docs/feature/>
## 技术选型
* 主框架Spring Boot 3.4、Spring Framework 6、Apache Shiro 2、J2Cache
* 主框架Spring Boot 3.5、Spring Framework 6、Apache Shiro 2、J2Cache
* 持久层Apache MyBatis 3.5、Hibernate Validator 8、Alibaba Druid 1.2
* 分离版Node.js、TypeScript、Vue3、Vite、Ant Design Vue、Vue Vben Admin
* 经典版Beetl 3.10HTML、jQuery 3.7、Bootstrap 3.3、AdminLTE 2.4
* 分离版:支持所有现代浏览器,如:谷歌 Chrome 86+、微软 Edge、火狐、国产浏览器 等
* 分离版:支持所有现代浏览器,如:谷歌 Chrome 87+、微软 Edge、火狐、国产浏览器 等
* 经典版:支持 IE10 和以上版本,以及其他所有现代浏览器,如:谷歌、火狐、国产浏览器 等
* 工作流引擎Flowable 7.1、符合 BPMN 规范、在线流程设计器、中国式流程、退回、撤回、自由流
* 技术选型(详细)已支持数据库:<http://jeesite.com/docs/technology/>
* JeeSite Vue 前后分离版:<https://gitee.com/thinkgem/jeesite-vue>
* Spring Boot 3.x 版本:<https://gitee.com/thinkgem/jeesite5/tree/v5.springboot3/>
* Spring Boot 2.x 版本:<https://gitee.com/thinkgem/jeesite5/tree/v5.springboot2/>
* Spring Boot 3.x 版本:<https://gitee.com/thinkgem/jeesite5/tree/v5.springboot3>
* Spring Boot 2.x 版本:<https://gitee.com/thinkgem/jeesite5/tree/v5.springboot2>
## 更多介绍
* 架构特点:<https://jeesite.com/docs/feature/>
* 内置功能:<https://jeesite.com/docs/function/>
* 目录结构:<https://jeesite.com/docs/catalog/>
* 架构特点<https://jeesite.com/docs/feature/>
* 参数配置<https://jeesite.com/docs/config/>
* 开发规范:<https://jeesite.com/docs/standard/>
* 数表设计:<https://jeesite.com/docs/treetable/>
* 代码生成:<https://jeesite.com/docs/code-gen/>
## 生态系统
* AI + RAG + CMS 人工智能助手:<https://jeesite.com/docs/cms-ai>
* 分布式微服务Spring Cloud<https://gitee.com/thinkgem/jeesite-cloud>
* Flowable业务流程引擎BPM<http://jeesite.com/docs/bpm/>
* 多站点内容管理模块CMS<https://jeesite.com/docs/cms/>
* 手机端移动端:<https://gitee.com/thinkgem/jeesite-uniapp>
* PC客户端程序<https://gitee.com/thinkgem/jeesite-client>
* Vue3分离版本<https://gitee.com/thinkgem/jeesite-vue>
* JeeSite统一认证<https://jeesite.com/docs/oauth2-server>
* JFlow工作流引擎<https://gitee.com/thinkgem/jeesite-jflow>
* Mybatis-Plus: <https://gitee.com/thinkgem/jeesite-mybatisplus>
* Magic接口快速开发<https://gitee.com/thinkgem/jeesite-magic-api>
* 内外网中间件:<https://my.oschina.net/thinkgem/blog/4624519>
## 快速体验
### 在线演示
@@ -139,9 +128,20 @@
1. 分离版地址:<https://vue.jeesite.com>
2. 经典版地址:<https://demo.jeesite.com>
### 快速运行
1. 免安装数据库,使用内嵌 H2 DB、包含 Vue 和 全栈双版本
2. 环境准备:`JDK 17 或更高版本``Maven 3.8+`、无需准备数据库
3. 下载源码:<https://gitee.com/thinkgem/jeesite5/repository/archive/v5.springboot3.zip> 并解压
4. 执行脚本:`/web-fast/bin/run-tomcat.bat(sh)` 启动服务即可(无需手动建库,自动初始化数据库)
5. Vue分离版本地址<http://127.0.0.1:8980/vue/login>
6. 全栈版本地址:<http://127.0.0.1:8980/a/login>
7. 初始登录账号超级管理员system 密码admin
8. 部署常见问题:<https://jeesite.com/docs/faq/>
### 本地运行
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. 下载源码:<https://gitee.com/thinkgem/jeesite5/repository/archive/v5.springboot3.zip> 并解压
3. 打开文件:`/web/src/main/resources/config/application.yml` 配置JDBC连接建立一个新库
4. 执行脚本:`/web/bin/init-data.bat(sh)` 初始化数据库(自动往新库里创建表和初始数据)
@@ -149,99 +149,89 @@
6. 浏览器访问:<http://127.0.0.1:8980/js> 账号 system 密码 admin
7. 部署常见问题:<https://jeesite.com/docs/faq/>
8. 分离端安装:<https://jeesite.com/docs/vue-install-deploy/>
### 快速运行
1. 环境准备:`JDK 17+``Maven 3.8+`、无需准备数据库(使用内嵌 H2 DB、Vue资源包
2. 下载源码:<https://gitee.com/thinkgem/jeesite5/repository/archive/v5.springboot3.zip> 并解压
3. 执行脚本:`/web-fast/bin/run-tomcat.bat(sh)` 启动服务即可(无需手动建库,自动初始化数据库)
4. Vue分离版本地址<http://127.0.0.1:8980/vue/login>
5. 全栈版本地址:<http://127.0.0.1:8980/a/login>
6. 初始登录账号超级管理员system 密码admin
7. 部署常见问题:<https://jeesite.com/docs/faq/>
9. 分离端常见问题:<https://jeesite.com/docs/vue-faq/>
### 容器运行
- 拉取 Docker 镜像JeeSite版本不是最新
```sh
docker pull thinkgem/jeesite-web
docker pull crpi-u3zm0t8trv68xpyx.cn-qingdao.personal.cr.aliyuncs.com/thinkgem/jeesite:latest
```
- 启动脚本
- 启动镜像
```sh
docker run --name jeesite-web -p 8980:8980 -d --restart unless-stopped \
-v ~/:/data thinkgem/jeesite-web && docker logs -f jeesite-web
docker run --name js5 -p 8980:8980 -d crpi-u3zm0t8trv68xpyx.cn-qingdao.personal.cr.aliyuncs.com/thinkgem/jeesite:latest
```
- 浏览器访问:<http://127.0.0.1:8980/js/> 账号 system 密码 admin
- 分离端安装:<https://jeesite.com/docs/vue-install-deploy/>
- 查看日志:
```sh
docker logs -f js5
```
- 浏览器访问:<http://127.0.0.1:8980/js> 账号 system 密码 admin
### 开发环境
1. 部署运行文档:<https://jeesite.com/docs/install-deploy/>
2. 部署常见问题:<https://jeesite.com/docs/faq/>
3. 分离端运行文档:<https://jeesite.com/docs/vue-install-deploy/>
4. 分离端常见问题:<https://jeesite.com/docs/vue-jeesite-vue/>
4. 分离端常见问题:<https://jeesite.com/docs/vue-faq/>
## 技术文章
## 学习文档
* 菜单和按钮权限<https://jeesite.com/docs/permi-shiro/>
* 强大的数据权限:<https://jeesite.com/docs/service-datascope/#数据权限>
* 表结构数据字典<https://jeesite.com/docs/code-gen/#表结构数据字典>
* 持久层设计<https://jeesite.com/docs/dao-mybatis/>
* 后端工具<https://jeesite.com/docs/sys-utils/>
* 库表生成、代码生成<https://jeesite.com/docs/code-gen/>
* 菜单权限、按钮权限:<https://jeesite.com/docs/permi-shiro/>
* 数据权限、库事务<https://jeesite.com/docs/service-datascope/#数据权限>
* 表结构、数据字典<https://jeesite.com/docs/code-gen/#表结构数据字典>
* 持久层框架、SQL<https://jeesite.com/docs/dao-mybatis/>
* 后端常用工具:<https://jeesite.com/docs/sys-utils/>
**分离版**
* 版本介绍:<https://jeesite.com/docs/jeesite-vue/>
* 源码解析:<https://jeesite.com/docs/vue-crud-view/>
* 表单组件:<https://jeesite.com/docs/vue-basic-form/>
* 表格组件:<https://jeesite.com/docs/vue-basic-table/>
* 参数配置:<https://jeesite.com/docs/vue-settings/>
* 常用组件:<https://jeesite.com/docs/vue-comp/>
* 前端权限:<https://jeesite.com/docs/vue-auth/>
* 图标组件:<https://jeesite.com/docs/vue-icon/>
* 前端样式库:<https://jeesite.com/docs/vue-style/>
* 多语言国际化:<https://jeesite.com/docs/vue-i18n/>
**经典版**
* 表单组件:<https://jeesite.com/docs/views-beetl/>
* 表格组件:<https://jeesite.com/docs/datagrid/>
* 常用工具:<https://jeesite.com/docs/jeesite-js/>
## 专题文章
* 自定义主题:<https://jeesite.com/docs/custom-views/>
* 国际化多语言:<https://jeesite.com/docs/i18n-locale/>
* 接口文档:<https://jeesite.com/docs/mobile-rest-api/>
* BPM工作流引擎<https://jeesite.com/docs/bpm/>
* 用户类型<https://jeesite.com/docs/user-type/>
* 消息推送<https://jeesite.com/docs/msg-push-use/>
* 单点登录<https://jeesite.com/docs/sso-cas/>
## 更多文档
* AI、CMS、RAG、Tool、MCP 人工智能助手<https://jeesite.com/docs/ai-cms>
* BPM 业务流程引擎Flowable<http://jeesite.com/docs/bpm/>
* CMS 多站点内容管理模块<https://jeesite.com/docs/cms/>
* 消息推送消息提醒:<https://jeesite.com/docs/msg-push-use/>
* 对象存储模块:<https://jeesite.com/docs/oss-client>
* 单点登录模块:<https://jeesite.com/docs/sso-cas>
* 在线任务调度:<https://jeesite.com/docs/job/>
* 对象存储:<https://jeesite.com/docs/oss-client/>
* 大屏设计器:<https://jeesite.com/docs/visual/>
* 报表设计器:<https://jeesite.com/docs/ureport/>
* 文件管理分享:<https://jeesite.com/docs/filemanager/>
* 文件在线预览:<https://jeesite.com/docs/filepreview/>
* 三员管理员:<https://jeesite.com/docs/manager3/>
* 手机端框架:<https://jeesite.com/docs/uniapp/>
* 统一认证服务:<https://jeesite.com/docs/oauth2-server/>
* 树表结构设计:<https://jeesite.com/docs/tree-table-use/>
## 云服务架构
* 集群、高可用架构:<https://jeesite.com/docs/cluster/>
* SaaS 多租户架构:<https://jeesite.com/docs/saas-corp-use/>
* 读写分离分片分表:<https://jeesite.com/docs/sharding/>
* Spring监控系统<https://jeesite.com/docs/webadmin/>
* 分布式跨应用事务:<https://jeesite.com/docs/seata/>
* 追踪系统集成:<https://jeesite.com/docs/skywalking/>
* ELK 日志收集:<https://jeesite.com/docs/elk-log/>
* 多租户、SaaS服务<https://jeesite.com/docs/saas-corp-use/>
* 集群、负载均衡、高可用<https://jeesite.com/docs/cluster/>
* Spring Cloud 微服务<https://jeesite.com/docs/springcloud/>
* 分布式事务 Seata<https://jeesite.com/docs/springcloud-seata/>
* 读写分离、分库分表:<https://jeesite.com/docs/sharding/>
## 前后分离版
* Vue 版介绍:<https://jeesite.com/docs/jeesite-vue/>
* Vue 安装部署:<https://jeesite.com/docs/vue-install-deploy/>
* Vue 参数配置:<https://jeesite.com/docs/vue-settings/>
* Vue 前端权限:<https://jeesite.com/docs/vue-auth/>
* Vue 源码解析:<https://jeesite.com/docs/vue-crud-view/>
* Vue 表单组件:<https://jeesite.com/docs/vue-basic-form/>
* Vue 表格组件:<https://jeesite.com/docs/vue-basic-table/>
* Vue 常用组件:<https://jeesite.com/docs/vue-comp/>
* Vue 图标组件:<https://jeesite.com/docs/vue-icon/>
* Vue 国际化多语言:<https://jeesite.com/docs/vue-i18n/>
* Vue 样式库:<https://jeesite.com/docs/vue-style/>
* MybatisPlus: <https://gitee.com/thinkgem/jeesite-mybatisplus>
* 接口快速开发<https://gitee.com/thinkgem/jeesite-magic-api>
* 内外网中间件<https://my.oschina.net/thinkgem/blog/4624519>
* 统一认证平台<https://jeesite.com/docs/oauth2-server>
## 授权协议声明

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-parent</artifactId>
<version>5.12.0.springboot3-SNAPSHOT</version>
<version>5.14.0.springboot3-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
@@ -52,6 +52,17 @@
<artifactId>fury-core</artifactId>
<version>${fury.version}</version>
</dependency>
<dependency>
<groupId>org.apache.fury</groupId>
<artifactId>fury-extensions</artifactId>
<version>${fury.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.fury</groupId>
<artifactId>fury-test-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Jackson json -->
<dependency>

View File

@@ -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";
@@ -60,7 +61,6 @@ public class DigestUtils {
* @param algorithm 散列算法("SHA-1"、"MD5"、"SM3"
* @param salt 可为空
* @param iterations 迭代次数
* @return
*/
public static byte[] digest(byte[] input, String algorithm, byte[] salt, int iterations) {
try {

View File

@@ -32,7 +32,7 @@ import java.util.regex.Pattern;
* 4. JDK 提供的 URLEncoder
* 5. XSS、SQL、orderBy 过滤器
* @author calvin、ThinkGem
* @version 2022-2-17
* @version 2025-7-9
*/
public class EncodeUtils {
@@ -191,8 +191,8 @@ public class EncodeUtils {
// 预编译XSS过滤正则表达式
private static final List<Pattern> xssPatterns = ListUtils.newArrayList(
Pattern.compile("(<\\s*(script|link|style|iframe)([\\s\\S]*?)(>|<\\/\\s*\\1\\s*>))|(</\\s*(script|link|style|iframe)\\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*(href|src)\\s*=\\s*(\"\\s*(javascript|vbscript|data):[^\"]+\"|'\\s*(javascript|vbscript|data):[^']+'|(javascript|vbscript|data):[^\\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)
);

View File

@@ -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 {
}

View File

@@ -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);
}
}

View File

@@ -24,20 +24,20 @@ public class CommandUtils {
public static String execute(String command, String charsetName) throws IOException {
Process process = Runtime.getRuntime().exec(command);
// 记录dos命令的返回信息
StringBuffer stringBuffer = new StringBuffer();
StringBuilder sb = new StringBuilder();
// 获取返回信息的流
InputStream in = process.getInputStream();
Reader reader = new InputStreamReader(in, charsetName);
BufferedReader bReader = new BufferedReader(reader);
String res = bReader.readLine();
while (res != null) {
stringBuffer.append(res);
stringBuffer.append("\n");
sb.append(res);
sb.append("\n");
res = bReader.readLine();
}
bReader.close();
reader.close();
return stringBuffer.toString();
return sb.toString();
}
}

View File

@@ -4,26 +4,21 @@
*/
package com.jeesite.common.image;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
import org.patchca.background.BackgroundFactory;
import org.patchca.color.ColorFactory;
import org.patchca.filter.predefined.CurvesRippleFilterFactory;
import org.patchca.filter.predefined.DiffuseRippleFilterFactory;
import org.patchca.filter.predefined.DoubleRippleFilterFactory;
import org.patchca.filter.predefined.MarbleRippleFilterFactory;
import org.patchca.filter.predefined.WobbleRippleFilterFactory;
import org.patchca.filter.predefined.*;
import org.patchca.font.RandomFontFactory;
import org.patchca.service.ConfigurableCaptchaService;
import org.patchca.text.renderer.BestFitTextRenderer;
import org.patchca.utils.encoder.EncoderHelper;
import org.patchca.word.RandomWordFactory;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
/**
* 验证码工具
* @author ThinkGem
@@ -31,7 +26,7 @@ import org.patchca.word.RandomWordFactory;
*/
public class CaptchaUtils {
private static Random random = new Random();
private static final Random random = new Random();
private volatile static ConfigurableCaptchaService ccs;
private static WobbleRippleFilterFactory wrff; // 摆波纹
private static DoubleRippleFilterFactory doff; // 双波纹
@@ -135,9 +130,6 @@ public class CaptchaUtils {
/**
* 生成验证码
* @param request
* @param response
* @throws IOException
* @return 验证码字符
*/
public static String generateCaptcha(OutputStream outputStream) throws IOException{

View File

@@ -11,7 +11,7 @@
//import com.drew.metadata.exif.GpsDirectory;
//
///**
// * 图片地理信息获取
// * 图片地理信息获取pom.xml 中打开 com.drewnoakes 依赖)
// * @author ThinkGem
// */
//public class ImageGeo {
@@ -45,7 +45,7 @@
// if (easting.equalsIgnoreCase("W")) {
// lonsign = -1.0d;
// }
//
//
// lat = (Math.abs(latpart[0].doubleValue()) + latpart[1].doubleValue() / 60.0d + latpart[2].doubleValue() / 3600.0d) * latsign;
// lon = (Math.abs(lonpart[0].doubleValue()) + lonpart[1].doubleValue() / 60.0d + lonpart[2].doubleValue() / 3600.0d) * lonsign;
//

View File

@@ -68,7 +68,7 @@ public class ImageUtils {
bilder.toFile(targetFile);
}
}catch(IOException e){
logger.error("图片压缩失败:" + imageFile.getAbsoluteFile(), e);
logger.error("图片压缩失败:{}", imageFile.getAbsoluteFile(), e);
}
}

View File

@@ -10,6 +10,8 @@ import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
@@ -23,13 +25,14 @@ import java.util.Hashtable;
*/
public class ZxingUtils {
private static final Logger logger = LoggerFactory.getLogger(ZxingUtils.class);
/**
* 条形码编码
*
* @param contents
* @param width
* @param height
* @param imgPath
* @param contents 内容
* @param width 宽度
* @param height 高度
* @param imgPath 图片路径
*/
public static void encode(String contents, int width, int height, String imgPath) {
int codeWidth = 3 + // start guard
@@ -42,14 +45,13 @@ public class ZxingUtils {
BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, BarcodeFormat.EAN_13, codeWidth, height, null);
MatrixToImageWriter.writeToPath(bitMatrix, "png", new File(imgPath).toPath());
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
}
/**
* 条形码解码
*
* @param imgPath
* @param imgPath 图片路径
* @return String
*/
public static String decode(String imgPath) {
@@ -58,25 +60,24 @@ public class ZxingUtils {
try {
image = ImageIO.read(new File(imgPath));
if (image == null) {
System.out.println("the decode image may be not exit.");
logger.debug("the decode image may be not exit.");
}
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
result = new MultiFormatReader().decode(bitmap, null);
return result.getText();
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
return null;
}
/**
* 二维码编码
*
* @param contents
* @param width
* @param height
* @param imgPath
* @param contents 内容
* @param width 宽度
* @param height 高度
* @param imgPath 图片路径
*/
public static void encode2(String contents, int width, int height, String imgPath) {
Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
@@ -88,14 +89,13 @@ public class ZxingUtils {
BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, width, height, hints);
MatrixToImageWriter.writeToPath(bitMatrix, "png", new File(imgPath).toPath());
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
}
/**
* 二维码解码
*
* @param imgPath
* @param imgPath 图片路径
* @return String
*/
public static String decode2(String imgPath) {
@@ -104,7 +104,7 @@ public class ZxingUtils {
try {
image = ImageIO.read(new File(imgPath));
if (image == null) {
System.out.println("the decode image may be not exit.");
logger.debug("the decode image may be not exit.");
}
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
@@ -113,7 +113,7 @@ public class ZxingUtils {
result = new MultiFormatReader().decode(bitmap, hints);
return result.getText();
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
return null;
}

View File

@@ -19,6 +19,8 @@ import org.springframework.core.io.ClassPathResource;
import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
@@ -30,7 +32,7 @@ import java.util.zip.ZipOutputStream;
* 文件操作工具类
* 实现文件的创建、删除、复制、压缩、解压以及目录的创建、删除、复制、压缩解压等功能
* @author ThinkGem
* @version 2015-3-16
* @version 2025-08-08
*/
@SuppressWarnings("deprecation")
public class FileUtils extends org.apache.commons.io.FileUtils {
@@ -57,17 +59,16 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
* @param coverlay 如果目标文件已存在,是否覆盖
* @return 如果复制成功则返回true否则返回false
*/
public static boolean copyFileCover(String srcFileName,
String descFileName, boolean coverlay) {
public static boolean copyFileCover(String srcFileName, String descFileName, boolean coverlay) {
File srcFile = new File(srcFileName);
// 判断源文件是否存在
if (!srcFile.exists()) {
logger.debug("复制文件失败,源文件 " + srcFileName + " 不存在!");
logger.debug("复制文件失败,源文件 {} 不存在!", srcFileName);
return false;
}
// 判断源文件是否是合法的文件
else if (!srcFile.isFile()) {
logger.debug("复制文件失败," + srcFileName + " 不是一个文件!");
logger.debug("复制文件失败,{} 不是一个文件!", srcFileName);
return false;
}
File descFile = new File(descFileName);
@@ -77,11 +78,11 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
if (coverlay) {
logger.debug("目标文件已存在,准备删除!");
if (!FileUtils.delFile(descFileName)) {
logger.debug("删除目标文件 " + descFileName + " 失败!");
logger.debug("删除目标文件 {} 失败!", descFileName);
return false;
}
} else {
logger.debug("复制文件失败,目标文件 " + descFileName + " 已存在!");
logger.debug("复制文件失败,目标文件 {} 已存在!", descFileName);
return false;
}
} else {
@@ -95,45 +96,26 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
}
}
}
// 准备复制文件
// 读取的位数
int readByte = 0;
InputStream ins = null;
OutputStream outs = null;
try {
try (
// 打开源文件
ins = new FileInputStream(srcFile);
InputStream ins = Files.newInputStream(srcFile.toPath());
// 打开目标文件的输出流
outs = new FileOutputStream(descFile);
byte[] buf = new byte[1024];
OutputStream outs = Files.newOutputStream(descFile.toPath());
) {
// 读取的位数
int readByte = 0;
// 一次读取1024个字节当readByte为-1时表示文件已经读取完毕
byte[] buf = new byte[1024];
while ((readByte = ins.read(buf)) != -1) {
// 将读取的字节流写入到输出流
outs.write(buf, 0, readByte);
}
logger.debug("复制单个文件 " + srcFileName + "" + descFileName
+ "成功!");
logger.debug("复制单个文件 {} 到{}成功!", srcFileName, descFileName);
return true;
} catch (Exception e) {
logger.debug("复制文件失败:" + e.getMessage());
logger.debug("复制文件失败:{}", e.getMessage());
return false;
} finally {
// 关闭输入输出流,首先关闭输出流,然后再关闭输入流
if (outs != null) {
try {
outs.close();
} catch (IOException oute) {
oute.printStackTrace();
}
}
if (ins != null) {
try {
ins.close();
} catch (IOException ine) {
ine.printStackTrace();
}
}
}
}
@@ -144,8 +126,7 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
* @return 如果复制成功返回true否则返回false
*/
public static boolean copyDirectory(String srcDirName, String descDirName) {
return FileUtils.copyDirectoryCover(srcDirName, descDirName,
false);
return FileUtils.copyDirectoryCover(srcDirName, descDirName, false);
}
/**
@@ -155,17 +136,16 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
* @param coverlay 如果目标目录存在,是否覆盖
* @return 如果复制成功返回true否则返回false
*/
public static boolean copyDirectoryCover(String srcDirName,
String descDirName, boolean coverlay) {
public static boolean copyDirectoryCover(String srcDirName, String descDirName, boolean coverlay) {
File srcDir = new File(srcDirName);
// 判断源目录是否存在
if (!srcDir.exists()) {
logger.debug("复制目录失败,源目录 " + srcDirName + " 不存在!");
logger.debug("复制目录失败,源目录 {} 不存在!", srcDirName);
return false;
}
// 判断源目录是否是目录
else if (!srcDir.isDirectory()) {
logger.debug("复制目录失败," + srcDirName + " 不是一个目录!");
logger.debug("复制目录失败,{} 不是一个目录!", srcDirName);
return false;
}
// 如果目标文件夹名不以文件分隔符结尾,自动添加文件分隔符
@@ -180,11 +160,11 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
// 允许覆盖目标目录
logger.debug("目标目录已存在,准备删除!");
if (!FileUtils.delFile(descDirNames)) {
logger.debug("删除目录 " + descDirNames + " 失败!");
logger.debug("删除目录 {} 失败!", descDirNames);
return false;
}
} else {
logger.debug("目标目录复制失败,目标目录 " + descDirNames + " 已存在!");
logger.debug("目标目录复制失败,目标目录 {} 已存在!", descDirNames);
return false;
}
} else {
@@ -200,32 +180,31 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
boolean flag = true;
// 列出源目录下的所有文件名和子目录名
File[] files = srcDir.listFiles();
for (int i = 0; i < files.length; i++) {
// 如果是一个单个文件,则直接复制
if (files[i].isFile()) {
flag = FileUtils.copyFile(files[i].getAbsolutePath(),
descDirName + files[i].getName());
// 如果拷贝文件失败,则退出循环
if (!flag) {
break;
if (files != null) {
for (File file : files) {
// 如果是一个单个文件,则直接复制
if (file.isFile()) {
flag = FileUtils.copyFile(file.getAbsolutePath(), descDirName + file.getName());
// 如果拷贝文件失败,则退出循环
if (!flag) {
break;
}
}
}
// 如果是子目录,则继续复制目录
if (files[i].isDirectory()) {
flag = FileUtils.copyDirectory(files[i]
.getAbsolutePath(), descDirName + files[i].getName());
// 如果拷贝目录失败,则退出循环
if (!flag) {
break;
// 如果是子目录,则继续复制目录
if (file.isDirectory()) {
flag = FileUtils.copyDirectory(file.getAbsolutePath(), descDirName + file.getName());
// 如果拷贝目录失败,则退出循环
if (!flag) {
break;
}
}
}
}
if (!flag) {
logger.debug("复制目录 " + srcDirName + "" + descDirName + " 失败!");
logger.debug("复制目录 {} 到 {} 失败!", srcDirName, descDirName);
return false;
}
logger.debug("复制目录 " + srcDirName + "" + descDirName + " 成功!");
logger.debug("复制目录 {} 到 {} 成功!", srcDirName, descDirName);
return true;
}
@@ -238,7 +217,7 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
*/
public static String readFileToString(String classResourcePath){
try (InputStream in = new ClassPathResource(classResourcePath).getInputStream()){
return IOUtils.toString(in, EncodeUtils.UTF_8);
return IOUtils.toString(in, StandardCharsets.UTF_8);
} catch (IOException e) {
logger.warn("Error file convert: {}", e.getMessage());
}
@@ -246,16 +225,14 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
}
/**
*
* 删除文件,可以删除单个文件或文件夹
*
* @param fileName 被删除的文件名
* @return 如果删除成功则返回true否是返回false
*/
public static boolean delFile(String fileName) {
File file = new File(fileName);
if (!file.exists()) {
logger.debug(fileName + " 文件不存在!");
logger.debug("{} 文件不存在!", fileName);
return true;
} else {
if (file.isFile()) {
@@ -267,9 +244,7 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
}
/**
*
* 删除单个文件
*
* @param fileName 被删除的文件名
* @return 如果删除成功则返回true否则返回false
*/
@@ -277,22 +252,20 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
File file = new File(fileName);
if (file.exists() && file.isFile()) {
if (file.delete()) {
logger.debug("删除文件 " + fileName + " 成功!");
logger.debug("删除文件 {} 成功!", fileName);
return true;
} else {
logger.debug("删除文件 " + fileName + " 失败!");
logger.debug("删除文件 {} 失败!", fileName);
return false;
}
} else {
logger.debug(fileName + " 文件不存在!");
logger.debug("{} 文件不存在!", fileName);
return true;
}
}
/**
*
* 删除目录及目录下的文件
*
* @param dirName 被删除的目录所在的文件路径
* @return 如果目录删除成功则返回true否则返回false
*/
@@ -309,36 +282,36 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
boolean flag = true;
// 列出全部文件及子目录
File[] files = dirFile.listFiles();
for (int i = 0; i < files.length; i++) {
// 删除子文件
if (files[i].isFile()) {
flag = FileUtils.deleteFile(files[i].getAbsolutePath());
// 如果删除文件失败,则退出循环
if (!flag) {
break;
if (files != null) {
for (File file : files) {
// 删除子文件
if (file.isFile()) {
flag = FileUtils.deleteFile(file.getAbsolutePath());
// 如果删除文件失败,则退出循环
if (!flag) {
break;
}
}
}
// 删除子目录
else if (files[i].isDirectory()) {
flag = FileUtils.deleteDirectory(files[i]
.getAbsolutePath());
// 如果删除子目录失败,则退出循环
if (!flag) {
break;
// 删除子目录
else if (file.isDirectory()) {
flag = FileUtils.deleteDirectory(file.getAbsolutePath());
// 如果删除子目录失败,则退出循环
if (!flag) {
break;
}
}
}
}
if (!flag) {
logger.debug("删除目录失败!");
return false;
}
// 删除当前目录
if (dirFile.delete()) {
logger.debug("删除目录 " + dirName + " 成功!");
logger.debug("删除目录 {} 成功!", dirName);
return true;
} else {
logger.debug("删除目录 " + dirName + " 失败!");
logger.debug("删除目录 {} 失败!", dirName);
return false;
}
@@ -352,11 +325,11 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
public static boolean createFile(String descFileName) {
File file = new File(descFileName);
if (file.exists()) {
logger.debug("文件 " + descFileName + " 已存在!");
logger.debug("文件 {} 已存在!", descFileName);
return false;
}
if (descFileName.endsWith(File.separator)) {
logger.debug(descFileName + " 为目录,不能创建目录!");
logger.debug("{} 为目录,不能创建目录!", descFileName);
return false;
}
if (!file.getParentFile().exists()) {
@@ -366,22 +339,19 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
return false;
}
}
// 创建文件
try {
if (file.createNewFile()) {
logger.debug(descFileName + " 文件创建成功!");
logger.debug("{} 文件创建成功!", descFileName);
return true;
} else {
logger.debug(descFileName + " 文件创建失败!");
logger.debug("{} 文件创建失败!", descFileName);
return false;
}
} catch (Exception e) {
e.printStackTrace();
logger.debug(descFileName + " 文件创建失败!");
logger.debug("{} 文件创建失败!", descFileName, e);
return false;
}
}
/**
@@ -396,15 +366,15 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
}
File descDir = new File(descDirNames);
if (descDir.exists()) {
logger.debug("目录 " + descDirNames + " 已存在!");
logger.debug("目录 {} 已存在!", descDirNames);
return false;
}
// 创建目录
if (descDir.mkdirs()) {
logger.debug("目录 " + descDirNames + " 创建成功!");
logger.debug("目录 {} 创建成功!", descDirNames);
return true;
} else {
logger.debug("目录 " + descDirNames + " 创建失败!");
logger.debug("目录 {} 创建失败!", descDirNames);
return false;
}
@@ -417,9 +387,9 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
public static void writeToFile(String fileName, String content, boolean append) {
try {
FileUtils.write(new File(fileName), content, EncodeUtils.UTF_8, append);
logger.debug("文件 " + fileName + " 写入成功!");
logger.debug("文件 {} 写入成功!", fileName);
} catch (IOException e) {
logger.debug("文件 " + fileName + " 写入失败! " + e.getMessage());
logger.debug("文件 {} 写入失败!", fileName, e);
}
}
@@ -430,9 +400,9 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
public static void writeToFile(String fileName, String content, String encoding, boolean append) {
try {
FileUtils.write(new File(fileName), content, encoding, append);
logger.debug("文件 " + fileName + " 写入成功!");
logger.debug("文件 {} 写入成功!", fileName);
} catch (IOException e) {
logger.debug("文件 " + fileName + " 写入失败! " + e.getMessage());
logger.debug("文件 {} 写入失败!", fileName, e);
}
}
@@ -447,12 +417,11 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
return;
}
byte[] data = EncodeUtils.decodeBase64(base64);
File file = new File(fileName);
try {
FileUtils.writeByteArrayToFile(file, data);
} catch (IOException e) {
e.printStackTrace();
logger.error("文件 {} 写入失败!", fileName, e);
}
}
@@ -470,33 +439,30 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
public static void zipFiles(String srcDirName, String fileName, String descFileName) {
// 判断目录是否存在
if (srcDirName == null) {
logger.debug("文件压缩失败,目录 " + srcDirName + " 不存在!");
logger.debug("文件压缩失败,目录 {} 不存在!", srcDirName);
return;
}
File fileDir = new File(srcDirName);
if (!fileDir.exists() || !fileDir.isDirectory()) {
logger.debug("文件压缩失败,目录 " + srcDirName + " 不存在!");
logger.debug("文件压缩失败,目录 {} 不存在!", srcDirName);
return;
}
String dirPath = fileDir.getAbsolutePath();
File descFile = new File(descFileName);
try {
ZipOutputStream zouts = new ZipOutputStream(new FileOutputStream(descFile));
try (ZipOutputStream outs = new ZipOutputStream(new FileOutputStream(descFile));) {
if ("*".equals(fileName) || StringUtils.EMPTY.equals(fileName)) {
FileUtils.zipDirectoryToZipFile(dirPath, fileDir, zouts);
FileUtils.zipDirectoryToZipFile(dirPath, fileDir, outs);
} else {
File file = new File(fileDir, fileName);
if (file.isFile()) {
FileUtils.zipFilesToZipFile(dirPath, file, zouts);
FileUtils.zipFilesToZipFile(dirPath, file, outs);
} else {
FileUtils.zipDirectoryToZipFile(dirPath, file, zouts);
FileUtils.zipDirectoryToZipFile(dirPath, file, outs);
}
}
zouts.close();
logger.debug(descFileName + " 文件压缩成功!");
logger.debug("{} 文件压缩成功!", descFileName);
} catch (Exception e) {
logger.debug("文件压缩失败" + e.getMessage());
e.printStackTrace();
logger.error("文件压缩失败!", e);
}
}
@@ -510,10 +476,11 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
String descFileNames = descFileName;
if (!descFileNames.endsWith(File.separator)) {
descFileNames = descFileNames + File.separator;
}
try {
}
try (
// 根据ZIP文件创建ZipFile对象
ZipFile zipFile = new ZipFile(zipFileName);
) {
ZipEntry entry = null;
String entryName = null;
String descFileDir = null;
@@ -537,21 +504,21 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
new File(descFileDir).getParentFile().mkdirs();
}
File file = new File(descFileDir);
// 打开文件输出流
OutputStream os = new FileOutputStream(file);
// 从ZipFile对象中打开entry的输入流
InputStream is = zipFile.getInputStream(entry);
while ((readByte = is.read(buf)) != -1) {
os.write(buf, 0, readByte);
try (
// 打开文件输出流
OutputStream os = new FileOutputStream(file);
// 从ZipFile对象中打开entry的输入流
InputStream is = zipFile.getInputStream(entry);
) {
while ((readByte = is.read(buf)) != -1) {
os.write(buf, 0, readByte);
}
}
os.close();
is.close();
}
zipFile.close();
logger.debug("文件解压成功!");
return true;
} catch (Exception e) {
logger.debug("文件解压失败" + e.getMessage());
logger.error("文件解压失败!", e);
return false;
}
}
@@ -566,25 +533,26 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
if (fileDir.isDirectory()) {
File[] files = fileDir.listFiles();
// 空的文件夹
if (files.length == 0) {
if (files != null && files.length == 0) {
// 目录信息
ZipEntry entry = new ZipEntry(getEntryName(dirPath, fileDir));
try {
zouts.putNextEntry(entry);
zouts.closeEntry();
} catch (Exception e) {
e.printStackTrace();
logger.error("压缩失败!", e);
}
return;
}
for (int i = 0; i < files.length; i++) {
if (files[i].isFile()) {
// 如果是文件,则调用文件压缩方法
FileUtils.zipFilesToZipFile(dirPath, files[i], zouts);
} else {
// 如果是目录,则递归调用
FileUtils.zipDirectoryToZipFile(dirPath, files[i], zouts);
if (files != null) {
for (File file : files) {
if (file.isFile()) {
// 如果是文件,则调用文件压缩方法
FileUtils.zipFilesToZipFile(dirPath, file, zouts);
} else {
// 如果是目录,则递归调用
FileUtils.zipDirectoryToZipFile(dirPath, file, zouts);
}
}
}
}
@@ -597,17 +565,16 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
* @param zouts 输出流
*/
public static void zipFilesToZipFile(String dirPath, File file, ZipOutputStream zouts) {
FileInputStream fin = null;
ZipEntry entry = null;
// 创建复制缓冲区
byte[] buf = new byte[4096];
int readByte = 0;
if (file.isFile()) {
try {
try (
// 创建一个文件输入流
fin = new FileInputStream(file);
FileInputStream fin = new FileInputStream(file);
) {
// 创建一个ZipEntry
entry = new ZipEntry(getEntryName(dirPath, file));
ZipEntry entry = new ZipEntry(getEntryName(dirPath, file));
// 存储信息到压缩文件
zouts.putNextEntry(entry);
// 复制字节到压缩文件
@@ -615,10 +582,9 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
zouts.write(buf, 0, readByte);
}
zouts.closeEntry();
fin.close();
logger.debug("添加文件 " + file.getAbsolutePath() + " 到zip文件中!");
logger.debug("添加文件 {} 到zip文件中!", file.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
logger.error("添加文件失败!", e);
}
}
}
@@ -627,7 +593,7 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
* 获取待压缩文件在ZIP文件中entry的名字即相对于跟目录的相对路径名
* @param dirPath 目录名
* @param file entry文件名
* @return
* @return entry名字
*/
private static String getEntryName(String dirPath, File file) {
String dirPaths = dirPath;
@@ -842,8 +808,8 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
/**
* 根据图片Base64获取文件扩展名
* @param imageBase64
* @return
* @param imageBase64 图片编码内容
* @return 图片文件的扩展名
* @author ThinkGem
*/
public static String getFileExtensionByImageBase64(String imageBase64){
@@ -861,33 +827,28 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
/**
* 获取工程源文件所在路径
* @return
*/
public static String getProjectPath(){
String projectPath = "";
try {
File file = ResourceUtils.getResource("").getFile();
if (file != null){
while(true){
File f = new File(path(file.getPath() + "/src/main"));
if (f.exists()){
break;
}
f = new File(path(file.getPath() + "/target/classes"));
if (f.exists()){
break;
}
File p = file.getParentFile();
if (p != null){
file = p;
}else{
break;
}
while (true) {
File f = new File(path(file.getPath() + "/src/main"));
if (f.exists()) {
break;
}
f = new File(path(file.getPath() + "/target/classes"));
if (f.exists()) {
break;
}
File p = file.getParentFile();
if (p != null) {
file = p;
} else {
break;
}
projectPath = file.toString();
}
} catch (FileNotFoundException e) {
// 忽略异常
projectPath = file.toString();
} catch (IOException e) {
// 忽略异常
}
@@ -900,33 +861,28 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
/**
* 获取工程源文件所在路径
* @return
*/
public static String getWebappPath(){
String webappPath = "";
try {
File file = ResourceUtils.getResource("").getFile();
if (file != null){
while(true){
File f = new File(path(file.getPath() + "/WEB-INF/classes"));
if (f.exists()){
break;
}
f = new File(path(file.getPath() + "/src/main/webapp"));
if (f.exists()){
return f.getPath();
}
File p = file.getParentFile();
if (p != null){
file = p;
}else{
break;
}
while (true) {
File f = new File(path(file.getPath() + "/WEB-INF/classes"));
if (f.exists()) {
break;
}
f = new File(path(file.getPath() + "/src/main/webapp"));
if (f.exists()) {
return f.getPath();
}
File p = file.getParentFile();
if (p != null) {
file = p;
} else {
break;
}
webappPath = file.toString();
}
} catch (FileNotFoundException e) {
// 忽略异常
webappPath = file.toString();
} catch (IOException e) {
// 忽略异常
}

View File

@@ -4,6 +4,9 @@
*/
package com.jeesite.common.io;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
@@ -15,71 +18,70 @@ import java.io.InputStream;
/**
* 数据流工具类
* @author ThinkGem
* @version 2025-08-08
*/
public class IOUtils extends org.apache.commons.io.IOUtils {
private static final Logger logger = LoggerFactory.getLogger(IOUtils.class);
/**
* 根据文件路径创建文件输入流处理 以字节为单位(非 unicode
* @param filePath
* @return
* @param filePath 文件路径
* @return 文件流
*/
public static FileInputStream getFileInputStream(String filePath) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(filePath);
} catch (FileNotFoundException e) {
System.out.println("错误信息:文件不存在");
e.printStackTrace();
logger.error("文件不存在!", e);
}
return fileInputStream;
}
/**
* 根据文件对象创建文件输入流处理 以字节为单位(非 unicode
* @param file
* @return
* @param file 文件对象
* @return 文件流
*/
public static FileInputStream getFileInputStream(File file) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
System.out.println("错误信息:文件不存在");
e.printStackTrace();
logger.error("文件不存在!", e);
}
return fileInputStream;
}
/**
* 根据文件对象创建文件输出流处理 以字节为单位(非 unicode
* @param file
* @param file 文件对象
* @param append true:文件以追加方式打开,false:则覆盖原文件的内容
* @return
* @return 文件流
*/
public static FileOutputStream getFileOutputStream(File file, boolean append) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file, append);
} catch (FileNotFoundException e) {
System.out.println("错误信息:文件不存在");
e.printStackTrace();
logger.error("文件不存在!", e);
}
return fileOutputStream;
}
/**
* 根据文件路径创建文件输出流处理 以字节为单位(非 unicode
* @param filePath
* @param filePath 文件路径
* @param append true:文件以追加方式打开,false:则覆盖原文件的内容
* @return
* @return 文件流
*/
public static FileOutputStream getFileOutputStream(String filePath, boolean append) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(filePath, append);
} catch (FileNotFoundException e) {
System.out.println("错误信息:文件不存在");
e.printStackTrace();
logger.error("文件不存在!", e);
}
return fileOutputStream;
}

View File

@@ -30,7 +30,7 @@ import java.util.regex.Pattern;
* 相同的属性在最后载入的文件中的值将会覆盖之前的值,
* 取不到从System.getProperty()获取。
* @author ThinkGem
* @version 2017-12-30
* @version 2025-4-17
*/
public class PropertiesUtils {
@@ -64,8 +64,11 @@ public class PropertiesUtils {
// 获取全局设置默认的配置文件(以下是支持环境配置的属性文件)
Set<String> set = SetUtils.newLinkedHashSet();
set.addAll(Arrays.asList(DEFAULT_CONFIG_FILE));
// 获取 spring.config.location 外部自定义的配置文件
// 获取 spring.config.location、spring.config.additional-location 外部自定义的配置文件
String customConfigs = System.getProperty("spring.config.location");
if (StringUtils.isBlank(customConfigs)){
customConfigs = System.getProperty("spring.config.additional-location");
}
if (StringUtils.isNotBlank(customConfigs)){
for (String customConfig : StringUtils.splitComma(customConfigs)){
if (!customConfig.contains("$")){

View File

@@ -4,7 +4,6 @@
*/
package com.jeesite.common.io;
import com.jeesite.common.codec.EncodeUtils;
import com.jeesite.common.lang.ExceptionUtils;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
@@ -14,6 +13,7 @@ import org.springframework.core.io.support.ResourcePatternResolver;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
* 资源供给类
@@ -53,9 +53,7 @@ public class ResourceUtils extends org.springframework.util.ResourceUtils {
/**
* 获取资源文件流(用后记得关闭)
* @param location
* @author ThinkGem
* @throws IOException
*/
public static InputStream getResourceFileStream(String location) throws IOException{
Resource resource = resourceLoader.getResource(location);
@@ -64,12 +62,11 @@ public class ResourceUtils extends org.springframework.util.ResourceUtils {
/**
* 获取资源文件内容
* @param location
* @author ThinkGem
*/
public static String getResourceFileContent(String location){
try(InputStream is = ResourceUtils.getResourceFileStream(location)){
return IOUtils.toString(is, EncodeUtils.UTF_8);
return IOUtils.toString(is, StandardCharsets.UTF_8);
}catch (IOException e) {
throw ExceptionUtils.unchecked(e);
}
@@ -77,13 +74,11 @@ public class ResourceUtils extends org.springframework.util.ResourceUtils {
/**
* Spring 搜索资源文件
* @param locationPattern
* @author ThinkGem
*/
public static Resource[] getResources(String locationPattern){
try {
Resource[] resources = resourceResolver.getResources(locationPattern);
return resources;
return resourceResolver.getResources(locationPattern);
} catch (IOException e) {
throw ExceptionUtils.unchecked(e);
}

View File

@@ -15,7 +15,6 @@ public class ByteUtils {
/**
* @param byteSize 字节
* @return
*/
public static String formatByteSize(long byteSize) {

View File

@@ -15,7 +15,7 @@ import java.util.Date;
/**
* 日期工具类, 继承org.apache.commons.lang.time.DateUtils类
* @author ThinkGem
* @version 2017-1-4
* @version 2025-08-08
*/
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
@@ -84,7 +84,6 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
* @param pattern 格式yyyy-MM-dd pattern可以为"yyyy-MM-dd" "HH:mm:ss" "E"
* @param amont 数量,前为负数,后为正数
* @param type 类型可参考Calendar的常量(如Calendar.HOUR、Calendar.MINUTE、Calendar.SECOND)
* @return
*/
public static String getDate(String pattern, int amont, int type) {
Calendar calendar = Calendar.getInstance(LocaleUtils.getTimeZone(), LocaleUtils.getLocale());
@@ -179,8 +178,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
/**
* 获取过去的天数
* @param date
* @return
* @param date 日期
*/
public static long pastDays(Date date) {
long t = System.currentTimeMillis()-date.getTime();
@@ -189,8 +187,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
/**
* 获取过去的小时
* @param date
* @return
* @param date 日期
*/
public static long pastHour(Date date) {
long t = System.currentTimeMillis()-date.getTime();
@@ -199,8 +196,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
/**
* 获取过去的分钟
* @param date
* @return
* @param date 日期
*/
public static long pastMinutes(Date date) {
long t = System.currentTimeMillis()-date.getTime();
@@ -209,15 +205,13 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
/**
* 获取两个日期之间的天数
*
* @param before
* @param after
* @return
* @param before 开始日期
* @param after 结束日期
*/
public static double getDistanceOfTwoDate(Date before, Date after) {
long beforeTime = before.getTime();
long afterTime = after.getTime();
return (afterTime - beforeTime) / (1000 * 60 * 60 * 24);
return (double) (afterTime - beforeTime) / (1000 * 60 * 60 * 24);
}
/**
@@ -250,8 +244,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
/**
* 获取日期是当年的第几周
* @param date
* @return
* @param date 日期
*/
public static int getWeekOfYear(Date date){
Calendar cal = Calendar.getInstance(LocaleUtils.getTimeZone(), LocaleUtils.getLocale());
@@ -262,7 +255,6 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
/**
* 获取一天的开始时间2015-11-3 00:00:00.000
* @param date 日期
* @return
*/
public static Date getOfDayFirst(Date date) {
if (date == null){
@@ -280,7 +272,6 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
/**
* 获取一天的最后时间2015-11-3 23:59:59.999
* @param date 日期
* @return
*/
public static Date getOfDayLast(Date date) {
if (date == null){
@@ -297,7 +288,6 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
/**
* 获取服务器启动时间
* @return
*/
public static Date getServerStartDate(){
long time = ManagementFactory.getRuntimeMXBean().getStartTime();

View File

@@ -19,8 +19,7 @@ public class ExceptionUtils {
/**
* 在request中获取异常类
* @param request
* @return
* @param request 请求对象
*/
public static Throwable getThrowable(HttpServletRequest request){
Throwable ex = null;

View File

@@ -79,7 +79,6 @@ public class NumberUtils extends org.apache.commons.lang3.math.NumberUtils {
/**
* 格式化双精度,保留两个小数
* @return
*/
public static String formatDouble(Double b) {
BigDecimal bg = new BigDecimal(b);
@@ -88,7 +87,6 @@ public class NumberUtils extends org.apache.commons.lang3.math.NumberUtils {
/**
* 百分比计算
* @return
*/
public static String formatScale(double one, long total) {
BigDecimal bg = new BigDecimal(one * 100 / total);
@@ -97,8 +95,6 @@ public class NumberUtils extends org.apache.commons.lang3.math.NumberUtils {
/**
* 格式化数值类型
* @param data
* @param pattern
*/
public static String formatNumber(Object data, String pattern) {
if (data == null){

View File

@@ -163,8 +163,8 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
/**
* 拷贝一个对象(但是子对象无法拷贝)
* @param source
* @param ignoreProperties
* @param source 原对象
* @param ignoreProperties 忽略的属性
*/
public static Object copyBean(Object source, String... ignoreProperties){
if (source == null){
@@ -182,7 +182,7 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
/**
* 克隆一个对象(完全拷贝)
* @param source
* @param source 原对象
*/
public static Object cloneBean(Object source){
if (source == null){
@@ -194,8 +194,6 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
/**
* 序列化对象
* @param object
* @return
*/
public static byte[] serialize(Object object) {
try {
@@ -212,8 +210,6 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
/**
* 反序列化对象
* @param bytes
* @return
*/
public static Object unserialize(byte[] bytes) {
try {
@@ -230,8 +226,6 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
/**
* 序列化对象
* @param object
* @return
*/
public static byte[] serializeJava(Object object) {
if (object == null){
@@ -255,8 +249,6 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
/**
* 反序列化对象
* @param bytes
* @return
*/
public static Object unserializeJava(byte[] bytes) {
if (bytes == null){
@@ -282,8 +274,6 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
/**
* 序列化对象
* @param object
* @return
*/
public static byte[] serializeFury(Object object) {
if (object == null){
@@ -300,8 +290,6 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
/**
* 反序列化对象
* @param bytes
* @return
*/
public static Object unserializeFury(byte[] bytes) {
if (bytes == null){

View File

@@ -6,6 +6,9 @@ package com.jeesite.common.lang;
import com.jeesite.common.codec.EncodeUtils;
import com.jeesite.common.collect.ListUtils;
import org.apache.commons.lang3.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
@@ -17,10 +20,12 @@ import java.util.regex.Pattern;
/**
* 字符串工具类, 继承org.apache.commons.lang3.StringUtils类
* @author ThinkGem
* @version 2018-1-6
* @version 2025-10-10
*/
public class StringUtils extends org.apache.commons.lang3.StringUtils {
private static final Logger logger = LoggerFactory.getLogger(StringUtils.class);
public static final String DOT = ".";
public static final String COMMA = ",";
public static final String COLON = ":";
@@ -31,7 +36,6 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/**
* 分隔字符串(逗号分隔)
* @param str 字符串
* @return
*/
public static String[] splitComma(final String str) {
return split(str, COMMA);
@@ -40,7 +44,6 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/**
* 连接字符串(逗号分隔)
* @param array 字符串数组
* @return
*/
public static String joinComma(final Object[] array) {
return join(array, COMMA);
@@ -49,81 +52,189 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/**
* 连接字符串(逗号分隔)
* @param iterable 字符串集合
* @return
*/
public static String joinComma(final Iterable<?> iterable) {
return join(iterable, COMMA);
}
/**
* 转换为字节数组
* @param str
* @return
*/
public static byte[] getBytes(String str){
if (str != null){
/**
* 转换为字节数组
* @param str 字符串
*/
public static byte[] getBytes(String str) {
if (str != null) {
return str.getBytes(StandardCharsets.UTF_8);
}else{
return null;
}
}
/**
* 转换为字节数组
* @param bytes
* @return
*/
public static String toString(byte[] bytes){
} else {
return null;
}
}
/**
* 转换为字节数组
* @param bytes 字节数组
*/
public static String toString(byte[] bytes) {
return new String(bytes, StandardCharsets.UTF_8);
}
/**
* 是否包含字符串
* @param str 验证字符串
* @param strs 字符串组
* @return 包含返回true
*/
public static boolean inString(String str, String... strs){
if (str != null && strs != null){
for (String s : strs){
if (str.equals(trim(s))){
return true;
}
}
}
return false;
}
/**
* 是否包含字符串
* @param str 验证字符串
* @param strs 字符串组
* @return 包含返回true
*/
public static boolean inStringIgnoreCase(String str, String... strs){
if (str != null && strs != null){
for (String s : strs){
if (str.equalsIgnoreCase(trim(s))){
return true;
}
}
}
return false;
}
/**
* 去除左右空格(包含中文空格)
* @param str
*/
public static String trim2(final String str) {
return str == null ? null : str.replaceAll("^[\\s| | ]*|[\\s| | ]*$", "");
}
/**
* 是否包含字符串
* @param str 验证字符串
* @param strs 字符串组
* @return 包含返回true
*/
public static boolean inString(String str, String... strs) {
if (str != null && strs != null) {
for (String s : strs) {
if (str.equals(trim(s))) {
return true;
}
}
}
return false;
}
/**
* 是否包含字符串
* @param str 验证字符串
* @param strs 字符串组
* @return 包含返回true
*/
public static boolean inStringIgnoreCase(String str, String... strs) {
if (str != null && strs != null) {
for (String s : strs) {
if (str.equalsIgnoreCase(trim(s))) {
return true;
}
}
}
return false;
}
/**
* 比较字符串,是否相等
*/
public static boolean equals(final CharSequence cs1, final CharSequence cs2) {
return Strings.CS.equals(cs1, cs2);
}
/**
* 比较字符串,是否相等(忽略大小写)
*/
public static boolean equalsIgnoreCase(final CharSequence cs1, final CharSequence cs2) {
return Strings.CI.equals(cs1, cs2);
}
/**
* 比较字符串,是否相等,只要有一个匹配就成立
*/
public static boolean equalsAny(final CharSequence string, final CharSequence... searchStrings) {
return Strings.CS.equalsAny(string, searchStrings);
}
/**
* 比较字符串,是否相等,只要有一个匹配就成立(忽略大小写)
*/
public static boolean equalsAnyIgnoreCase(final CharSequence string, final CharSequence... searchStrings) {
return Strings.CI.equalsAny(string, searchStrings);
}
/**
* 比较字符串,是否在字符串中包含
*/
public static boolean contains(final CharSequence seq, final CharSequence searchSeq) {
return Strings.CS.contains(seq, searchSeq);
}
/**
* 比较字符串,是否在字符串中包含,只要有一个匹配就成立
*/
public static boolean containsAny(final CharSequence cs, final CharSequence... searchCharSequences) {
return Strings.CS.containsAny(cs, searchCharSequences);
}
/**
* 比较字符串,是否在字符串中包含,只要有一个匹配就成立(忽略大小写)
*/
public static boolean containsAnyIgnoreCase(final CharSequence cs, final CharSequence... searchCharSequences) {
return Strings.CI.containsAny(cs, searchCharSequences);
}
/**
* 比较字符串,是否在字符串中包含(忽略大小写)
*/
public static boolean containsIgnoreCase(final CharSequence str, final CharSequence searchStr) {
return Strings.CI.contains(str, searchStr);
}
/**
* 比较字符串,前缀是否匹配
*/
public static boolean startsWith(final CharSequence str, final CharSequence prefix) {
return Strings.CS.startsWith(str, prefix);
}
/**
* 比较字符串,前缀是否匹配,只要有一个匹配就成立
*/
public static boolean startsWithAny(final CharSequence sequence, final CharSequence... searchStrings) {
return Strings.CS.startsWithAny(sequence, searchStrings);
}
/**
* 比较字符串,前缀是否匹配(忽略大小写)
*/
public static boolean startsWithIgnoreCase(final CharSequence str, final CharSequence prefix) {
return Strings.CI.startsWith(str, prefix);
}
/**
* 比较字符串,后缀是否匹配
*/
public static boolean endsWith(final CharSequence str, final CharSequence suffix) {
return Strings.CS.endsWith(str, suffix);
}
/**
* 比较字符串,后缀是否匹配,只要有一个匹配就成立
*/
public static boolean endsWithAny(final CharSequence sequence, final CharSequence... searchStrings) {
return Strings.CS.endsWithAny(sequence, searchStrings);
}
/**
* 比较字符串,后缀是否匹配(忽略大小写)
*/
public static boolean endsWithIgnoreCase(final CharSequence str, final CharSequence suffix) {
return Strings.CI.endsWith(str, suffix);
}
/**
* 替换字符串
*/
public static String replace(final String text, final String searchString, final String replacement) {
return Strings.CS.replace(text, searchString, replacement);
}
/**
* 替换字符串(忽略大小写)
*/
public static String replaceIgnoreCase(final String text, final String searchString, final String replacement) {
return Strings.CI.replace(text, searchString, replacement);
}
/**
* 去除左右空格(包含中文空格)
*/
public static String trim2(final String str) {
return str == null ? null : str.replaceAll("^[\\s| | ]*|[\\s| | ]*$", "");
}
/**
* 替换掉HTML标签方法
*/
public static String stripHtml(String html) {
if (isBlank(html)){
if (isBlank(html)) {
return EMPTY;
}
//html.replaceAll("\\&[a-zA-Z]{0,9};", "").replaceAll("<[^>]*>", "");
@@ -132,26 +243,22 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
Matcher m = p.matcher(html);
return m.replaceAll(EMPTY);
}
/**
* 替换为手机识别的HTML去掉样式及属性保留回车。
* @param html
* @return
*/
public static String toMobileHtml(String html){
if (isBlank(html)){
public static String toMobileHtml(String html) {
if (isBlank(html)) {
return EMPTY;
}
return html.replaceAll("<([a-z]+?)\\s+?.*?>", "<$1>");
}
/**
* 对txt进行HTML编码并将\n转换为&gt;br/&lt;、\t转换为&nbsp; &nbsp;
* @param txt
* @return
*/
public static String toHtml(String txt){
if (isBlank(txt)){
public static String toHtml(String txt) {
if (isBlank(txt)) {
return EMPTY;
}
return replace(replace(EncodeUtils.encodeHtml(trim(txt)), "\n", "<br/>"), "\t", "&nbsp; &nbsp; ");
@@ -159,12 +266,11 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/**
* 缩略字符串(不区分中英文字符)
* @param str 目标字符串
* @param str 目标字符串
* @param length 截取长度
* @return
*/
public static String abbr(String str, int length) {
if (isBlank(str)){
if (isBlank(str)) {
return EMPTY;
}
try {
@@ -181,25 +287,24 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
}
return sb.toString();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
return EMPTY;
}
// 缩略字符串替换Html正则表达式预编译
private static Pattern p1 = Pattern.compile("<([a-zA-Z]+)[^<>]*>");
private static final Pattern p1 = Pattern.compile("<([a-zA-Z]+)[^<>]*>");
/**
* 缩略字符串适应于与HTML标签的
* @param param 目标字符串
* @param param 目标字符串
* @param length 截取长度
* @return
*/
public static String htmlAbbr(String param, int length) {
if (isBlank(param)){
if (isBlank(param)) {
return EMPTY;
}
StringBuffer result = new StringBuffer();
StringBuilder result = new StringBuilder();
int n = 0;
char temp;
boolean isCode = false; // 是不是HTML代码
@@ -221,9 +326,8 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
n += String.valueOf(temp).getBytes("GBK").length;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
if (n <= length - 3) {
result.append(temp);
} else {
@@ -254,123 +358,138 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
}
return result.toString();
}
/**
* 首字母大写
*/
public static String cap(String str){
public static String cap(String str) {
return capitalize(str);
}
/**
* 首字母小写
*/
public static String uncap(String str){
public static String uncap(String str) {
return uncapitalize(str);
}
/**
* 驼峰命名法工具
* @return
* camelCase("hello_world") == "helloWorld"
* capCamelCase("hello_world") == "HelloWorld"
* uncamelCase("helloWorld") = "hello_world"
*/
public static String camelCase(String s) {
if (s == null) {
return null;
}
s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == UNDERLINE.charAt(0) || c == MINUS.charAt(0)) {
upperCase = i != 1; // 不允许第二个字符是大写
} else if (upperCase) {
sb.append(Character.toUpperCase(c));
upperCase = false;
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 驼峰命名法工具
* @return
* camelCase("hello_world") == "helloWorld"
* capCamelCase("hello_world") == "HelloWorld"
* uncamelCase("helloWorld") = "hello_world"
/**
* 转换为驼峰命名法
* @return camelCase("hello_world") == "helloWorld"
* capCamelCase("hello_world") == "HelloWorld"
* uncamelCase("helloWorld") = "hello_world"
*/
public static String capCamelCase(String s) {
if (s == null) {
return null;
}
s = camelCase(s);
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
/**
* 驼峰命名法工具
* @return
* camelCase("hello_world") == "helloWorld"
* capCamelCase("hello_world") == "HelloWorld"
* uncamelCase("helloWorld") = "hello_world"
public static String camelCase(String s) {
if (s == null) {
return null;
}
s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false;
for (int i = 0, j = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == UNDERLINE.charAt(0) || c == MINUS.charAt(0)) {
upperCase = j > 1; // 不允许第二个字符是大写
} else if (upperCase) {
sb.append(Character.toUpperCase(c));
upperCase = false;
j++;
} else {
sb.append(c);
j++;
}
}
return sb.toString();
}
/**
* 转换为驼峰命名法(首字母大写)
*
* @return camelCase("hello_world") == "helloWorld"
* capCamelCase("hello_world") == "HelloWorld"
* uncamelCase("helloWorld") = "hello_world"
*/
public static String uncamelCase(String s) {
if (s == null) {
return null;
}
StringBuilder sb = new StringBuilder();
boolean upperCase = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
boolean nextUpperCase = true;
if (i < (s.length() - 1)) {
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
}
if ((i > 0) && Character.isUpperCase(c)) {
if (!upperCase || !nextUpperCase) {
sb.append(UNDERLINE);
}
upperCase = true;
} else {
upperCase = false;
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
/**
* 转换为JS获取对象值生成三目运算返回结果
* @param objectString 对象串
* 例如row.user.id
* 返回:!row?'':!row.user?'':!row.user.id?'':row.user.id
*/
public static String jsGetVal(String objectString){
StringBuilder result = new StringBuilder();
StringBuilder val = new StringBuilder();
String[] vals = split(objectString, ".");
for (int i=0; i<vals.length; i++){
val.append("." + vals[i]);
result.append("!"+(val.substring(1))+"?'':");
}
result.append(val.substring(1));
return result.toString();
}
public static String capCamelCase(String s) {
return cap(camelCase(s));
}
/**
* 取消驼峰命名发
*
* @return camelCase("hello_world") == "helloWorld"
* capCamelCase("hello_world") == "HelloWorld"
* uncamelCase("helloWorld") = "hello_world"
*/
public static String uncamelCase(String s) {
if (s == null) {
return null;
}
StringBuilder sb = new StringBuilder();
boolean upperCase = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
boolean nextUpperCase = true;
if (i < (s.length() - 1)) {
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
}
if ((i > 0) && Character.isUpperCase(c)) {
if (!upperCase || !nextUpperCase) {
sb.append(UNDERLINE);
}
upperCase = true;
} else {
upperCase = false;
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
/**
* 转换为JS获取对象值生成三目运算返回结果
* @param objectString 对象串
* 例如row.user.id
* 返回:!row?'':!row.user?'':!row.user.id?'':row.user.id
*/
public static String jsGetVal(String objectString) {
StringBuilder result = new StringBuilder();
StringBuilder val = new StringBuilder();
String[] vals = split(objectString, ".");
for (String s : vals) {
val.append(".").append(s);
result.append("!").append(val.substring(1)).append("?'':");
}
result.append(val.substring(1));
return result.toString();
}
/**
* 获取随机字符串
* @param count
* @return
* @param count 长度
*/
public static String getRandomStr(int count) {
char[] codeSeq = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
return getRandom(count, codeSeq);
}
/**
* 获取随机数字
* @param count 长度
*/
public static String getRandomNum(int count) {
char[] codeSeq = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
return getRandom(count, codeSeq);
}
/**
* 获取随机字符串
*
* @param count 长度
* @param codeSeq 因子
*/
private static String getRandom(int count, char[] codeSeq) {
Random random = new Random();
StringBuilder s = new StringBuilder();
for (int i = 0; i < count; i++) {
@@ -380,31 +499,14 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
return s.toString();
}
/**
* 获取随机数字
* @param count
* @return
*/
public static String getRandomNum(int count) {
char[] codeSeq = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
Random random = new Random();
StringBuilder s = new StringBuilder();
for (int i = 0; i < count; i++) {
String r = String.valueOf(codeSeq[random.nextInt(codeSeq.length)]);
s.append(r);
}
return s.toString();
}
/**
* 获取树节点名字
* @param isShowCode 是否显示编码<br>
* true or 1显示在左侧(code) name<br>
* 2显示在右侧name (code)<br>
* false or null不显示编码name
* @param code 编码
* @param name 名称
* @return
* true or 1显示在左侧(code) name<br>
* 2显示在右侧name (code)<br>
* false or null不显示编码name
* @param code 编码
* @param name 名称
*/
public static String getTreeNodeName(String isShowCode, String code, String name) {
if ("true".equals(isShowCode) || "1".equals(isShowCode)) {

View File

@@ -10,9 +10,6 @@ public class WorkDayUtils {
/**
* 获取日期之间的天数
* @param d1
* @param d2
* @return
*/
public int getDaysBetween(java.util.Calendar d1, java.util.Calendar d2) {
if (d1.after(d2)) { // swap dates so that d1 is start and d2 is end
@@ -35,9 +32,6 @@ public class WorkDayUtils {
/**
* 获取工作日
* @param d1
* @param d2
* @return
*/
public int getWorkingDay(java.util.Calendar d1, java.util.Calendar d2) {
int result = -1;
@@ -72,8 +66,6 @@ public class WorkDayUtils {
/**
* 获取中文日期
* @param date
* @return
*/
public String getChineseWeek(Calendar date) {
final String[] dayNames = { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" };
@@ -84,8 +76,6 @@ public class WorkDayUtils {
/**
* 获得日期的下一个星期一的日期
* @param date
* @return
*/
public Calendar getNextMonday(Calendar date) {
Calendar result = null;
@@ -99,9 +89,6 @@ public class WorkDayUtils {
/**
* 获取休息日
* @param d1
* @param d2
* @return
*/
public int getHolidays(Calendar d1, Calendar d2) {
return this.getDaysBetween(d1, d2) - this.getWorkingDay(d1, d2);

View File

@@ -29,10 +29,7 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.io.IOException;
import java.io.Serial;
import java.lang.reflect.AnnotatedElement;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.*;
/**
* 封装 Jackson实现 JSON String 与 Java Object 互转
@@ -75,54 +72,28 @@ public class JsonMapper extends ObjectMapper {
* @author ThinkGem
*/
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")));
PropertiesUtils props = PropertiesUtils.getInstance();
// 设置默认语言环境
props.getPropertyIfNotBlank("lang.defaultLocale", (defaultLocale) -> {
this.setLocale(LocaleUtils.toLocale(defaultLocale));
});
// 设置默认时区
props.getPropertyIfNotBlank("lang.defaultTimeZone", (defaultTimeZone) -> {
this.setTimeZone(TimeZone.getTimeZone(defaultTimeZone));
});
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"};
private static class JeeSiteJsonSerializer extends JsonSerializer<Date> {
private final String pattern;
private 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));
}
}
}
}
private static class JeeSiteJsonDeserializer extends JsonDeserializer<Date> {
private final String pattern;
private 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());
}
}
}
@Override
public Object findSerializer(Annotated a) {
if (a instanceof AnnotatedMethod) {
AnnotatedElement m = a.getAnnotated();
JsonFormat jf = m.getAnnotation(JsonFormat.class);
if (jf != null && StringUtils.containsAnyIgnoreCase(jf.pattern(), pattern)) {
return new JeeSiteJsonSerializer(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) {
@@ -135,13 +106,13 @@ public class JsonMapper extends ObjectMapper {
@Override
public Object findDeserializer(Annotated a) {
if (a instanceof AnnotatedMethod) {
AnnotatedElement m = a.getAnnotated();
JsonFormat jf = m.getAnnotation(JsonFormat.class);
if (jf != null && StringUtils.containsAnyIgnoreCase(jf.pattern(), pattern)) {
return new JeeSiteJsonDeserializer(jf.pattern());
}
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) {
@@ -155,6 +126,38 @@ public class JsonMapper extends ObjectMapper {
return this;
}
public static final class JeeSiteJsonSerializer extends JsonSerializer<Date> {
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<Date> {
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

View File

@@ -4,8 +4,8 @@
*/
package com.jeesite.common.mapper;
import com.fasterxml.jackson.databind.JavaType;
import com.jeesite.common.io.PropertiesUtils;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,11 +38,17 @@ public class XmlMapper extends com.fasterxml.jackson.dataformat.xml.XmlMapper{
* 构造方法
*/
public XmlMapper() {
PropertiesUtils props = PropertiesUtils.getInstance();
// 设置默认语言环境
props.getPropertyIfNotBlank("lang.defaultLocale", (defaultLocale) -> {
this.setLocale(LocaleUtils.toLocale(defaultLocale));
});
// 设置默认时区
props.getPropertyIfNotBlank("lang.defaultTimeZone", (defaultTimeZone) -> {
this.setTimeZone(TimeZone.getTimeZone(defaultTimeZone));
});
// Spring ObjectMapper 初始化配置,支持 @JsonView
new Jackson2ObjectMapperBuilder().configure(this);
// 设置默认时区
this.setTimeZone(TimeZone.getTimeZone(PropertiesUtils.getInstance()
.getProperty("lang.defaultTimeZone", "GMT+08:00")));
}
/**
@@ -52,7 +58,7 @@ public class XmlMapper extends com.fasterxml.jackson.dataformat.xml.XmlMapper{
try {
return this.writeValueAsString(object);
} catch (IOException e) {
logger.warn("write to xml string error:" + object, e);
logger.warn("write to xml string error: {}", object, e);
return null;
}
}
@@ -64,14 +70,14 @@ public class XmlMapper extends com.fasterxml.jackson.dataformat.xml.XmlMapper{
try {
return this.writerWithView(jsonView).writeValueAsString(object);
} catch (IOException e) {
logger.warn("write to xml string error:" + object, e);
logger.warn("write to xml string error: {}", object, e);
return null;
}
}
/**
* 反序列化POJO或简单Collection如List<String>.
* @see #fromJson(String, JavaType)
* @see #fromXml(String, Class)
*/
public <T> T fromXmlString(String xmlString, Class<T> clazz) {
if (StringUtils.isEmpty(xmlString) || "<CLOB>".equals(xmlString)) {
@@ -80,7 +86,7 @@ public class XmlMapper extends com.fasterxml.jackson.dataformat.xml.XmlMapper{
try {
return this.readValue(xmlString, clazz);
} catch (IOException e) {
logger.warn("parse xml string error:" + xmlString, e);
logger.warn("parse xml string error: {}", xmlString, e);
return null;
}
}

View File

@@ -21,7 +21,7 @@ import java.util.List;
*/
public class VideoUtils {
private static final Logger log = LoggerFactory.getLogger(VideoUtils.class);
private static final Logger logger = LoggerFactory.getLogger(VideoUtils.class);
private static String ffmpegFile; // ffmpeg.exe所放的路径
private static String mencoderFile; // mencoder.exe所放的路径
private static String qtFaststartFile; // qt-faststart.exe所放的路径
@@ -111,10 +111,10 @@ public class VideoUtils {
}
} catch (Exception e) {
statusTemp = false;
log.error("视频剪切图片失败", e);
logger.error("视频剪切图片失败", e);
}
}
log.debug("视频剪切图片" + (statusTemp ? "成功" : "失败") + ",用时:" + TimeUtils.formatTime(System.currentTimeMillis() - startTime));
logger.debug("视频剪切图片" + (statusTemp ? "成功" : "失败") + ",用时:" + TimeUtils.formatTime(System.currentTimeMillis() - startTime));
return statusTemp;
}
@@ -128,31 +128,30 @@ public class VideoUtils {
int type = checkContentType();
String tempFile = outputFile + ".tmp";
if (statusTemp && type == 0) {
log.debug("使用ffmpage进行视频转换");
logger.debug("使用ffmpage进行视频转换");
statusTemp = processFfmpeg(inputFile, tempFile);
} else if (statusTemp && type == 1) {
log.debug("使用mencoder进行视频转换");
logger.debug("使用mencoder进行视频转换");
statusTemp = processMencoder(inputFile, tempFile);
}
if (statusTemp){
log.debug("将mp4视频的元数据信息转到视频第一帧");
logger.debug("将mp4视频的元数据信息转到视频第一帧");
statusTemp = processQtFaststart(tempFile, outputFile);
}
log.debug("删除临时文件");
logger.debug("删除临时文件");
FileUtils.deleteFile(tempFile);
log.debug("视频转换" + (statusTemp ? "成功" : "失败") + ",用时:" + TimeUtils.formatTime(System.currentTimeMillis() - startTime));
logger.debug("视频转换{},用时:{}", statusTemp ? "成功" : "失败", TimeUtils.formatTime(System.currentTimeMillis() - startTime));
return statusTemp;
}
/**
* 检查文件是否存在
* @param inputFile
* @return boolean
*/
public boolean checkfile(String inputFile) {
File file = new File(inputFile);
if (!file.isFile() || !file.exists()) {
log.warn("文件不存在!");
logger.warn("文件不存在!");
return false;
}
return true;
@@ -160,8 +159,7 @@ public class VideoUtils {
/**
* ffmpeg 截取缩略图
* @param inputFile
* @return boolean
* @return boolean
*/
public boolean processFfmpegCutpic(String inputFile, String outputFile) {
List<String> command = new java.util.ArrayList<String>();
@@ -274,23 +272,22 @@ public class VideoUtils {
/**
* 执行命令
* @param command
* @return boolean
* @return boolean
*/
private boolean process(List<String> command) {
try {
log.debug(StringUtils.join(command, StringUtils.SPACE));
logger.debug(StringUtils.join(command, StringUtils.SPACE));
// Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
Process process = Runtime.getRuntime().exec(command.toArray(new String[command.size()]));
Process process = Runtime.getRuntime().exec(command.toArray(new String[0]));
new PrintErrorReader(process.getErrorStream()).start();
new PrintInputStream(process.getInputStream()).start();
process.waitFor();
return true;
} catch (Exception e) {
if (StringUtils.contains(e.getMessage(), "CreateProcess error=2")){
log.error("缺少视频转换工具请配置video.ffmpegFile相关参数。" + e.getMessage());
logger.error("缺少视频转换工具请配置video.ffmpegFile相关参数。{}", e.getMessage());
}else{
log.error(e.getMessage(), e);
logger.error(e.getMessage(), e);
}
return false;
}
@@ -406,10 +403,10 @@ public class VideoUtils {
BufferedReader br = new BufferedReader(new InputStreamReader(__is));
String line = null;
while ((line = br.readLine()) != null) {
log.debug(line);
logger.debug(line);
}
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
}
}
@@ -427,10 +424,10 @@ public class VideoUtils {
BufferedReader br = new BufferedReader(new InputStreamReader(__is));
String line = null;
while ((line = br.readLine()) != null) {
log.error(line);
logger.error(line);
}
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
}
}

View File

@@ -13,6 +13,7 @@ import com.jeesite.common.io.PropertiesUtils;
/**
* 发送电子邮件
* @author ThinkGem
*/
public class EmailUtils {
@@ -20,7 +21,6 @@ public class EmailUtils {
/**
* 发送邮件
* @return
*/
public static boolean send(String toAddress, String subject, String content) {
PropertiesUtils props = PropertiesUtils.getInstance();
@@ -51,7 +51,6 @@ public class EmailUtils {
* @param toAddress 接收地址
* @param subject 标题
* @param content 内容
* @return
*/
public static boolean send(String fromAddress, String fromPassword, String fromHostName, Integer smtpPort,
String sslOnConnect, String sslSmtpPort, String toAddress, String subject, String content) {

View File

@@ -1,3 +1,7 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.common.msg;
import org.slf4j.Logger;
@@ -5,6 +9,7 @@ import org.slf4j.LoggerFactory;
/**
* 发送短信请实现send方法
* @author ThinkGem
*/
public class SmsUtils {

View File

@@ -1,3 +1,7 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.common.network;
import com.jeesite.common.codec.EncodeUtils;
@@ -6,12 +10,15 @@ import com.jeesite.common.lang.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
/**
* IP 地址工具
* @author ThinkGem
*/
public class IpUtils {
/**
* 获取客户端IP地址
* @param request
* @return
* @param request 请求对象
*/
public static String getRemoteAddr(HttpServletRequest request) {
if (request == null) {
@@ -38,8 +45,7 @@ public class IpUtils {
/**
* 是否是本地地址
* @param ip
* @return
* @param ip 地址
*/
public static boolean isLocalAddr(String ip){
return StringUtils.inString(ip, "127.0.0.1", "0:0:0:0:0:0:0:1");
@@ -47,14 +53,12 @@ public class IpUtils {
/**
* 判断IP地址为内网IP还是公网IP
*
* <br>
* tcp/ip协议中专门保留了三个IP地址区域作为私有地址其地址范围如下
* 10.0.0.0/810.0.0.010.255.255.255
* 172.16.0.0/12172.16.0.0172.31.255.255
* 192.168.0.0/16192.168.0.0192.168.255.255
*
* @param ip
* @return
* @param ip 地址
*/
public static boolean isInternalAddr(String ip) {

View File

@@ -4,18 +4,22 @@
*/
package com.jeesite.common.network;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* MAC地址工具
*
* @author ThinkGem
* @version 2014-6-18
*/
public class MacUtils {
private static final Logger logger = LoggerFactory.getLogger(MacUtils.class);
/**
* 获取当前操作系统名称. return 操作系统名称 例如:windows,Linux,Unix等.
*/
@@ -25,7 +29,6 @@ public class MacUtils {
/**
* 获取Unix网卡的mac地址.
*
* @return mac地址
*/
public static String getUnixMACAddress() {
@@ -33,39 +36,31 @@ public class MacUtils {
BufferedReader bufferedReader = null;
Process process = null;
try {
/**
* Unix下的命令一般取eth0作为本地主网卡 显示信息中包含有mac地址信息
*/
// Unix下的命令一般取eth0作为本地主网卡 显示信息中包含有mac地址信息
process = Runtime.getRuntime().exec("ifconfig eth0");
bufferedReader = new BufferedReader(new InputStreamReader(
process.getInputStream()));
String line = null;
int index = -1;
while ((line = bufferedReader.readLine()) != null) {
/**
* 寻找标示字符串[hwaddr]
*/
// 寻找标示字符串[hwaddr]
index = line.toLowerCase().indexOf("hwaddr");
/**
* 找到了
*/
// 找到了
if (index != -1) {
/**
* 取出mac地址并去除2边空格
*/
// 取出mac地址并去除2边空格
mac = line.substring(index + "hwaddr".length() + 1).trim();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
logger.error(e.getMessage());
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e1) {
e1.printStackTrace();
// ignore
}
bufferedReader = null;
process = null;
@@ -84,9 +79,7 @@ public class MacUtils {
BufferedReader bufferedReader = null;
Process process = null;
try {
/**
* linux下的命令一般取eth0作为本地主网卡 显示信息中包含有mac地址信息
*/
// linux下的命令一般取eth0作为本地主网卡 显示信息中包含有mac地址信息
process = Runtime.getRuntime().exec("ifconfig eth0");
bufferedReader = new BufferedReader(new InputStreamReader(
process.getInputStream()));
@@ -94,26 +87,22 @@ public class MacUtils {
int index = -1;
while ((line = bufferedReader.readLine()) != null) {
index = line.toLowerCase().indexOf("硬件地址");
/**
* 找到了
*/
// 找到了
if (index != -1) {
/**
* 取出mac地址并去除2边空格
*/
// 取出mac地址并去除2边空格
mac = line.substring(index + 4).trim();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
logger.error(e.getMessage());
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e1) {
e1.printStackTrace();
// ignore
}
bufferedReader = null;
process = null;
@@ -129,7 +118,6 @@ public class MacUtils {
/**
* 获取widnows网卡的mac地址.
*
* @return mac地址
*/
public static String getWindowsMACAddress() {
@@ -137,37 +125,31 @@ public class MacUtils {
BufferedReader bufferedReader = null;
Process process = null;
try {
/**
* windows下的命令显示信息中包含有mac地址信息
*/
// windows下的命令显示信息中包含有mac地址信息
process = Runtime.getRuntime().exec("ipconfig /all");
bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = null;
int index = -1;
while ((line = bufferedReader.readLine()) != null) {
/**
* 寻找标示字符串[physical address 或 物理地址]
*/
// 寻找标示字符串[physical address 或 物理地址]
if (line.split("-").length == 6){
index = line.indexOf(":");
if (index != -1) {
/**
* 取出mac地址并去除2边空格
*/
// 取出mac地址并去除2边空格
mac = line.substring(index + 1).trim();
}
break;
}
}
} catch (IOException e) {
e.printStackTrace();
logger.error(e.getMessage());
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e1) {
e1.printStackTrace();
// ignore
}
bufferedReader = null;
process = null;

View File

@@ -21,7 +21,7 @@ import java.util.Map;
/**
* 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
* @author calvin、ThinkGem
* @version 2023-2-6
* @version 2025-08-08
*/
@SuppressWarnings("rawtypes")
public class ReflectUtils {
@@ -112,7 +112,7 @@ public class ReflectUtils {
if (field == null) {
//throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
if (obj != null) {
logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
logger.debug("在 [{}] 中,没有找到 [{}] 字段 ", obj.getClass(), fieldName);
}
return null;
}
@@ -132,7 +132,7 @@ public class ReflectUtils {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
//throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
logger.debug("在 [{}] 中,没有找到 [{}] 字段 ", obj.getClass(), fieldName);
return;
}
try {
@@ -157,9 +157,7 @@ public class ReflectUtils {
Method method = getAccessibleMethod(obj, methodName, parameterTypes);
if (method == null) {
//throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
if (obj != null) {
logger.debug("在 [" + (obj.getClass() == Class.class ? obj : obj.getClass()) + "] 中,没有找到 [" + methodName + "] 方法 ");
}
logger.debug("在 [{}] 中,没有找到 [{}] 方法 ", obj.getClass() == Class.class ? obj : obj.getClass(), methodName);
return null;
}
try {
@@ -182,7 +180,7 @@ public class ReflectUtils {
if (method == null) {
// 如果为空不报错,直接返回空。 throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
if (obj != null) {
logger.debug("在 [" + (obj.getClass() == Class.class ? obj : obj.getClass()) + "] 中,没有找到 [" + methodName + "] 方法 ");
logger.debug("在 [{}] 中,没有找到 [{}] 方法 ", obj.getClass() == Class.class ? obj : obj.getClass(), methodName);
}
return null;
}
@@ -252,7 +250,7 @@ public class ReflectUtils {
args[i] = DateUtil.getJavaDate((Double) args[i]);
}
}else{
System.out.println(cs[i] + " " + args[i]);
logger.debug("class: {}, args: {}", cs[i], args[i]);
}
}
}
@@ -405,17 +403,16 @@ public class ReflectUtils {
public static Class getClassGenricType(final Class clazz, final int index) {
Type genType = clazz.getGenericSuperclass();
if (!(genType instanceof ParameterizedType)) {
logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType");
logger.debug("{}'s superclass not ParameterizedType", clazz.getSimpleName());
return Object.class;
}
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
if (index >= params.length || index < 0) {
logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+ params.length);
logger.debug("Index: {}, Size of {}'s Parameterized Type: {}", index, clazz.getSimpleName(), params.length);
return Object.class;
}
if (!(params[index] instanceof Class)) {
logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
logger.debug("{} not set the actual class on superclass generic parameter", clazz.getSimpleName());
return Object.class;
}
return (Class) params[index];
@@ -429,7 +426,7 @@ public class ReflectUtils {
throw new RuntimeException("Instance must not be null");
}
Class clazz = instance.getClass();
if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
if (clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && !Object.class.equals(superClass)) {
return superClass;

View File

@@ -1,3 +1,7 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.common.text;
import net.sourceforge.pinyin4j.PinyinHelper;
@@ -6,6 +10,8 @@ import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.regex.Pattern;
@@ -14,10 +20,12 @@ import java.util.regex.Pattern;
* @author ThinkGem
*/
public class PinyinUtils {
private static final Logger logger = LoggerFactory.getLogger(PinyinUtils.class);
private static class Static{
private static Pattern idPatt = Pattern.compile("\\W");
private static HanyuPinyinOutputFormat defaultFormat;
private static final Pattern idPat = Pattern.compile("\\W");
private static final HanyuPinyinOutputFormat defaultFormat;
static{
defaultFormat = new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
@@ -46,28 +54,28 @@ public class PinyinUtils {
if (chinese == null){
return null;
}
StringBuffer pybf = new StringBuffer();
StringBuilder sb = new StringBuilder();
char[] arr = chinese.toCharArray();
for (int i = 0; i < arr.length; i++) {
if (arr[i] > 128) {
for (char c : arr) {
if (c > 128) {
try {
String[] temp = PinyinHelper.toHanyuPinyinStringArray(arr[i], Static.defaultFormat);
String[] temp = PinyinHelper.toHanyuPinyinStringArray(c, Static.defaultFormat);
if (temp != null && temp.length > 0) {
pybf.append(temp[0].charAt(0));
}else{
pybf.append(String.valueOf(arr[i]));
sb.append(temp[0].charAt(0));
} else {
sb.append(String.valueOf(c));
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
} else {
pybf.append(arr[i]);
sb.append(c);
}
}
if (isId){
return Static.idPatt.matcher(pybf.toString()).replaceAll("").trim();
return Static.idPat.matcher(sb.toString()).replaceAll("").trim();
}
return pybf.toString();
return sb.toString();
}
/**
@@ -90,28 +98,28 @@ public class PinyinUtils {
if (chinese == null){
return null;
}
StringBuffer pybf = new StringBuffer();
StringBuilder sb = new StringBuilder();
char[] arr = chinese.toCharArray();
for (int i = 0; i < arr.length; i++) {
if (arr[i] > 128) {
for (char c : arr) {
if (c > 128) {
try {
String[] ss = PinyinHelper.toHanyuPinyinStringArray(arr[i], Static.defaultFormat);
if (ss != null && ss.length > 0){
pybf.append(ss[0]);
}else{
pybf.append(String.valueOf(arr[i]));
String[] ss = PinyinHelper.toHanyuPinyinStringArray(c, Static.defaultFormat);
if (ss != null && ss.length > 0) {
sb.append(ss[0]);
} else {
sb.append(String.valueOf(c));
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
} else {
pybf.append(arr[i]);
sb.append(c);
}
}
if (isId){
return Static.idPatt.matcher(pybf.toString()).replaceAll("").trim();
return Static.idPat.matcher(sb.toString()).replaceAll("").trim();
}
return pybf.toString();
return sb.toString();
}
/**
@@ -143,7 +151,7 @@ public class PinyinUtils {
if (input == null){
return null;
}
char c[] = input.toCharArray();
char[] c = input.toCharArray();
for (int i = 0; i < c.length; i++) {
if (c[i] == '\u3000') {
c[i] = ' ';

View File

@@ -27,11 +27,11 @@ public class IdcardUtils {
"62", "63", "64", "65", "71", "81", "82", "91" };
/** 每位加权因子 */
public static final int power[] = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9,
public static final int[] power = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9,
10, 5, 8, 4, 2 };
/** 第18位校检码 */
public static final String verifyCode[] = { "1", "0", "X", "9", "8", "7",
public static final String[] verifyCode = { "1", "0", "X", "9", "8", "7",
"6", "5", "4", "3", "2" };
/** 最低年限 */
public static final int MIN = 1930;
@@ -116,9 +116,7 @@ public class IdcardUtils {
/**
* 将15位身份证号码转换为18位
*
* @param idCard
* 15位身份编码
* @param idCard 15位身份编码
* @return 18位身份编码
*/
public static String conver15CardTo18(String idCard) {

View File

@@ -47,24 +47,31 @@ public class LocaleUtils {
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){
context = new TimeZoneAwareLocaleContext() {
@Override
public Locale getLocale() {
return Locale.getDefault();
}
@Override
public TimeZone getTimeZone() {
return TimeZone.getDefault();
}
};
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;
}

View File

@@ -21,8 +21,7 @@ public class ThreadUtils {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
// Ignore.
return;
Thread.currentThread().interrupt();
}
}
@@ -33,8 +32,7 @@ public class ThreadUtils {
try {
Thread.sleep(unit.toMillis(duration));
} catch (InterruptedException e) {
// Ignore.
return;
Thread.currentThread().interrupt();
}
}

View File

@@ -509,7 +509,7 @@ public class ExcelExport implements Closeable{
cell.setCellStyle(style);
// }
} catch (Exception ex) {
log.info("Set cell value ["+row.getRowNum()+","+column+"] error: " + ex.toString());
log.info("Set cell value [{},{}] error: {}", row.getRowNum(), column, ex.toString());
cell.setCellValue(ObjectUtils.toString(val));
}
return cell;
@@ -581,7 +581,7 @@ public class ExcelExport implements Closeable{
this.addCell(row, colunm++, val, ef.align(), ef.fieldType(), ef.dataFormat());
sb.append(val + ", ");
}
log.debug("Write success: ["+row.getRowNum()+"] "+sb.toString());
log.debug("Write success: [{}] {}", row.getRowNum(), sb.toString());
}
return this;
}
@@ -633,7 +633,7 @@ public class ExcelExport implements Closeable{
// try {
// this.close();
// } catch (Exception e) {
// e.printStackTrace();
// log.error(e.getMessage(), e);
// }
// return this;
// }
@@ -647,7 +647,7 @@ public class ExcelExport implements Closeable{
try {
wb.close();
} catch (IOException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
}
}

View File

@@ -235,7 +235,6 @@ public class ExcelImport implements Closeable {
/**
* 获取行对象
* @param rownum
* @return 返回Row对象如果空行返回null
*/
public Row getRow(int rownum){
@@ -262,7 +261,6 @@ public class ExcelImport implements Closeable {
/**
* 获取数据行号
* @return
*/
public int getDataRowNum(){
return headerNum;
@@ -270,7 +268,6 @@ public class ExcelImport implements Closeable {
/**
* 获取最后一个数据行号
* @return
*/
public int getLastDataRowNum(){
//return this.sheet.getLastRowNum() + headerNum;
@@ -279,7 +276,6 @@ public class ExcelImport implements Closeable {
/**
* 获取最后一个列号
* @return
*/
public int getLastCellNum(){
Row row = this.getRow(headerNum);
@@ -305,7 +301,7 @@ public class ExcelImport implements Closeable {
if (DateUtil.isCellDateFormatted(cell)) {
val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换
}else{
if ((Double) val % 1 > 0){
if (Math.abs((double)val % 1) > 1E-10){
val = new DecimalFormat("0.00").format(val);
}else{
val = new DecimalFormat("0").format(val);
@@ -465,7 +461,7 @@ public class ExcelImport implements Closeable {
//val = DictUtils.getDictValue(val.toString(), ef.dictType(), "");
//log.debug("Dictionary type value: ["+i+","+colunm+"] " + val);
} catch (Exception ex) {
log.info("Get cell value ["+i+","+column+"] error: " + ex.toString());
log.info("Get cell value [{},{}] error: {}", i, column, ex.toString());
val = null;
}
}
@@ -542,22 +538,20 @@ public class ExcelImport implements Closeable {
ReflectUtils.invokeSetter(e, ef.attrName(), val);
}else{
if (os[1] instanceof Field){
//ReflectUtils.invokeSetter(e, ((Field)os[1]).getName(), val);
((Field)os[1]).set(e, val);
ReflectUtils.invokeSetter(e, ((Field)os[1]).getName(), val);
}else if (os[1] instanceof Method){
//String mthodName = ((Method)os[1]).getName();
//if ("get".equals(mthodName.substring(0, 3))){
// mthodName = "set"+StringUtils.substringAfter(mthodName, "get");
//}
//ReflectUtils.invokeMethod(e, mthodName, new Class[] {valType}, new Object[] {val});
((Method)os[1]).invoke(e, val);
String mthodName = ((Method)os[1]).getName();
if ("get".equals(mthodName.substring(0, 3))){
mthodName = "set"+mthodName.substring(3);
}
ReflectUtils.invokeMethodByAsm(e, mthodName, val);
}
}
}
sb.append(val+", ");
}
dataList.add(e);
log.debug("Read success: ["+i+"] "+sb.toString());
log.debug("Read success: [{}] {}", i, sb.toString());
}
return dataList;
}
@@ -577,7 +571,7 @@ public class ExcelImport implements Closeable {
try {
wb.close();
} catch (IOException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
}
}

View File

@@ -8,7 +8,6 @@ import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
/**
* BigDecimal类型转换
* @author ThinkGem

View File

@@ -9,7 +9,6 @@ import java.text.NumberFormat;
import org.apache.commons.lang3.StringUtils;
/**
* 金额类型转换(保留两位)
* @author ThinkGem

View File

@@ -59,8 +59,6 @@ public class BookMark {
/**
* 构造函数
* @param ctBookmark
* @param para
*/
public BookMark(CTBookmark ctBookmark, XWPFParagraph para) {
this._ctBookmark = ctBookmark;
@@ -72,9 +70,6 @@ public class BookMark {
/**
* 构造函数,用于表格中的标签
* @param ctBookmark
* @param para
* @param tableCell
*/
public BookMark(CTBookmark ctBookmark, XWPFParagraph para, XWPFTableCell tableCell) {
this(ctBookmark, para);

View File

@@ -1,10 +1,17 @@
package com.jeesite.common.utils.word;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import com.jeesite.common.io.ResourceUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHeight;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTrPr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import java.io.*;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Iterator;
@@ -12,22 +19,6 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHeight;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTrPr;
import org.w3c.dom.Node;
import com.jeesite.common.io.ResourceUtils;
/**
* 使用POI,进行Word相关的操作
* @author xuyu
@@ -38,6 +29,8 @@ import com.jeesite.common.io.ResourceUtils;
*/
public class WordExport {
private static final Logger log = LoggerFactory.getLogger(WordExport.class);
/** 内部使用的文档对象 **/
private XWPFDocument document;
@@ -57,7 +50,7 @@ public class WordExport {
}
bookMarks = new BookMarks(document);
} catch (IOException | InvalidFormatException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
}
}
@@ -224,10 +217,8 @@ public class WordExport {
try {
fos = new FileOutputStream(newFile);
this.document.write(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
} finally {
try {
if (fos != null){
@@ -237,7 +228,7 @@ public class WordExport {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
}
}
}

View File

@@ -15,12 +15,10 @@ import jakarta.servlet.http.HttpServletResponse;
*/
public class ResultUtils {
private static final boolean isDefaultResult = PropertiesUtils.getInstance()
.getPropertyToBoolean("web.isDefaultResult", "false");
private static final String resultParamName = PropertiesUtils.getInstance()
.getProperty("web.resultParamName", "__data");
private static final String headerParamName = PropertiesUtils.getInstance()
.getProperty("web.headerParamName", "x-data");
private static final PropertiesUtils props = PropertiesUtils.getInstance();
private static final boolean isDefaultResult = props.getPropertyToBoolean("web.isDefaultResult", "false");
private static final String resultParamName = props.getProperty("web.resultParamName", "__data");
private static final String headerParamName = props.getProperty("web.headerParamName", "x-data");
/**
* 设置 web.isResult 参数可强制全局使用统一结果输出,否则,传递 __data=true 参数,或 x-data 请求头为 true 时启用

View File

@@ -16,6 +16,8 @@ import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.RequestContextHolder;
@@ -32,6 +34,8 @@ import java.util.Map.Entry;
*/
public class ServletUtils {
private static final Logger logger = LoggerFactory.getLogger(ServletUtils.class);
public static final String EXT_PARAMS_PREFIX = "param_"; // 扩展参数前缀
// 定义静态文件后缀静态文件排除URI地址
@@ -107,7 +111,7 @@ public class ServletUtils {
}
}
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
}
@@ -121,7 +125,7 @@ public class ServletUtils {
throw new Exception("检测到“application.yml”中没有配置“web.staticFile”属性。配置示例\n#静态文件后缀\nweb.staticFile=" +
".css,.js,.map,.png,.jpg,.gif,.jpeg,.webp,.bmp,.ico,.swf,.psd,.htc,.crx,.xpi,.exe,.ipa,.apk,.otf,.eot,.svg,.ttf,.woff,.woff2");
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
}
if (StringUtils.containsAny(uri, STATIC_FILE_EXCLUDE_URI)) {
@@ -363,7 +367,7 @@ public class ServletUtils {
}
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
return null;
}

View File

@@ -19,8 +19,6 @@ public class UserAgentUtils {
/**
* 获取用户代理对象
* @param request
* @return
*/
public static UserAgent getUserAgent(HttpServletRequest request){
return UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
@@ -28,8 +26,6 @@ public class UserAgentUtils {
/**
* 获取设备类型
* @param request
* @return
*/
public static DeviceType getDeviceType(HttpServletRequest request){
return getUserAgent(request).getOperatingSystem().getDeviceType();
@@ -37,8 +33,6 @@ public class UserAgentUtils {
/**
* 是否是PC
* @param request
* @return
*/
public static boolean isComputer(HttpServletRequest request){
return DeviceType.COMPUTER.equals(getDeviceType(request));
@@ -46,8 +40,6 @@ public class UserAgentUtils {
/**
* 是否是手机
* @param request
* @return
*/
public static boolean isMobile(HttpServletRequest request){
return DeviceType.MOBILE.equals(getDeviceType(request));
@@ -55,8 +47,6 @@ public class UserAgentUtils {
/**
* 是否是平板
* @param request
* @return
*/
public static boolean isTablet(HttpServletRequest request){
return DeviceType.TABLET.equals(getDeviceType(request));
@@ -64,8 +54,6 @@ public class UserAgentUtils {
/**
* 是否是手机和平板
* @param request
* @return
*/
public static boolean isMobileOrTablet(HttpServletRequest request){
DeviceType deviceType = getDeviceType(request);
@@ -74,8 +62,6 @@ public class UserAgentUtils {
/**
* 获取浏览类型
* @param request
* @return
*/
public static Browser getBrowser(HttpServletRequest request){
return getUserAgent(request).getBrowser();
@@ -83,8 +69,6 @@ public class UserAgentUtils {
/**
* 是否IE版本是否小于等于IE8
* @param request
* @return
*/
public static boolean isLteIE8(HttpServletRequest request){
Browser browser = getBrowser(request);

View File

@@ -1,7 +0,0 @@
/*! Respond.js v1.4.2: min/max-width media query polyfill Copyright 2014 Scott Jehl Licensed under MIT http://j.mp/respondjs */
//https://oss.maxcdn.com/respond/1.4.2/respond.min.js
!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='&shy;<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){v(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},g=function(a){return a.replace(c.regex.minmaxwh,"").match(c.regex.other)};if(c.ajax=f,c.queue=d,c.unsupportedmq=g,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,comments:/\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,maxw:/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,minmaxwh:/\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,other:/\([^\)]*\)/g},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var h,i,j,k=a.document,l=k.documentElement,m=[],n=[],o=[],p={},q=30,r=k.getElementsByTagName("head")[0]||l,s=k.getElementsByTagName("base")[0],t=r.getElementsByTagName("link"),u=function(){var a,b=k.createElement("div"),c=k.body,d=l.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=k.createElement("body"),c.style.background="none"),l.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&l.insertBefore(c,l.firstChild),a=b.offsetWidth,f?l.removeChild(c):c.removeChild(b),l.style.fontSize=d,e&&(c.style.fontSize=e),a=j=parseFloat(a)},v=function(b){var c="clientWidth",d=l[c],e="CSS1Compat"===k.compatMode&&d||k.body[c]||d,f={},g=t[t.length-1],p=(new Date).getTime();if(b&&h&&q>p-h)return a.clearTimeout(i),i=a.setTimeout(v,q),void 0;h=p;for(var s in m)if(m.hasOwnProperty(s)){var w=m[s],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?j||u():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?j||u():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(n[w.rules]))}for(var C in o)o.hasOwnProperty(C)&&o[C]&&o[C].parentNode===r&&r.removeChild(o[C]);o.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=k.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,r.insertBefore(E,g.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(k.createTextNode(F)),o.push(E)}},w=function(a,b,d){var e=a.replace(c.regex.comments,"").replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},i=!f&&d;b.length&&(b+="/"),i&&(f=1);for(var j=0;f>j;j++){var k,l,o,p;i?(k=d,n.push(h(a))):(k=e[j].match(c.regex.findStyles)&&RegExp.$1,n.push(RegExp.$2&&h(RegExp.$2))),o=k.split(","),p=o.length;for(var q=0;p>q;q++)l=o[q],g(l)||m.push({media:l.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:n.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}v()},x=function(){if(d.length){var b=d.shift();f(b.href,function(c){w(c,b.href,b.media),p[b.href]=!0,a.setTimeout(function(){x()},0)})}},y=function(){for(var b=0;b<t.length;b++){var c=t[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!p[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(w(c.styleSheet.rawCssText,e,f),p[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!s||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}x()};y(),c.update=y,c.getEmValue=u,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this);
/*! @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed */
//https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js
!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document);

View File

@@ -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];

View File

@@ -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];

View File

@@ -16,8 +16,8 @@ span{display:block;font-size:12px;line-height:12px;}
.clean{clear:both;}
</style></head><body>
<h1>浏览器版本过低,是时候升级您的浏览器了</h1>
<p>本系统 <a href="http://jeesite.com">JeeSite</a> 支持 IE9 及以上版本及其他所有现代浏览器,如:谷歌浏览器、火狐浏览器、国产浏览器 等。</p>
<p>您正在使用 Internet Explorer 的过期版本IE6、IE7、IE8 内核的浏览器)。这意味着在升级浏览器前,您将无法继续访问。</p>
<p>本系统 <a href="http://jeesite.com">JeeSite</a> 支持 IE10 及以上版本及其他所有现代浏览器,如:谷歌浏览器、火狐浏览器、国产浏览器 等。</p>
<p>您正在使用 Internet Explorer 的过期版本IE6、IE7、IE8、IE9 内核的浏览器)。这意味着在升级浏览器前,您将无法继续访问。</p>
<hr>
<h2>为什么会出现这个页面?</h2>
<p>如果您不知道升级浏览器是什么意思请请教一些熟练电脑操作的朋友。如果您使用的不是Internet Explorer而是360、QQ、搜狗等双核浏览器出现这个页面可能是您切换到了兼容模式<strong style="color:#f00;">切换到极速模式</strong>下,如果还不行请升级至最新版浏览器。</p>
@@ -29,10 +29,10 @@ span{display:block;font-size:12px;line-height:12px;}
<h2>您可以选择更先进的浏览器</h2>
<p>推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问 <b id="url"></b>即可。</p>
<div class="browser"><ul>
<li><img src="img/chrome360.jpg"><a href="http://chrome.360.cn/"> 360极速浏览器<span>360 Chrome</span></a></li>
<li><img src="img/chrome.jpg"><a href="http://www.google.cn/intl/zh-CN/chrome/browser/desktop/index.html"> 谷歌浏览器<span>Google Chrome</span></a></li>
<li><img src="img/firefox.jpg"><a href="http://www.firefox.com.cn/download/"> 火狐浏览器<span>Mozilla Firefox</span></a></li>
<li><img src="img/edge.png"><a href="https://www.microsoft.com/zh-cn/edge"> Edge 浏览器<span>Microsoft Edge</span></a></li>
<li><img src="img/chrome.jpg"><a href="http://www.google.cn/intl/zh-CN/chrome/browser/desktop/index.html"> 谷歌浏览器<span>Google Chrome</span></a></li>
<li><img src="img/chrome360.jpg"><a href="http://chrome.360.cn/"> 360极速浏览器<span>360 Chrome</span></a></li>
<li><img src="img/firefox.jpg"><a href="https://www.firefox.com/zh-CN/"> 火狐浏览器<span>Mozilla Firefox</span></a></li>
</ul><div class="clean"></div></div>
<hr><br/>
<script>

View File

@@ -48,8 +48,6 @@ public class CodeStatistic {
/**
* 获得目录下的文件和子目录下的文件
* @param f
* @return
*/
public static ArrayList<File> getFile(File f) {
File[] ff = f.listFiles();
@@ -66,7 +64,6 @@ public class CodeStatistic {
/**
* 统计方法
* @param f
*/
private static void count(File f) {
FileReader fr = null;

View File

@@ -14,37 +14,50 @@ import com.jeesite.common.codec.EncodeUtils;
public class EncodeUtilsTest {
public static void main(String[] args) {
EncodeUtils.xssFilter("1 你好 <script>alert(document.cookie)</script>我还在。");
EncodeUtils.xssFilter("2 你好 <strong>加粗文字</strong>我还在。");
EncodeUtils.xssFilter("<!--HTML-->3 你好 \"><strong>加粗文字</strong>我还在。");
EncodeUtils.xssFilter("<!--HTML-->4 你好 <iframe src=\"abcdef\"></iframe><strong>加粗文字</strong>我还在。");
EncodeUtils.xssFilter("<!--HTML-->5 你好 <iframe src=\"abcdef\"/><strong>加粗文字</strong>我还在。");
EncodeUtils.xssFilter("<!--HTML-->6 你好 <iframe src=\"abcdef\"><strong>加粗文字</strong>我还在。");
EncodeUtils.xssFilter("<!--HTML-->7 你好 <script type=\"text/javascript\">alert(document.cookie)</script>我还在。");
EncodeUtils.xssFilter("<!--HTML-->8 你好 <script\n type=\"text/javascript\">\nalert(document.cookie)\n</script>我还在。");
EncodeUtils.xssFilter("<!--HTML-->9 你好 <script src='' onerror='alert(document.cookie)'></script>我还在。");
EncodeUtils.xssFilter("<!--HTML-->10 你好 <script type=text/javascript>alert()我还在。");
EncodeUtils.xssFilter("<!--HTML-->11 你好 <script>alert(document.cookie)</script>我还在。");
EncodeUtils.xssFilter("<!--HTML-->12 你好 <script>window.location='url'我还在。");
EncodeUtils.xssFilter("<!--HTML-->13 你好 </script></iframe>我还在。");
EncodeUtils.xssFilter("<!--HTML-->14 你好 eval(abc)我还在。");
EncodeUtils.xssFilter("<!--HTML-->15 你好 expression(abc)我还在。");
EncodeUtils.xssFilter("<!--HTML-->16 你好 <img src='abc.jpg' onerror='location='';alert(document.cookie);'></img>我还在。");
EncodeUtils.xssFilter("<!--HTML-->17 你好 <img src='abc.jpg' onerror='alert(document.cookie);'/>我还在。");
EncodeUtils.xssFilter("<!--HTML-->18 你好 <img src='abc.jpg' onerror='alert(document.cookie);'>我还在。");
EncodeUtils.xssFilter("<!--HTML-->19 你好 <a onload='alert(\"abc\")'>hello</a>我还在。");
EncodeUtils.xssFilter("<!--HTML-->20 你好 <a href=\"/abc\">hello</a>我还在。");
EncodeUtils.xssFilter("<!--HTML-->21 你好 <a href='/abc'>hello</a>我还在。");
EncodeUtils.xssFilter("<!--HTML-->22 你好 <a href='vbscript:alert(\"abc\");'>hello</a>我还在。");
EncodeUtils.xssFilter("<!--HTML-->23 你好 <a href='javascript:alert(\"abc\");'>hello</a>我还在。");
EncodeUtils.xssFilter("<!--HTML-->24 你好 ?abc=def&hello=123&world={\"a\":1}我还在。");
EncodeUtils.xssFilter("<!--HTML-->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++, "你好 <script>alert(document.cookie)</script>我还在。");
xssFilter(i++, "你好 <strong>加粗文字</strong>我还在。");
xssFilter(i++, "<!--HTML-->你好 \"><strong>加粗文字</strong>我还在。");
xssFilter(i++, "<!--HTML-->你好 <iframe src=\"abcdef\"></iframe><strong>加粗文字</strong>我还在。");
xssFilter(i++, "<!--HTML-->你好 <iframe src=\"abcdef\"/><strong>加粗文字</strong>我还在。");
xssFilter(i++, "<!--HTML-->你好 <iframe src=\"abcdef\"><strong>加粗文字</strong>我还在。");
xssFilter(i++, "<!--HTML-->你好 <script type=\"text/javascript\">alert(document.cookie)</script>我还在。");
xssFilter(i++, "<!--HTML-->你好 <script\n type=\"text/javascript\">\nalert(document.cookie)\n</script>我还在。");
xssFilter(i++, "<!--HTML-->你好 <script src='' onerror='alert(document.cookie)'></script>我还在。");
xssFilter(i++, "<!--HTML-->你好 <script type=text/javascript>alert()我还在。");
xssFilter(i++, "<!--HTML-->你好 <script>alert(document.cookie)</script>我还在。");
xssFilter(i++, "<!--HTML-->你好 <script>window.location='url'我还在。");
xssFilter(i++, "<!--HTML-->你好 </script></iframe>我还在。");
xssFilter(i++, "<!--HTML-->你好 eval(abc)我还在。");
xssFilter(i++, "<!--HTML-->你好 expression(abc)我还在。");
xssFilter(i++, "<!--HTML-->你好 <img src='abc.jpg' onerror='location='';alert(document.cookie);'></img>我还在。");
xssFilter(i++, "<!--HTML-->你好 <img src='abc.jpg' onerror='alert(document.cookie);'/>我还在。");
xssFilter(i++, "<!--HTML-->你好 <img src='abc.jpg' onerror='alert(document.cookie);'>我还在。");
xssFilter(i++, "<!--HTML-->你好 <a onload='alert(\"abc\")'>hello</a>我还在。");
xssFilter(i++, "<!--HTML-->你好 <a href=\"/abc\">hello</a>我还在。");
xssFilter(i++, "<!--HTML-->你好 <a href='/abc'>hello</a>我还在。");
xssFilter(i++, "<!--HTML-->你好 <a href='vbscript:alert(\"abc\");'>hello</a>我还在。");
xssFilter(i++, "<!--HTML-->你好 <a href='javascript:alert(\"abc\");'>hello</a>我还在。");
xssFilter(i++, "<!--HTML-->你好 ?abc=def&hello=123&world={\"a\":1}我还在。");
xssFilter(i++, "<!--HTML-->你好 ?abc=def&hello=123&world={'a':1}我还在。");
xssFilter(i++, "<!--HTML-->\"><svg/ONLOAD=confirm(3) />");
xssFilter(i++, "<!--HTML-->XSS<embed src=\"data:text/html;base64,PHNjcmlwdD5hbGVydCg5KTwvc2NyaXB0Pgo=\">");
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));
}
}

View File

@@ -1,28 +0,0 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.test.codec;
import com.jeesite.common.codec.Sha1Utils;
/**
* SHA-1 加密工具类,散列加密,不可逆加密
* @author ThinkGem
* @version 2024-07-22
*/
public class Sha1UtilsTest {
public static void main(String[] args) {
String s = "Hello word! 你好,中文!";
System.out.println(s);
String salt = Sha1Utils.genSaltString(8);
System.out.println(salt);
String data = Sha1Utils.sha1(s, salt);
System.out.println(data);
}
}

View File

@@ -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.test.codec;
import com.jeesite.common.codec.ShaUtils;
/**
* SHA-1 加密工具类,散列加密,不可逆加密
* @author ThinkGem
* @version 2024-07-22
*/
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 = ShaUtils.genSaltString(SALT_SIZE);
System.out.println(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);
}
}

View File

@@ -18,7 +18,7 @@ public class ZxingUtilsTest {
String baseDir = FileUtils.getProjectPath();
// 条形码
String imgPath = baseDir + "\\target\\zxing_EAN13.png";
String imgPath = baseDir + "/target/zxing_EAN13.png";
String contents = "6923450657713";
int width = 105, height = 50;
@@ -30,7 +30,7 @@ public class ZxingUtilsTest {
System.out.println("finished zxing EAN-13 decode.");
// 二维码
String imgPath2 = baseDir + "\\target\\zxing.png";
String imgPath2 = baseDir + "/target/zxing.png";
String contents2 = "Hello Gem, welcome to Zxing!\nEMail [ thinkgem@163.com ]";
int width2 = 300, height2 = 300;

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.test.lang;
import com.jeesite.common.lang.StringUtils;
import java.text.ParseException;
/**
* 字符串工具测试类
* @author ThinkGem
* @version 2025-09-18
*/
public class StringUtilsTest {
public static void main(String[] args) throws ParseException {
System.out.println(StringUtils.camelCase("id_name") + " = idName");
System.out.println(StringUtils.camelCase("_id_name") + " = idName");
System.out.println(StringUtils.camelCase("__id_name") + " = idName");
System.out.println(StringUtils.camelCase("a_id") + " = aid");
System.out.println(StringUtils.camelCase("a_b_id") + " = abId");
System.out.println(StringUtils.camelCase("__a_id") + " = aid");
System.out.println(StringUtils.camelCase("__a_b_id") + " = abId");
System.out.println(StringUtils.capCamelCase("id_name") + " = IdName");
System.out.println(StringUtils.capCamelCase("a_b_id_name") + " = AbIdName");
System.out.println(StringUtils.uncamelCase("abIdName") + " = ab_id_name");
System.out.println(StringUtils.uncamelCase("AbIdName") + " = ab_id_name");
}
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.test.utils.excel;
import com.jeesite.common.collect.ListUtils;
import com.jeesite.common.utils.excel.ExcelExport;
import java.io.File;
import java.math.BigDecimal;
import java.util.List;
/**
* 导出Excel文件测试类
* @author ThinkGem
* @version 2025-10-21
*/
public class ExcelExportEntityTest {
/**
* 导出测试
*/
public static void main(String[] args) throws Throwable {
List<ExcelTestEntity> list = ListUtils.newArrayList();
for (int i = 1; i <= 50; i++) {
ExcelTestEntity e = new ExcelTestEntity();
e.setField1("test"+i);
e.setField2(123+i);
e.setField3(BigDecimal.valueOf(12345+i));
list.add(e);
}
File classPath = new File(ExcelExportEntityTest.class.getResource("/").getFile());
String fileName = classPath.getParentFile().getAbsoluteFile() + "/export_entity.xlsx";
// 创建一个Sheet表并导入数据
try(ExcelExport ee = new ExcelExport("表格1", ExcelTestEntity.class)){
ee.setDataList(list).writeFile(fileName);
}
System.out.println("Export success.");
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.test.utils.excel;
import com.jeesite.common.utils.excel.ExcelImport;
import java.io.File;
import java.util.List;
/**
* 导入Excel文件测试类
* @author ThinkGem
* @version 2025-10-21
*/
public class ExcelImportEntityTest {
/**
* 导入测试
*/
public static void main(String[] args) throws Throwable {
File classPath = new File(ExcelExportTest.class.getResource("/").getFile());
String fileName = classPath.getParentFile().getAbsoluteFile() + "/export_entity.xlsx";
try(ExcelImport ei = new ExcelImport(fileName, 2, 0)){
List<ExcelTestEntity> list = ei.getDataList(ExcelTestEntity.class);
for (ExcelTestEntity e : list) {
System.out.print(e.getField1()+", ");
System.out.print(e.getField2()+", ");
System.out.print(e.getField3()+", ");
System.out.println();
}
}
System.out.println("Import success.");
}
}

View File

@@ -6,8 +6,6 @@ package com.jeesite.test.utils.excel;
import com.jeesite.common.utils.excel.ExcelImport;
import org.apache.poi.ss.usermodel.Row;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.test.utils.excel;
import com.jeesite.common.utils.excel.annotation.ExcelField;
import com.jeesite.common.utils.excel.annotation.ExcelFields;
import java.math.BigDecimal;
/**
* Excel 测试数据实体
* @author ThinkGem
*/
public class ExcelTestEntity {
// 方式1声明在字段上
@ExcelField(title = "字段1", align = ExcelField.Align.CENTER, sort = 10)
private String field1;
private Integer field2;
private BigDecimal field3;
// 方式2声明在构造上
@ExcelFields({
@ExcelField(title = "字段2", attrName = "field2", align = ExcelField.Align.CENTER, sort = 20),
})
public ExcelTestEntity() {}
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public Integer getField2() {
return field2;
}
public void setField2(Integer field2) {
this.field2 = field2;
}
// 方式3声明在方法上
@ExcelField(title = "字段3", align = ExcelField.Align.CENTER, sort = 30)
public BigDecimal getField3() {
return field3;
}
public void setField3(BigDecimal field3) {
this.field3 = field3;
}
}

View File

@@ -1,3 +1,7 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.test.utils.word;
import com.jeesite.common.io.FileUtils;

View File

@@ -1,7 +1,7 @@
## 模块简介
模块基于 Spring AI 和 JeeSite 内容管理系统CMS并结合了检索增强生成Retrieval-Augmented Generation, RAG技术
模块基于 Spring AI 和 JeeSite 内容管理系统CMS并结合了检索增强生成Retrieval-Augmented Generation, RAG技术
和先进的人工智能算法AI打造了一个强大的企业级知识管理和智能对话平台。该模块专为企业设计旨在通过高效的知识获取和精准的对话能力
提升企业的信息管理效率和员工的工作效能。
@@ -9,11 +9,13 @@
能获得最新且准确的结果。这种检索与生成相结合的方式,不仅提高了信息检索的准确性,还增强了回答的上下文关联性,
特别适合处理复杂的企业知识库。
此外该模块,支持在线大模型和本地部署的大模型Ollama、DeepSeek、通义千问理论上支持所有 OpenAPI 标准接口的 AI 提供商。
此外该模块,支持云端模型和本地部署的大模型Ollama、DeepSeek、通义千问理论上支持所有 OpenAPI 标准接口的 AI 提供商。
并能无缝集成多种嵌入式 AI 模型的向量数据库,如 Chroma、PGVector、Elasticsearch、Milvus 等,实现高效的数据存储、检索及分析。
无论是大规模数据集还是高度专业化的领域知识JeeSite CMS + RAG + AI 都能提供定制化解决方案,满足企业多样化的业务需求和技术要求。
企业可以轻松管理和访问复杂的信息资源,促进内部知识共享和创新,从而在竞争激烈的市场环境中保持领先地位。
提供大模型 Tool 本地工具调用及 MCP 服务端和客户端工具调用,助力大模型与您的业务深度融合,实现高效交互。
优势:本模块结构清晰,代码简洁易懂,不管是正式项目、或是学习 AI 技术、都能轻松应对读懂源代码。
## 在线演示
@@ -22,20 +24,25 @@
## 源码下载
* 后端:<https://gitee.com/thinkgem/jeesite5/tree/v5.springboot3/modules/cms-ai>
* 后端:<https://gitee.com/thinkgem/jeesite5/tree/v5.springboot3/modules/ai>
* 前端:<https://gitee.com/thinkgem/jeesite-vue/tree/main/packages/cms>
## AI 模型配置
支持的 AI 模型列表:<https://docs.spring.io/spring-ai/reference/1.0/api/index.html>
* 线上模型:理论上支持所有 [OpenAPI](https://help.aliyun.com/zh/model-studio/developer-reference/use-qwen-by-calling-api) 标准接口的 AI 提供商。
* 云端模型:理论上支持所有 [OpenAPI](https://help.aliyun.com/zh/model-studio/developer-reference/use-qwen-by-calling-api) 标准接口的 AI 提供商。
* 本地模型:使用 [Ollama](https://ollama.com) 安装方法,本文不多赘述,网上有很多安装资料。
* 模型类型包括:聊天对话模型和嵌入式向量库模型,需注意 dimensions 维度参数,要和模型要求的匹配。
具体配置项详见 [jeesite-cms-ai.yml](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml) 文件,有注释。
```yml
# 向量库类型openai、ollama
spring.ai.model.chat: openai
```
具体配置项详见 [jeesite-ai-cms.yml](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/ai/ai-cms/src/main/resources/config/jeesite-ai-cms.yml) 文件,有注释。
## 向量数据库配置
@@ -47,12 +54,17 @@
* Milvus
* ...
具体配置项详见 [jeesite-cms-ai.yml](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml) 文件,有注释。
```yml
# 向量库类型chroma、pgvector、elasticsearch、milvus、指定 none 表示不使用向量库
spring.ai.vectorstore.type: none
```
具体配置项详见 [jeesite-ai-cms.yml](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/ai/ai-cms/src/main/resources/config/jeesite-ai-cms.yml) 文件,有注释。
### 安装 Chroma
```sh
docker run -it --rm --name chroma -p 8000:8000 ghcr.io/chroma-core/chroma:1.0.0
docker run -d --name chroma -p 8000:8000 ghcr.io/chroma-core/chroma:1.0.0
```
### 安装 PGVector
@@ -119,11 +131,46 @@ CREATE INDEX ON vector_store_1024 USING HNSW (embedding vector_cosine_ops);
* 菜单名称AI 助手
* 菜单地址:/cms/chat/index
## 支持工具调用 Tool Calling
## 工具调用 Tool Calling
工具调用 Tool Calling也称 Function Calling是人工智能应用程序中的常见模式允许模型与一组 API 或工具交互,从而增强其功能。
实例代码,详见 [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 实现你的业务联动。
实例代码,详见 [TestAiTools.java、UserAiTools.java](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/ai/ai-tools/src/main/java/com/jeesite/modules/ai/tools/) 让 AI 调用你的 java 实现你的业务联动。并能保持当前用户会话,进行数据权限控制。
## MCP 模型上下文协议
实现 MCPModel Context Protocol服务端基于 SSEServer-Sent Events的标准接入。
* 服务端:[web-mcp](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/web-ai/web-mcp/)
```yml
spring:
ai:
mcp:
server:
enabled: true
sse-endpoint: /api/v1/sse
sse-message-endpoint: /api/v1/mcp
```
MCP SSE 接口地址http://127.0.0.1:8981/api/v1/sse
* 客户端:[web-cms](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/web-ai/web-cms/)
```yml
spring:
ai:
model:
chat: ollama
mcp:
client:
enabled: true
sse:
connections:
jeesite:
url: http://127.0.0.1:8981
sse-endpoint: /api/v1/sse
```
聊天窗口,询问“当前服务器时间”,在 MCP 服务端会显示调用日志,并返回相应信息。
## 支持结构化输出
@@ -131,7 +178,7 @@ CREATE INDEX ON vector_store_1024 USING HNSW (embedding vector_cosine_ops);
pom.xml 中注释掉 `<artifactId>spring-ai-starter-model-openai</artifactId>`
打开注释 `<artifactId>spring-ai-starter-model-ollama</artifactId>`
启用 `Ollama` 本地模型进行测试,地址如下
启用 `Ollama` 本地模型,测试类:`AiChatServiceTest.java`,或测试地址
* 文本格式输出
- 源码位置CmsAiChatService.chatText(message)

View File

@@ -41,7 +41,7 @@
<suspend_validator>false</suspend_validator>
<export_setting>
<export_ddl_setting>
<output_path>db/cms-ai.sql</output_path>
<output_path>db/ai-cms.sql</output_path>
<encoding>UTF-8</encoding>
<line_feed>CR+LF</line_feed>
<is_open_after_saved>false</is_open_after_saved>
@@ -73,7 +73,7 @@
</export_ddl_setting>
<export_excel_setting>
<category_id>null</category_id>
<output_path>db/cms-ai.xls</output_path>
<output_path>db/ai-cms.xls</output_path>
<template></template>
<template_path></template_path>
<used_default_template_lang>en</used_default_template_lang>
@@ -89,7 +89,7 @@
<is_open_after_saved>true</is_open_after_saved>
</export_html_setting>
<export_image_setting>
<output_file_path>db/cms-ai.png</output_file_path>
<output_file_path>db/ai-cms.png</output_file_path>
<category_dir_path></category_dir_path>
<with_category_image>true</with_category_image>
<is_open_after_saved>true</is_open_after_saved>

View File

@@ -5,49 +5,49 @@
<parent>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-parent</artifactId>
<version>5.12.0.springboot3-SNAPSHOT</version>
<relativePath>../../parent/pom.xml</relativePath>
<artifactId>jeesite-parent-ai</artifactId>
<version>5.14.0.springboot3-SNAPSHOT</version>
<relativePath>../../../parent/ai/pom.xml</relativePath>
</parent>
<artifactId>jeesite-module-cms-ai</artifactId>
<artifactId>jeesite-module-ai-cms</artifactId>
<packaging>jar</packaging>
<name>JeeSite Module CMS+RAG+AI</name>
<name>JeeSite Module AI + CMS + RAG</name>
<url>http://jeesite.com</url>
<inceptionYear>2013-Now</inceptionYear>
<properties>
<spring-ai.version>1.0.0-RC1</spring-ai.version>
</properties>
<dependencies>
<!-- 核心模块 --><!--suppress VulnerableLibrariesLocal -->
<dependency>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-module-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- 内容管理模块 -->
<dependency>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-module-cms</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- 在线大模型 -->
<!-- 云端模型 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- 本地大模型
<!-- 本地大模型 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency> -->
</dependency>
<!-- 向量数据库 -->
<dependency>
@@ -65,19 +65,19 @@
<artifactId>httpclient5</artifactId>
</dependency>
<!-- PG 向量数据库
<!-- PG 向量数据库 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency> -->
</dependency>
<!-- ES 向量数据库
<!-- ES 向量数据库 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
</dependency> -->
</dependency>
<!-- Milvus 向量数据库
<!-- Milvus 向量数据库 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-milvus</artifactId>
@@ -92,7 +92,7 @@
<groupId>io.netty</groupId>
<artifactId>netty-resolver-dns-native-macos</artifactId>
<classifier>osx-aarch_64</classifier>
</dependency> -->
</dependency>
<!-- HTML 转 Markdown -->
<dependency>
@@ -117,20 +117,21 @@
<artifactId>commons-compress</artifactId>
<version>1.27.1</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Ai Tools -->
<dependency>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-module-ai-tools</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- MCP Client -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
</dependencies>
<developers>
<developer>

View File

@@ -2,16 +2,18 @@
* 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;
package com.jeesite.modules.ai.cms.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 com.jeesite.modules.ai.cms.properties.AiCmsProperties;
import com.jeesite.modules.ai.cms.service.CacheChatMemoryRepository;
import com.jeesite.modules.ai.tools.TestAiTools;
import com.jeesite.modules.ai.tools.UserAITools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -24,24 +26,41 @@ import org.springframework.jdbc.core.JdbcTemplate;
* @author ThinkGem
*/
@Configuration
@EnableConfigurationProperties(CmsAiProperties.class)
public class CmsAiChatConfig {
@EnableConfigurationProperties(AiCmsProperties.class)
public class AiCmsChatConfig {
/**
* 聊天对话客户端
* 聊天对话客户端使用本地 Tools
* @author ThinkGem
*/
@Bean
public ChatClient chatClient(ChatClient.Builder builder, CmsAiProperties properties) {
@Bean("chatClient")
@ConditionalOnProperty(name = "spring.ai.mcp.client.enabled", havingValue = "false", matchIfMissing = true)
public ChatClient chatClient(ChatClient.Builder builder, AiCmsProperties properties,
TestAiTools testAiTools, UserAITools userAITools) {
if (StringUtils.isNotBlank(properties.getDefaultSystem())) {
builder.defaultSystem(properties.getDefaultSystem());
}
if (properties.getToolCalls()) {
builder.defaultTools(new CmsAiTools());
if (properties.getTools().getEnabled()) {
builder.defaultTools(testAiTools, userAITools);
}
return builder.build();
}
/**
* 聊天对话客户端使用 MCP Tools
* @author ThinkGem
*/
@Bean("chatClient")
@ConditionalOnProperty(name = "spring.ai.mcp.client.enabled", havingValue = "true", matchIfMissing = false)
public ChatClient chatClientMcp(ChatClient.Builder builder, AiCmsProperties properties,
SyncMcpToolCallbackProvider syncMcpToolCallbackProvider) {
if (StringUtils.isNotBlank(properties.getDefaultSystem())) {
builder.defaultSystem(properties.getDefaultSystem());
}
builder.defaultToolCallbacks(syncMcpToolCallbackProvider.getToolCallbacks());
return builder.build();
}
/**
* 聊天对话数据存储
* @author ThinkGem

View File

@@ -2,7 +2,7 @@
* 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;
package com.jeesite.modules.ai.cms.config;
import com.jeesite.common.config.Global;
import org.springframework.context.annotation.Bean;
@@ -16,7 +16,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
* @author ThinkGem
*/
@Configuration
public class CmsAiWebMvcConfig implements WebMvcConfigurer {
public class AiCmsWebMvcConfig implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {

View File

@@ -2,10 +2,10 @@
* 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;
package com.jeesite.modules.ai.cms.config;
import com.jeesite.common.lang.StringUtils;
import com.jeesite.common.mapper.JsonMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -38,6 +38,7 @@ public class WebClientThinkConfig {
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public WebClientCustomizer webClientCustomizerThink() {
return webClientBuilder -> {
ExchangeFilterFunction requestFilter = ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
@@ -59,11 +60,12 @@ public class WebClientThinkConfig {
List<String> lines = new ArrayList<>();
String[] list = eventString.split("\\n", -1);
for (String line : list) {
if (!line.startsWith("data: ")) {
lines.add(line);
continue;
String jsonPart = line;
boolean dataPrefix = false;
if (line.startsWith("data: ")) {
jsonPart = line.substring("data: ".length()).trim();
dataPrefix = true;
}
String jsonPart = line.substring("data: ".length()).trim();
if (!(StringUtils.startsWith(jsonPart, "{")
&& StringUtils.endsWith(jsonPart, "}")
&& !"data: [DONE]".equals(line))) {
@@ -76,38 +78,54 @@ public class WebClientThinkConfig {
continue;
}
// 修改内容字段
List<Object> choices = (List<Object>)map.get("choices");
boolean ollamaEvent = false;
List<Object> choices = (List<Object>) map.get("choices");
if (choices == null) {
lines.add(line);
continue;
Map<String, Object> message = (Map<String, Object>) map.get("message");
if (message == null) {
lines.add(line);
continue;
}
choices = List.of(message);
ollamaEvent = true;
}
for (Object o : choices) {
Map<String, Object> choice = (Map<String, Object>) o;
if (choice == null) {
continue;
}
String content;
String reasoningContent;
Map<String, Object> delta = (Map<String, Object>) choice.get("delta");
if (delta == null) {
continue;
if (delta != null) {
content = (String) delta.get("content");
reasoningContent = (String) delta.get("reasoning_content");
} else {
content = (String) choice.get("content");
reasoningContent = (String) choice.get("thinking");
}
String reasoningContent = (String) delta.get("reasoning_content");
String content = (String) delta.get("content");
if (StringUtils.isNotEmpty(reasoningContent) && StringUtils.isEmpty(content)) {
if (!thinkingFlag.get()) {
thinkingFlag.set(true);
delta.put("content", "<think>\n" + reasoningContent);
content = "<think>\n" + reasoningContent;
} else {
delta.put("content", reasoningContent);
content = reasoningContent;
}
} else {
if (thinkingFlag.get()) {
thinkingFlag.set(false);
delta.put("content", "</think>\n" + (content == null ? "" : content));
content = "</think>\n" + (content == null ? "" : content);
}
}
if (ollamaEvent) {
choice.put("content", content);
map.put("message", choice);
} else if (delta != null) {
delta.put("content", content);
}
}
// 重新生成事件字符串
lines.add("data: " + JsonMapper.toJson(map));
lines.add((dataPrefix ? "data: " : "") + JsonMapper.toJson(map));
}
String finalLine = StringUtils.join(lines, "\n");
logger.trace("Modified response: ==> {}", finalLine);

View File

@@ -0,0 +1,86 @@
package com.jeesite.modules.ai.cms.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
@ConfigurationProperties("spring.ai")
public class AiCmsProperties {
/**
* 向量数据库设置
*/
@NestedConfigurationProperty
private final Vectorstore vectorstore = new Vectorstore();
/**
* 是否启用 Tool calling 工具调用【例子详见 TestAiTools.java、UserAiTools.java 】
*/
@NestedConfigurationProperty
private final Tools tools = new Tools();
/**
* 默认系统提示词
*/
private String defaultSystem = "";
/**
* 默认问题模板格式
*/
private String defaultPromptTemplate = "";
public Vectorstore getVectorstore() {
return vectorstore;
}
public Tools getTools() {
return tools;
}
public String getDefaultSystem() {
return defaultSystem;
}
public void setDefaultSystem(String defaultSystem) {
this.defaultSystem = defaultSystem;
}
public String getDefaultPromptTemplate() {
return defaultPromptTemplate;
}
public void setDefaultPromptTemplate(String defaultPromptTemplate) {
this.defaultPromptTemplate = defaultPromptTemplate;
}
public static class Vectorstore {
/**
* 向量库类型选择chroma、pgvector、elasticsearch、milvus
*/
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
public static class Tools {
/**
* 是否启用 Tool calling 工具调用【例子详见 TestAiTools.java、UserAiTools.java 】
*/
private Boolean enabled = false;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}
}

View File

@@ -2,20 +2,23 @@
* 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.service;
package com.jeesite.modules.ai.cms.service;
import com.jeesite.common.cache.CacheUtils;
import com.jeesite.common.collect.ListUtils;
import com.jeesite.common.collect.MapUtils;
import com.jeesite.common.config.Global;
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.ai.cms.properties.AiCmsProperties;
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.apache.shiro.util.ThreadContext;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
@@ -27,12 +30,13 @@ 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.beans.factory.ObjectProvider;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.stereotype.Service;
@@ -48,20 +52,26 @@ import java.util.Map;
* @author ThinkGem
*/
@Service
public class CmsAiChatService extends BaseService {
public class AiCmsChatService extends BaseService {
private static final String CMS_CHAT_CACHE = "cmsChatCache";
private static final String[] USER_MESSAGE_SEARCH = new String[]{"{", "}"};
private static final String[] USER_MESSAGE_REPLACE = new String[]{"\\{", "\\}"};
@Autowired
private ChatClient chatClient;
@Autowired
private ChatMemory chatMemory;
@Autowired(required = false)
private VectorStore vectorStore;
@Autowired
private CmsAiProperties properties;
private final ChatClient chatClient;
private final ChatMemory chatMemory;
private final VectorStore vectorStore;
private final AiCmsProperties properties;
public AiCmsChatService(ChatClient chatClient,
ChatMemory chatMemory,
ObjectProvider<VectorStore> vectorStore,
AiCmsProperties properties) {
this.chatClient = chatClient;
this.chatMemory = chatMemory;
this.vectorStore = vectorStore.getIfAvailable();
this.properties = properties;
}
/**
* 获取聊天对话消息
@@ -83,11 +93,7 @@ public class CmsAiChatService extends BaseService {
}
public Map<String, Map<String, Object>> getChatCacheMap() {
Map<String, Map<String, Object>> cache = CacheUtils.get(CMS_CHAT_CACHE, getChatCacheKey());
if (cache == null) {
cache = MapUtils.newHashMap();
}
return cache;
return CacheUtils.computeIfAbsent(CMS_CHAT_CACHE, getChatCacheKey(), k -> MapUtils.newHashMap());
}
/**
@@ -126,8 +132,16 @@ public class CmsAiChatService extends BaseService {
* @author ThinkGem
*/
public Flux<ChatResponse> chatStream(String conversationId, String message, HttpServletRequest request) {
ChatClient.ChatClientRequestSpec spec = chatClient.prompt()
.messages(new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE)))
String text = StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE);
List<Media> media = ListUtils.newArrayList();
// List<FileUpload> 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());
@@ -137,10 +151,11 @@ public class CmsAiChatService extends BaseService {
.promptTemplate(new PromptTemplate(properties.getDefaultPromptTemplate()))
.build());
}
spec.toolContext(Map.of("subject", ThreadContext.getSubject()));
return spec.stream()
.chatResponse()
.doOnNext(response -> {
if (response.getResult() != null && StringUtils.isNotBlank(response.getResult().getOutput().getText())) {
if (StringUtils.isNotBlank(response.getResult().getOutput().getText())) {
AssistantMessage assistantMessage = (AssistantMessage)request.getAttribute("assistantMessage");
AssistantMessage currAssistantMessage = response.getResult().getOutput();
if (assistantMessage == null) {
@@ -164,8 +179,12 @@ public class CmsAiChatService extends BaseService {
})
.onErrorResume(error -> {
String errorMessage = error.getMessage();
if (error instanceof WebClientResponseException webClientError) {
errorMessage = webClientError.getResponseBodyAsString();
if (Global.getPropertyToBoolean("error.page.printErrorInfo", "true")){
if (error instanceof WebClientResponseException webClientError) {
errorMessage = webClientError.getResponseBodyAsString();
} else if (error.getCause() instanceof WebClientResponseException webClientError) {
errorMessage = webClientError.getResponseBodyAsString();
}
}
AssistantMessage assistantMessage = new AssistantMessage(errorMessage);
chatMemory.add(conversationId, assistantMessage);
@@ -197,7 +216,7 @@ public class CmsAiChatService extends BaseService {
return chatClient.prompt()
.messages(
new SystemMessage("""
[ {name:'张三', sex:'男', age:'17'}, {name:'李四', sex:'女', age:'18'} ]返回 json
[{name:'张三', sex:'男', age:'17'}, {name:'李四', sex:'女', age:'18'}]返回 json
"""),
new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE))
)

View File

@@ -2,7 +2,7 @@
* 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.service;
package com.jeesite.modules.ai.cms.service;
import com.jeesite.common.collect.ListUtils;
import com.jeesite.common.collect.MapUtils;
@@ -36,8 +36,7 @@ import org.springframework.ai.document.Document;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;
import java.io.IOException;
@@ -52,13 +51,14 @@ import java.util.Set;
* @author ThinkGem
*/
@Service
@ConditionalOnBean(VectorStore.class)
public class ArticleVectorStoreImpl implements ArticleVectorStore {
protected Logger logger = LoggerFactory.getLogger(getClass());
private final Logger logger = LoggerFactory.getLogger(getClass());
private final VectorStore vectorStore;
@Autowired
private VectorStore vectorStore;
public ArticleVectorStoreImpl(ObjectProvider<VectorStore> vectorStore) {
this.vectorStore = vectorStore.getIfAvailable();
}
/**
* 保存文章到向量库
@@ -66,6 +66,7 @@ public class ArticleVectorStoreImpl implements ArticleVectorStore {
*/
@Override
public void save(Article article) {
if (vectorStore == null) return;
Map<String, Object> metadata = MapUtils.newHashMap();
metadata.put("id", article.getId());
metadata.put("siteCode", article.getCategory().getSite().getSiteCode());
@@ -184,6 +185,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());
}
@@ -194,6 +196,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());

View File

@@ -2,7 +2,7 @@
* 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.service;
package com.jeesite.modules.ai.cms.service;
import com.jeesite.common.cache.CacheUtils;
import org.jetbrains.annotations.NotNull;
@@ -28,8 +28,7 @@ public class CacheChatMemoryRepository implements ChatMemoryRepository {
@Override
public @NotNull List<Message> findByConversationId(@NotNull String conversationId) {
List<Message> all = CacheUtils.get(CMS_CHAT_MSG_CACHE, conversationId);
return all != null ? all : List.of();
return CacheUtils.computeIfAbsent(CMS_CHAT_MSG_CACHE, conversationId, k -> List.of());
}
@Override

View File

@@ -2,16 +2,15 @@
* 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.web;
package com.jeesite.modules.ai.cms.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.ai.cms.service.AiCmsChatService;
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;
@@ -32,8 +31,11 @@ import java.util.stream.Collectors;
@RequestMapping("${adminPath}/cms/chat")
public class CmsAiChatController extends BaseController {
@Autowired
private CmsAiChatService cmsAiChatService;
private final AiCmsChatService aiCmsChatService;
public CmsAiChatController(AiCmsChatService aiCmsChatService) {
this.aiCmsChatService = aiCmsChatService;
}
/**
* 获取聊天对话消息
@@ -41,7 +43,7 @@ public class CmsAiChatController extends BaseController {
*/
@RequestMapping("/message")
public List<Message> message(String id) {
return cmsAiChatService.getChatMessage(id);
return aiCmsChatService.getChatMessage(id);
}
/**
@@ -50,7 +52,7 @@ public class CmsAiChatController extends BaseController {
*/
@RequestMapping("/list")
public Collection<Map<String, Object>> list() {
return cmsAiChatService.getChatCacheMap().values().stream()
return aiCmsChatService.getChatCacheMap().values().stream()
.sorted(Comparator.comparing(map -> (String) map.get("id"),
Comparator.reverseOrder())).collect(Collectors.toList());
}
@@ -61,7 +63,7 @@ public class CmsAiChatController extends BaseController {
*/
@RequestMapping("/save")
public String save(String id, String title) {
Map<String, Object> map = cmsAiChatService.saveChatConversation(id, title);
Map<String, Object> map = aiCmsChatService.saveChatConversation(id, title);
return renderResult(Global.TRUE, "保存成功", map);
}
@@ -71,7 +73,7 @@ public class CmsAiChatController extends BaseController {
*/
@RequestMapping("/delete")
public String delete(@RequestParam String id) {
cmsAiChatService.deleteChatConversation(id);
aiCmsChatService.deleteChatConversation(id);
return renderResult(Global.TRUE, "删除成功", id);
}
@@ -82,7 +84,7 @@ public class CmsAiChatController extends BaseController {
*/
@RequestMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatResponse> stream(@RequestParam String id, @RequestParam String message, HttpServletRequest request) {
return cmsAiChatService.chatStream(id, message, request);
return aiCmsChatService.chatStream(id, message, request);
}
/**
@@ -92,27 +94,28 @@ public class CmsAiChatController extends BaseController {
*/
@RequestMapping(value = "/text")
public String text(@RequestParam String message) {
return cmsAiChatService.chatText(message);
return aiCmsChatService.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<String, Object> json(@RequestParam String message) {
return cmsAiChatService.chatJson(message);
return aiCmsChatService.chatJson(message);
}
/**
* 聊天对话结构化输出
* 聊天对话结构化输出 Entity
* @author ThinkGem
* http://127.0.0.1:8980/js/a/cms/chat/entity?message=北京
*/
@RequestMapping(value = "/entity")
public List<Area> entity(@RequestParam String message) {
return cmsAiChatService.chatArea(message);
return aiCmsChatService.chatArea(message);
}
}

View File

@@ -2,11 +2,11 @@
## 重要提示Tip
## 请勿在该配置文件中添加其它任何配置(添加也不会生效)。
## 该文件,仅仅是为了让 jeesite-cms-ai.yml 文件,
## 该文件,仅仅是为了让 jeesite-ai-cms.yml 文件,
## 在 IDEA 中有一个自动完成及帮助提示,并无其它用意。
## 参数配置请在 jeesite-cms-ai.yml 文件中添加。
## 参数配置请在 jeesite-ai-cms.yml 文件中添加。
spring:
config:
import:
- classpath:config/jeesite-cms-ai.yml
- classpath:config/jeesite-ai-cms.yml

View File

@@ -3,26 +3,57 @@
spring:
ai:
# 在线大模型【请在 pom.xml 中打开 openai 的注释,并注释上其它模型】
# 模型选择openai、ollama
model:
chat: openai
embedding: ${spring.ai.model.chat}
embedding.text: ${spring.ai.model.chat}
embedding.multimodal: ${spring.ai.model.chat}
audio.transcription: none
audio.speech: none
moderation: none
image: none
# ========= 聊天对话 相关配置 =========
# 云端模型【请在 pom.xml 中打开 openai 的注释,并注释上其它模型】
openai:
# 硅基流动
base-url: https://api.siliconflow.cn
api-key: ${SFLOW_APP_KEY}
# 聊天对话模型
# 聊天对话模型使用阿里百炼
chat:
base-url: https://dashscope.aliyuncs.com/compatible-mode
api-key: ${BAILIAN_APP_KEY}
options:
model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
model: deepseek-r1-distill-llama-8b
max-tokens: 1024
temperature: 0.6
top-p: 0.9
top-p: 0.7
frequency-penalty: 0
# 向量库知识库模型(注意:不同的模型维度不同)
# 嵌入向量模型使用硅基流动
embedding:
base-url: https://api.siliconflow.cn
api-key: ${SFLOW_APP_KEY}
options:
model: BAAI/bge-m3
dimensions: 512
# # 硅基流动
# base-url: https://api.siliconflow.cn
# api-key: ${SFLOW_APP_KEY}
# # 聊天对话模型
# chat:
# options:
# model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
# max-tokens: 1024
# temperature: 0.6
# top-p: 0.9
# frequency-penalty: 0
# # 向量库知识库模型(注意:不同的模型维度不同)
# embedding:
# options:
# model: BAAI/bge-m3
# dimensions: 512
# # 模力方舟
# base-url: https://ai.gitee.com
# api-key: ${GITEE_APP_KEY}
@@ -34,7 +65,6 @@ spring:
# temperature: 0.6
# top-p: 0.9
# frequency-penalty: 0
# #logprobs: true
# # 向量库知识库模型(注意:不同的模型维度不同)
# embedding:
# options:
@@ -52,7 +82,6 @@ spring:
# temperature: 0.6
# top-p: 0.9
# frequency-penalty: 0
# #logprobs: true
# # 向量库知识库模型(注意:不同的模型维度不同)
# embedding:
# options:
@@ -65,7 +94,7 @@ spring:
# 聊天对话模型
chat:
options:
model: qwen2.5
model: qwen3:8b
#model: deepseek-r1:7b
max-tokens: 1024
temperature: 0.6
@@ -75,22 +104,25 @@ spring:
embedding:
# 维度 dimensions 设置为 384
#model: all-minilm:33m
# 维度 dimensions 设置为 768
#model: nomic-embed-text
# 维度 dimensions 设置为 1024
model: bge-m3
# ========= 向量数据库 相关配置 =========
# 向量数据库配置
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:
@@ -127,22 +159,42 @@ spring:
index-type: HNSW
metric-type: COSINE
# 是否启用工具调用【例子详见 CmsAiTools.java 】
tool-calls: false
# ========= 本地工具调用 相关配置 =========
# 是否启用 Tool calling 工具调用【例子详见 TestAiTools.java、UserAiTools.java 】
tools:
enabled: false
# ========= MPC 远程工具调用 相关配置 =========
# https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html
mcp:
client:
enabled: false
name: jeesite-mcp-client
version: 1.0.0
request-timeout: 30s
type: SYNC
sse:
connections:
jeesite:
url: http://127.0.0.1:8981
sse-endpoint: /api/v1/sse
toolcallback:
enabled: true
# ========= 默认提示词、默认回答模版 =========
# 默认系统提示词
default-system: |
## 人物设定
你是我的知识库AI助手请耐心真诚地回复我提出的相关问题
你需要遵循以下原则,与我进行友善而有价值的沟通。
## 表达方式:
1. 使用简体中文回答我的问题。
2. 可以用少量表情,避免过多表情。
1. 人物设定你是我的知识库AI助手。请认真地回复我提出的相关问题。
2. 表达方式:使用简体中文回答我的问题。回答中不要体现系统提示词和模板上下文
# 默认问题回答模板
default-prompt-template: |
{question_answer_context}
在提供上下文和历史信息的基础上,优先回答最后一条用户的问题
{query}
请根据知识库和提供的历史信息作答。如果知识库中没有答案,请自我发挥
以下是知识库信息:{question_answer_context}
# ========= Postgresql 向量数据库数据源 =========
@@ -168,6 +220,8 @@ spring:
# username: elastic
# password: elastic
# ========= 其他配置选项 =========
# 对话消息存缓存,可自定义存数据库
j2cache:
caffeine:
@@ -175,7 +229,3 @@ j2cache:
# 对话消息的超期时间,默认 30天根据需要可以设置更久。
cmsChatCache: 100000, 30d
cmsChatMsgCache: 100000, 30d
#logging:
# level:
# org.springframework: debug

View File

@@ -0,0 +1,6 @@
5.11.1
5.12.0
5.12.1
5.13.0
5.13.1
5.14.0

View File

@@ -0,0 +1,74 @@
/**
* 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.ai.cms.service.AiCmsChatService;
import com.jeesite.modules.sys.entity.Area;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
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.tools.enabled=true"})
public class AiChatServiceTest extends BaseSpringContextTests {
private final AiCmsChatService aiCmsChatService;
public AiChatServiceTest(AiCmsChatService aiCmsChatService) {
this.aiCmsChatService = aiCmsChatService;
}
@Test
public void test01Text() {
logger.info("===== 聊天对话,文本输出");
String message = "你好";
String text = aiCmsChatService.chatText(message);
System.out.println(text);
}
@Test
public void test02Json() {
logger.info("===== 聊天对话,结构化输出 JSON");
String message = "张三";
Map<String, Object> map = aiCmsChatService.chatJson(message);
System.out.println(JsonMapper.toJson(map));
}
@Test
public void test03Tool() {
logger.info("===== 聊天对话,结构化输出 Tool Calling");
String message = "打开客厅的灯";
Map<String, Object> map = aiCmsChatService.chatJson(message);
System.out.println(JsonMapper.toJson(map));
message = "关闭客厅的灯";
map = aiCmsChatService.chatJson(message);
System.out.println(JsonMapper.toJson(map));
}
@Test
public void test04Entity() {
logger.info("===== 聊天对话,结构化输出 Entity");
String message = "北京";
List<Area> list = aiCmsChatService.chatArea(message);
System.out.println(JsonMapper.toJson(list));
}
}

View File

@@ -0,0 +1,15 @@
# 数据库连接
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

View File

@@ -3,7 +3,7 @@
<!-- Logger level setting -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<include resource="config/logger-core.xml"/>
<include resource="config/logger-default.xml" />
<!-- Console log output -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">

View File

@@ -0,0 +1,22 @@
@echo off
rem /**
rem * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
rem * No deletion without permission, or be held responsible to law.
rem *
rem * Author: ThinkGem@163.com
rem */
echo.
echo [<5B><>Ϣ] <20><><EFBFBD>𹤳̵<F0B9A4B3>Maven<65><6E><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
echo.
%~d0
cd %~dp0
call mvn -v
echo.
cd ..
call mvn clean deploy -Dmaven.test.skip=true -Pdeploy
cd bin
pause

View File

@@ -0,0 +1,18 @@
#!/bin/sh
# /**
# * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
# * No deletion without permission, or be held responsible to law.
# *
# * Author: ThinkGem@163.com
# */
echo ""
echo "[信息] 部署工程到Maven服务器。"
echo ""
mvn -v
echo ""
cd ..
mvn clean deploy -Dmaven.test.skip=true -Pdeploy
cd bin

View File

@@ -0,0 +1,22 @@
@echo off
rem /**
rem * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
rem * No deletion without permission, or be held responsible to law.
rem *
rem * Author: ThinkGem@163.com
rem */
echo.
echo [<5B><>Ϣ] <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ<EFBFBD><D7B0><EFBFBD>̣<EFBFBD><CCA3><EFBFBD><EFBFBD><EFBFBD>jar<61><72><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>
echo.
%~d0
cd %~dp0
call mvn -v
echo.
cd ..
call mvn clean install -Dmaven.test.skip=true -Ppackage
cd bin
pause

View File

@@ -0,0 +1,18 @@
#!/bin/sh
# /**
# * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
# * No deletion without permission, or be held responsible to law.
# *
# * Author: ThinkGem@163.com
# */
echo ""
echo "[信息] 打包安装工程生成jar包文件。"
echo ""
mvn -v
echo ""
cd ..
mvn clean install -Dmaven.test.skip=true -Ppackage
cd bin

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-parent-ai</artifactId>
<version>5.14.0.springboot3-SNAPSHOT</version>
<relativePath>../../../parent/ai/pom.xml</relativePath>
</parent>
<artifactId>jeesite-module-ai-tools</artifactId>
<packaging>jar</packaging>
<name>JeeSite Module AI Tools</name>
<url>http://jeesite.com</url>
<inceptionYear>2013-Now</inceptionYear>
<properties>
</properties>
<dependencies>
<!-- 核心模块 --><!--suppress VulnerableLibrariesLocal -->
<dependency>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-module-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- 内容管理模块 -->
<dependency>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-module-cms</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- AI Model -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-model</artifactId>
</dependency>
<!-- AI MCP -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
</dependency>
</dependencies>
<developers>
<developer>
<id>thinkgem</id>
<name>WangZhen</name>
<email>thinkgem at 163.com</email>
<roles><role>Project lead</role></roles>
<timezone>+8</timezone>
</developer>
</developers>
<organization>
<name>JeeSite</name>
<url>http://jeesite.com</url>
</organization>
</project>

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.modules.ai.tools;
import com.jeesite.common.lang.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
/**
* AI MCP 工具调用
* @author ThinkGem
*/
@Component
public class TestAiTools {
private final Logger logger = LoggerFactory.getLogger(TestAiTools.class);
/**
* 获取服务器时间
*/
@Tool(name="当前服务器时间", description = "当前服务器日期时间,格式为 yyyy-MM-dd HH:mm:ss。")
public String getCurrentDateTime() {
String dateTime = "当前日期时间:" + DateUtils.getDateTime();
logger.info("当前日期时间 ============== {}", dateTime);
return dateTime;
}
/**
* 开关房间的灯
*/
@Tool(
name = "房间灯光开关",
description = "控制指定房间的灯光开关。需要提供房间名称(如 '客厅'、'卧室'和目标状态true 表示开灯false 表示关灯)。"
)
public String roomLightSwitch(
@ToolParam(description = "要控制的房间名称,例如:'客厅'、'卧室'、'餐厅'、'厨房'") String roomName,
@ToolParam(description = "灯光目标状态true 表示打开灯false 表示关闭灯") boolean on) {
String message = roomName + " 房间里的灯被 " + (on ? "打开" : "关闭");
logger.info("房间灯光开关 ============== {}", message);
return String.format("""
{
"message": "%s",
"roomName": "%s",
"on": %s
}
""", message, roomName, on);
}
}

View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.modules.ai.tools;
import com.jeesite.common.lang.StringUtils;
import com.jeesite.common.mapper.JsonMapper;
import com.jeesite.common.mybatis.mapper.query.QueryType;
import com.jeesite.modules.sys.entity.EmpUser;
import com.jeesite.modules.sys.entity.User;
import com.jeesite.modules.sys.service.EmpUserService;
import com.jeesite.modules.sys.utils.UserUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* AI MCP 工具调用
* @author ThinkGem
*/
@Component
public class UserAITools {
private final Logger logger = LoggerFactory.getLogger(UserAITools.class);
private final EmpUserService empUserService;
public UserAITools(EmpUserService empUserService) {
this.empUserService = empUserService;
}
/**
* 获取当前会话的用户信息
*/
@Tool(name="当前用户信息", description = "无条件获取当前用户信息")
public String getCurrentUser(ToolContext toolContext) {
User currentUser = UserUtils.getUser();
if (StringUtils.isBlank(currentUser.getUserCode())) {
logger.info("当前用户信息 ============== 当前用户未登录。");
return "当前用户未登录。";
}
String result = JsonMapper.toJson(currentUser);
logger.info("当前用户信息 ============== 查询结果:{}", result);
return result;
}
/**
* 查询用户信息
*/
@Tool(name="查询用户信息", description = "根据用户名(登录账号)或员工姓名模糊查询用户信息。" +
"结果以表格形式展示包含用户名userName、姓名empUser、部门officeName等基本信息。")
public String findEmpUserInfo(ToolContext toolContext,
@ToolParam(description = "用户的登录名或员工的真实姓名,支持模糊匹配") String userName
) {
EmpUser where = new EmpUser();
where.sqlMap().getWhere().and(w -> w
.or("a.user_name", QueryType.LIKE, userName)
.or("e.emp_name", QueryType.LIKE, userName));
// 权限控制,只能查询当前用户能查询的用户信息
logger.info("获取用户信息 ============== 当前用户: {}", where.currentUser().getUserCode());
empUserService.addDataScopeFilter(where);
List<EmpUser> list = empUserService.findList(where);
String result = JsonMapper.toJson(list);
logger.info("获取用户信息 ============== 查询结果: {}", result);
if (list.isEmpty()) {
return "未找到符合条件的用户信息。";
}
return result;
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.modules.ai.tools.aspect;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* Tool 上下文用户信息注入切面
* @author ThinkGem
*/
@Aspect
@Component
public class ToolContextAspect {
@Around("@annotation(org.springframework.ai.tool.annotation.Tool)")
public Object handleThreadContext(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
ToolContext toolContext = null;
for (Object arg : args) {
if (arg instanceof ToolContext) {
toolContext = (ToolContext) arg;
break;
}
}
if (toolContext != null) {
Map<String, Object> context = toolContext.getContext();
if (context.containsKey("subject")) {
ThreadContext.bind((Subject) context.get("subject"));
}
}
return joinPoint.proceed();
}
}

View File

@@ -2,27 +2,26 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-root</artifactId>
<version>5.12.0.springboot3-SNAPSHOT</version>
<parent>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-parent-ai</artifactId>
<version>5.14.0.springboot3-SNAPSHOT</version>
<relativePath>../../parent/ai/pom.xml</relativePath>
</parent>
<artifactId>jeesite-module-ai</artifactId>
<packaging>pom</packaging>
<name>JeeSite Root</name>
<name>JeeSite Module AI</name>
<url>http://jeesite.com</url>
<inceptionYear>2013-Now</inceptionYear>
<modules>
<module>../parent</module>
<module>../common</module>
<module>../modules</module>
<module>../web</module>
<module>../web-ai</module>
<module>../web-api</module>
<module>../web-fast</module>
<module>../web-mini</module>
<module>ai-cms</module>
<module>ai-tools</module>
</modules>
<developers>
<developer>
<id>thinkgem</id>
@@ -32,10 +31,10 @@
<timezone>+8</timezone>
</developer>
</developers>
<organization>
<name>JeeSite</name>
<url>http://jeesite.com</url>
</organization>
</project>
</project>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-parent</artifactId>
<version>5.12.0.springboot3-SNAPSHOT</version>
<version>5.14.0.springboot3-SNAPSHOT</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
@@ -18,7 +18,8 @@
<inceptionYear>2013-Now</inceptionYear>
<dependencies>
<!-- 核心模块 --><!--suppress VulnerableLibrariesLocal -->
<dependency>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-module-core</artifactId>

View File

@@ -4,12 +4,6 @@
*/
package com.jeesite.modules.app.db;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import com.jeesite.common.config.Global;
import com.jeesite.common.tests.BaseInitDataTests;
import com.jeesite.modules.app.entity.AppComment;
@@ -17,6 +11,10 @@ import com.jeesite.modules.app.entity.AppUpgrade;
import com.jeesite.modules.app.service.AppCommentService;
import com.jeesite.modules.app.service.AppUpgradeService;
import com.jeesite.modules.gen.utils.GenUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 初始化APP表及数据
@@ -27,6 +25,11 @@ import com.jeesite.modules.gen.utils.GenUtils;
@ConditionalOnProperty(name="jeesite.initdata", havingValue="true", matchIfMissing=false)
public class InitAppData extends BaseInitDataTests {
public InitAppData(AppUpgradeService appUpgradeService, AppCommentService appCommentService) {
this.appUpgradeService = appUpgradeService;
this.appCommentService = appCommentService;
}
@Override
public boolean initData() throws Exception {
if (GenUtils.isTableExists(Global.getTablePrefix() + "app_upgrade")) {
@@ -41,8 +44,7 @@ public class InitAppData extends BaseInitDataTests {
return true;
}
@Autowired
private AppUpgradeService appUpgradeService;
private final AppUpgradeService appUpgradeService;
public void initAppUpgrade() throws Exception{
// clearTable(AppUpgrade.class);
initExcelData(AppUpgrade.class, params -> {
@@ -57,8 +59,7 @@ public class InitAppData extends BaseInitDataTests {
});
}
@Autowired
private AppCommentService appCommentService;
private final AppCommentService appCommentService;
public void initAppComment() throws Exception{
// clearTable(AppComment.class);
initExcelData(AppComment.class, params -> {

View File

@@ -23,8 +23,7 @@ public class AppCommentService extends CrudService<AppCommentDao, AppComment> {
/**
* 获取单条数据
* @param appComment
* @return
* @param appComment 主键
*/
@Override
public AppComment get(AppComment appComment) {
@@ -35,7 +34,6 @@ public class AppCommentService extends CrudService<AppCommentDao, AppComment> {
* 查询分页数据
* @param appComment 查询条件
* @param appComment page 分页对象
* @return
*/
@Override
public Page<AppComment> findPage(AppComment appComment) {
@@ -44,7 +42,7 @@ public class AppCommentService extends CrudService<AppCommentDao, AppComment> {
/**
* 保存数据(插入或更新)
* @param appComment
* @param appComment 查询条件
*/
@Override
@Transactional
@@ -56,7 +54,7 @@ public class AppCommentService extends CrudService<AppCommentDao, AppComment> {
/**
* 更新状态
* @param appComment
* @param appComment 数据对象
*/
@Override
@Transactional
@@ -66,7 +64,7 @@ public class AppCommentService extends CrudService<AppCommentDao, AppComment> {
/**
* 删除数据
* @param appComment
* @param appComment 数据对象
*/
@Override
@Transactional

View File

@@ -22,8 +22,7 @@ public class AppUpgradeService extends CrudService<AppUpgradeDao, AppUpgrade> {
/**
* 获取单条数据
* @param appUpgrade
* @return
* @param appUpgrade 主键
*/
@Override
public AppUpgrade get(AppUpgrade appUpgrade) {
@@ -34,7 +33,6 @@ public class AppUpgradeService extends CrudService<AppUpgradeDao, AppUpgrade> {
* 查询分页数据
* @param appUpgrade 查询条件
* @param appUpgrade page 分页对象
* @return
*/
@Override
public Page<AppUpgrade> findPage(AppUpgrade appUpgrade) {
@@ -43,7 +41,7 @@ public class AppUpgradeService extends CrudService<AppUpgradeDao, AppUpgrade> {
/**
* 保存数据(插入或更新)
* @param appUpgrade
* @param appUpgrade 数据对象
*/
@Override
@Transactional
@@ -53,7 +51,7 @@ public class AppUpgradeService extends CrudService<AppUpgradeDao, AppUpgrade> {
/**
* 更新状态
* @param appUpgrade
* @param appUpgrade 数据对象
*/
@Override
@Transactional
@@ -63,7 +61,7 @@ public class AppUpgradeService extends CrudService<AppUpgradeDao, AppUpgrade> {
/**
* 删除数据
* @param appUpgrade
* @param appUpgrade 数据对象
*/
@Override
@Transactional

View File

@@ -13,7 +13,6 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
@@ -33,9 +32,12 @@ import java.util.Date;
@RequestMapping(value = "${adminPath}/app/appComment")
public class AppCommentController extends BaseController {
@Autowired
private AppCommentService appCommentService;
private final AppCommentService appCommentService;
public AppCommentController(AppCommentService appCommentService) {
this.appCommentService = appCommentService;
}
/**
* 获取数据
*/

View File

@@ -4,21 +4,19 @@
*/
package com.jeesite.modules.app.web;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.jeesite.common.config.Global;
import com.jeesite.common.web.BaseController;
import com.jeesite.modules.app.entity.AppComment;
import com.jeesite.modules.app.entity.AppUpgrade;
import com.jeesite.modules.app.service.AppCommentService;
import com.jeesite.modules.app.service.AppUpgradeService;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
/**
* APP公共服务 Controller
@@ -29,12 +27,14 @@ import com.jeesite.modules.app.service.AppUpgradeService;
@RequestMapping(value = "/app")
public class AppPublicServiceController extends BaseController {
@Autowired
private AppUpgradeService appUpgradeService;
private final AppUpgradeService appUpgradeService;
private final AppCommentService appCommentService;
public AppPublicServiceController(AppUpgradeService appUpgradeService, AppCommentService appCommentService) {
this.appUpgradeService = appUpgradeService;
this.appCommentService = appCommentService;
}
@Autowired
private AppCommentService appCommentService;
/**
* 升级检测
*/

View File

@@ -4,11 +4,14 @@
*/
package com.jeesite.modules.app.web;
import com.jeesite.common.config.Global;
import com.jeesite.common.entity.Page;
import com.jeesite.common.web.BaseController;
import com.jeesite.modules.app.entity.AppUpgrade;
import com.jeesite.modules.app.service.AppUpgradeService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
@@ -17,12 +20,6 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.jeesite.common.config.Global;
import com.jeesite.common.entity.Page;
import com.jeesite.common.web.BaseController;
import com.jeesite.modules.app.entity.AppUpgrade;
import com.jeesite.modules.app.service.AppUpgradeService;
/**
* APP版本管理Controller
* @author ThinkGem
@@ -32,9 +29,12 @@ import com.jeesite.modules.app.service.AppUpgradeService;
@RequestMapping(value = "${adminPath}/app/appUpgrade")
public class AppUpgradeController extends BaseController {
@Autowired
private AppUpgradeService appUpgradeService;
private final AppUpgradeService appUpgradeService;
public AppUpgradeController(AppUpgradeService appUpgradeService) {
this.appUpgradeService = appUpgradeService;
}
/**
* 获取数据
*/

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