Compare commits

...

185 Commits

Author SHA1 Message Date
thinkgem
70dec92cf8 update README.md 2025-04-24 08:15:29 +08:00
thinkgem
e4d695ef93 代码生成模板类型错误 2025-04-23 17:24:58 +08:00
thinkgem
0b7d490a8a 5.11.1 2025-04-23 09:42:10 +08:00
thinkgem
75da09424a update README.md 2025-04-23 09:00:48 +08:00
thinkgem
d877b8d93b update README.md 2025-04-23 09:00:40 +08:00
thinkgem
d03ed31483 默认系统提示词,放到 yml 中配置,并完善注释 2025-04-21 19:41:52 +08:00
thinkgem
4c5c0e63d5 补充系统提示词 2025-04-21 16:32:59 +08:00
thinkgem
7c65d67b3e update 2025-04-21 16:32:35 +08:00
thinkgem
ea1744afda 健壮性增强 2025-04-21 13:40:20 +08:00
thinkgem
5ad7114d89 删除用不到的文件 2025-04-21 13:38:42 +08:00
thinkgem
1b8b6162f4 1. 主动停止的响应的消息仍然存储对话数据;2. 对话消息框当手动向上滚动的时候,停止滚动到底部,方便阅读已生成的消息;3. 消息内容,对 JSON 格式的数据,进行格式化显示;4. 支持“深度思考”按钮,可展开和折叠深度思考的消息;对 AI 的用户消息进行转义,避免不会支持的消息报错;5. 当 AI 接口调用异常的时候,给于用户提示实际的接口返回内容;6. 新增自动更新对话标题;6. 加载消息过程中,避免再次发送新消息和切换对话。 2025-04-21 13:24:23 +08:00
thinkgem
695762b34c 升级 spring ai 1.0.0-M7 2025-04-21 13:23:08 +08:00
thinkgem
8f2444fdbb 新增 web-ai 项目,方便研发 2025-04-21 13:22:44 +08:00
thinkgem
47b3d918eb 界面优化 2025-04-19 09:58:42 +08:00
thinkgem
79ab80062d add ai menu 2025-04-18 19:30:16 +08:00
thinkgem
f4c5910758 整理初始化数据库脚本菜单,扩展模块移到扩展菜单里。 2025-04-18 15:38:35 +08:00
thinkgem
661fea7d29 update Dockerfile 2025-04-17 16:15:53 +08:00
thinkgem
b8a6f215e6 update docker-build.sh 2025-04-17 14:00:37 +08:00
thinkgem
77b38b31d9 调整属性文件加载顺序,classpath:application.yml 优先于 classpath:config/application.yml(升级注意) 2025-04-17 09:07:50 +08:00
thinkgem
5580deb796 update h2 2025-04-17 09:06:23 +08:00
thinkgem
70bb0c1d57 代码优化 2025-04-17 09:05:55 +08:00
thinkgem
731599ac70 优化界面 2025-04-15 12:43:27 +08:00
thinkgem
adae0af8d6 表格操作列图标更新、默认按钮美化、表格设置按钮更新 2025-04-14 12:59:09 +08:00
thinkgem
00a260028f 弹窗消息透明度修改为0.9 2025-04-14 12:55:31 +08:00
thinkgem
3e0a5abcda 新增 Markdown 编辑器,文件上传接口 2025-04-09 15:07:55 +08:00
thinkgem
228b507f12 增加文章附件解析,存入向量数据库。 2025-04-08 15:38:25 +08:00
thinkgem
e69cc355b3 update ds_pgvector 2025-04-08 15:37:45 +08:00
thinkgem
9684e75697 HttpClientUtils增加文件流下载方法 2025-04-08 15:35:59 +08:00
thinkgem
48e639f5d4 update PropertiesUtils 2025-04-07 18:39:24 +08:00
thinkgem
c0e40dfab0 remove SchemeHttpsConfig 2025-04-07 18:38:42 +08:00
thinkgem
1f7ddcf1cc 支持 dataSourceNames 免配置,自动读取 jdbc 的属性来加载多数据源 2025-04-07 18:37:04 +08:00
thinkgem
cac28a3d57 代码生成模板中增加重启流程操作 2025-04-07 12:05:03 +08:00
thinkgem
282050262c webp格式上传,默认参数少个点 2025-04-07 11:54:13 +08:00
thinkgem
b2545d4ae2 启用岗位角色权限权限后,给予操作提示 2025-04-04 17:19:00 +08:00
thinkgem
7a2850c3ff 增加参数:登录后激活当前用户所在的主部门,默认为不激活即混合权限 2025-04-04 10:48:36 +08:00
thinkgem
6715af11da 优化 DataScope 数据权限,移除 apiMode 参数,增加 apiMode 方法;
优化 DataScope 数据权限,增加 debug、joinMode、ruleSql 方法,方便动态调用
优化 扩展查询 SQL 实现,使用 sqlMap().getColumn().addExtSql 替代 extColumnKeys;
优化 扩展查询 SQL 实现,使用 sqlMap().getTable().addExtSql 替代 extFormKeys;
优化 扩展查询 SQL 实现,使用 sqlMap().getWhere().addExtSql 替代 extWhereKeys;
新增 扩展更新 SQL 语句,sqlMap().getUpdate().addExtColumnSql 和 addExtWhereSql
2025-04-02 16:39:01 +08:00
thinkgem
c0637ebf0d 优化 SqlMap 性能,增加 update 和 insert 生成参数接口,方便在 Mapper 中直接引用。 2025-04-01 11:57:35 +08:00
thinkgem
9bc8d5dd55 update title 2025-03-31 11:27:34 +08:00
thinkgem
38a56ab404 优化树表性能,左树右表联动时,减少一次查询 2025-03-31 09:42:28 +08:00
thinkgem
029f6906e4 查询条件如区域、公司、配置、机构、岗位、栏目、站点、分类等显示编码使用 like 查询,等其它功能查询细节优化 2025-03-30 15:04:05 +08:00
thinkgem
d2b790702b 优化树表管理代码生成模板,左树右表代码生成模板 2025-03-29 22:22:03 +08:00
thinkgem
f1a3dab555 /test/testTree/list to /test/testTree/index 2025-03-29 22:08:16 +08:00
thinkgem
cf7f27d3b0 EncodeUtils增加手机号和邮箱的掩码处理方法 2025-03-29 22:05:34 +08:00
thinkgem
2905763862 testTree增加index,左树快捷选择 2025-03-29 22:04:49 +08:00
thinkgem
0e0ee1d7d3 Tool Calling、Function Calling 2025-03-29 11:31:44 +08:00
thinkgem
5b6d7de04a 支持机构子节点数据的机构负责人和电话的模糊查询 2025-03-29 11:15:47 +08:00
thinkgem
cd5af0ab78 更正机构导出模板的显示标题 2025-03-29 11:14:32 +08:00
thinkgem
2931cc52bb 新增 Tool calling,让 AI 调用你的 java 实现你的业务联动 2025-03-29 11:08:35 +08:00
thinkgem
52c62be658 公司和部门管理列表,根据代码模糊查询时,可以查出子节点 2025-03-28 10:22:01 +08:00
thinkgem
6324fa15ae HttpClientUtils 使用 jdk 自带的 http5 替代 apache httpclient 工具 2025-03-28 10:21:37 +08:00
thinkgem
c7cc1343c8 优化外部调用接口调用测试类 2025-03-27 14:15:30 +08:00
thinkgem
11524982e5 EncodeUtils.encodeUrl 忽略字符编码传递空值,默认 UTF-8 2025-03-27 14:14:31 +08:00
thinkgem
54656cb441 点击右上角【Star】收藏本软件 ^_^ 2025-03-27 09:06:52 +08:00
thinkgem
05fc17bd5e next version 2025-03-27 08:59:25 +08:00
thinkgem
97eb30fc11 update README.md 2025-03-25 14:15:51 +08:00
thinkgem
3fda79ec8d update README.md 2025-03-25 13:23:43 +08:00
thinkgem
a08a4cf06c 5.11.0 2025-03-24 14:50:19 +08:00
thinkgem
0264dec97b 新增 sys.office.notAllowDeleteIfUserExists 和 sys.company.notAllowDeleteIfUserExists 参数,增加删除机构和公司数据的时候校验是否有用户 2025-03-24 14:03:58 +08:00
thinkgem
500cbf8268 清理组织和公司下的用户缓存,包含子机构 2025-03-24 14:02:46 +08:00
thinkgem
82732c2993 update README.md 2025-03-24 13:59:55 +08:00
thinkgem
69a1941be6 update productVersion 2025-03-24 13:59:00 +08:00
thinkgem
80ffeac471 update 2025-03-22 18:51:47 +08:00
thinkgem
8826dc3259 update README.md 2025-03-22 18:51:19 +08:00
thinkgem
b87fb6a07f 网站优化一些基础样式 2025-03-22 18:48:46 +08:00
thinkgem
e9ba5b96f2 文章按权重排序修复 2025-03-22 18:48:23 +08:00
thinkgem
0ee7337789 AI 增加 Chroma 向量库 2025-03-20 21:30:49 +08:00
thinkgem
1356893d4e update 2025-03-20 12:07:15 +08:00
thinkgem
080b48559f 新增CMS+RAG+AI知识库模块/向量数据库检索增强生成及人工智能对话 2025-03-19 12:39:51 +08:00
thinkgem
af61f14a9a add job init remarks 2025-03-17 15:46:01 +08:00
thinkgem
b53e257085 增加首列title属性 2025-03-13 12:58:08 +08:00
thinkgem
988eac224d update README.md 2025-03-11 22:21:50 +08:00
thinkgem
186d18c160 新增内容管理数据到向量数据库的集成接口 2025-03-11 15:27:11 +08:00
thinkgem
132f814c2d 属性获取工具,支持从系统环境变量中获取参数 2025-03-11 15:22:55 +08:00
thinkgem
f8736420de update README.md 2025-03-11 15:22:34 +08:00
thinkgem
6dca1ded69 jeesite-*.yml模块配置文件,增加IDEA提示助手 2025-03-08 10:11:30 +08:00
thinkgem
0b3b0731ff 修正代码生成微服务模板缺少配置文件问题 2025-03-07 16:23:08 +08:00
thinkgem
5bcc66d3f7 update 2025-03-04 13:03:00 +08:00
thinkgem
c14f464c3f 修正安全管理器限制的本地调用 2025-03-04 09:50:57 +08:00
thinkgem
75e6ccf888 账号找回密码增加租户参数 2025-03-02 21:03:07 +08:00
thinkgem
3bf68070fe 登录界面租户框调整到上面 2025-02-28 16:55:07 +08:00
thinkgem
0374499e8a update config 2025-02-28 13:35:17 +08:00
thinkgem
0b6a1157cc 个人中心代码优化 2025-02-26 14:19:19 +08:00
thinkgem
d76f991a9f next 2025-02-24 09:25:02 +08:00
thinkgem
00ec4d17c9 新增字段权限接口,过滤 select 返回值,update 和 insert 字段值 2025-02-21 15:52:06 +08:00
thinkgem
de33f20797 增加更严格的权限控制,对单条数据进行数据权限过滤 2025-02-21 15:47:01 +08:00
thinkgem
5f56575df4 AppComment合并更新状态方法 2025-02-21 15:45:14 +08:00
thinkgem
4cade5fa31 优化 编辑公司时,清理公司下的用户缓存 2025-02-20 17:50:02 +08:00
thinkgem
de2c13279b 优化 编辑机构时,清理机构下的用户缓存 2025-02-20 17:49:51 +08:00
thinkgem
87116f0fc7 新增 findUserListByCompanyCodes 方法,根据公司编码获取用户编码和名称 2025-02-20 17:48:48 +08:00
thinkgem
3e68536eea fileupload bizType参数为空时,readonly不生效问题 2025-02-20 17:47:50 +08:00
thinkgem
6f55139127 update README.md 2025-02-17 10:49:27 +08:00
thinkgem
4407d979e6 点击右上角【Star】收藏本软件 ^_^ 2025-02-17 10:48:51 +08:00
thinkgem
ddac21b170 5.10.1 2025-02-15 23:18:09 +08:00
thinkgem
94c5ad6e06 5.10.1 2025-02-15 21:17:12 +08:00
thinkgem
d429dde8c5 自定义数据权限分配树,默认展开等级设为1 2025-02-15 16:41:29 +08:00
thinkgem
9c5db77a65 登录后不指定默认当前部门编号 2025-02-15 16:40:28 +08:00
thinkgem
dddad4d113 更新 visual 类名 2025-02-14 22:04:00 +08:00
thinkgem
0ff7e3bf8f 添加 文档接口响应参数 2025-02-13 18:36:35 +08:00
thinkgem
bfc9953b32 新增 条件规则数据权限、自定义SQL数据权限,暂存 2025-02-12 18:40:12 +08:00
thinkgem
4a3604169c 优化 二级管理员参数提示信息 2025-02-12 18:17:06 +08:00
thinkgem
8630ec6e71 remove aria-hidden="true" 2025-02-12 18:16:01 +08:00
thinkgem
f063d6327b 新增 按菜单进行数据权限授权,后端通过addFilterByPermission方法调用权限。 2025-02-11 14:29:07 +08:00
thinkgem
8b97b011f8 新增 user.dataScopeJoinMode 参数,默认关闭 JOIN 模式的数据权限,如有需要可打开此参数 2025-02-11 12:50:46 +08:00
thinkgem
04cbc2a4fe 新增 列名 columnName 正则表达式的安全过滤 2025-02-11 12:46:53 +08:00
thinkgem
46fd24c819 update 2025-02-11 12:44:10 +08:00
thinkgem
46bdef5058 增加默认值 2025-02-07 20:44:22 +08:00
thinkgem
1fd1b3310f 更新配置文件 2025-02-07 17:22:55 +08:00
thinkgem
cc9add884d 代码优化 EmpUtils 增加 getCompanyCodes、getCompanyParentCodess 方法 2025-02-07 17:11:11 +08:00
thinkgem
b59d91ea4e 将 router.push 替换为 useGo 增加代码健壮性 2025-02-07 16:03:33 +08:00
thinkgem
9e728c5608 add ObjectUtilsTest.java 2025-02-05 21:10:23 +08:00
thinkgem
05185bb29a 修正String转Long丢失精度问题 2025-02-05 21:10:00 +08:00
thinkgem
35c6dabab3 update config 2025-02-05 20:54:12 +08:00
thinkgem
4de0934e7f 新增 切换部门功能、多部门岗位权限 2025-02-01 16:22:15 +08:00
thinkgem
b12c010cd3 优化 查询当前员工关联的岗位信息和附属机构信息接口 2025-01-28 11:47:05 +08:00
thinkgem
16ba930729 新增 获取当前用户岗位列表 EmpUtils.getEmployeePostList 缓存接口 2025-01-28 11:45:26 +08:00
thinkgem
f717a5230a 新增 岗位查询角色接口新增 postCode_in 查询条件 2025-01-28 11:44:18 +08:00
thinkgem
e47cc42da8 丰富 EmployeeOffice 返回结果 2025-01-28 11:43:00 +08:00
thinkgem
ca0fb1d2cb 新增 form:treeselect 组件 fieldNames 属性,自定义数据源的字段名 2025-01-28 11:41:09 +08:00
thinkgem
f027edab9a 从LoginController分离出SwitchController类 2025-01-27 23:42:56 +08:00
thinkgem
a578a70a9b 修正 sqlserver 分页时,缺少 order by 的问题 2025-01-21 15:37:29 +08:00
thinkgem
9d2d00bc89 优化 切换当前身份时,自动对应身份的桌面地址 2025-01-20 17:31:16 +08:00
thinkgem
8a9739c362 新增 RSA 非对称加密工具 2025-01-16 17:36:21 +08:00
thinkgem
ad25e79f31 update README.md 2025-01-16 17:27:00 +08:00
thinkgem
b45cabbd25 format code 2025-01-14 10:37:34 +08:00
thinkgem
e9bc20dd9b update MsgPushTest 2025-01-13 15:09:52 +08:00
thinkgem
d18028a247 update README.md 2025-01-10 14:00:47 +08:00
thinkgem
2ace98d3df upgrade h2 to 2.3.232 2025-01-08 17:03:39 +08:00
thinkgem
99f908b338 update useless code 2025-01-08 17:02:56 +08:00
thinkgem
05873ff46d update README.md 2025-01-06 11:22:07 +08:00
thinkgem
a2e7f3f640 mybatis 3.5.16、mybatis spring 3.0.4 2025-01-06 10:26:40 +08:00
thinkgem
24c7d20531 next version 2025-01-06 09:42:21 +08:00
thinkgem
812e3f2a28 自定义 InitBinder 不进行绑定 Header 中的数据 2025-01-04 09:41:19 +08:00
thinkgem
2c59192482 update area data 2025-01-01 19:44:18 +08:00
thinkgem
f31810325a 5.10.0 2024-12-31 11:27:32 +08:00
thinkgem
ac47ac128d update import icon 2024-12-31 10:30:06 +08:00
thinkgem
71ebba0b8f 2025 2024-12-30 14:57:04 +08:00
thinkgem
58e3c0f6e1 update h2db 2024-12-29 16:45:27 +08:00
thinkgem
6350b7d87c update README.md 2024-12-29 16:45:02 +08:00
thinkgem
647c17c350 update bin 2024-12-26 11:43:10 +08:00
thinkgem
79c1213391 上传文件必填时,防止表单验证提示输入历史 2024-12-25 16:20:51 +08:00
thinkgem
49967508f4 update README.md 2024-12-25 15:50:16 +08:00
thinkgem
65b3cb19be update jdbc url 2024-12-25 10:30:54 +08:00
thinkgem
fa1107737a update yml config 2024-12-24 00:15:54 +08:00
thinkgem
f4d3b2016e spring boot 3.4.0 2024-12-23 17:35:32 +08:00
thinkgem
719ad508d0 remove beetl includeJSP 2024-12-23 17:14:12 +08:00
thinkgem
a43f65f4b8 add fury serialize 2024-12-23 17:12:34 +08:00
thinkgem
542c3ff07b update README.md 2024-12-13 12:59:09 +08:00
thinkgem
1dbe3ae0ee update README.md 2024-12-13 10:19:25 +08:00
thinkgem
235966b4a8 增加参考配置,可查看哪些用户的数据,ctrlPermi 更改为 0 后显示 2024-12-11 10:32:41 +08:00
thinkgem
b6e69ea0c3 细节优化,控制业务范围重命名为业务范围 2024-12-11 10:27:57 +08:00
thinkgem
4a233121c9 移除重复参数 2024-12-11 10:23:18 +08:00
thinkgem
23b14b4517 新增BigDecimalType 2024-12-05 11:12:00 +08:00
thinkgem
27254d09b3 update 2024-12-05 11:11:51 +08:00
thinkgem
f2f75aceee update version 2024-12-04 14:30:09 +08:00
thinkgem
bd8bd10ed5 使用对象存储时,文件管理的在线预览文件中的下载地址修正 2024-12-04 14:17:35 +08:00
thinkgem
6984e97cd2 update 2024-11-26 20:10:09 +08:00
thinkgem
ea2a3193ea update README.md 2024-11-24 20:36:58 +08:00
thinkgem
290e8ec31e 5.9.2 2024-11-23 09:55:58 +08:00
thinkgem
30039a35bb update 2024-11-21 09:49:06 +08:00
thinkgem
14f57be279 更新注册提示信息 2024-11-13 17:16:45 +08:00
thinkgem
207897e7be update 2024-11-13 13:47:51 +08:00
thinkgem
df728bc3d6 切换系统时,清除当前岗位和角色状态 2024-11-12 10:55:08 +08:00
thinkgem
3333ad2e10 设备信息字段 device_info 长度调整为 4000 2024-11-11 17:11:33 +08:00
thinkgem
83752a77cb spring boot 3.3.5 2024-11-08 09:52:44 +08:00
thinkgem
14437dfb7a 登录后重定向地址验证,如果是非法地址,则指定默认的登录成功地址 2024-11-07 17:53:43 +08:00
thinkgem
ce1c06eef2 update README.md 2024-11-07 16:13:30 +08:00
thinkgem
a98f8a816a update description 2024-11-07 16:13:26 +08:00
thinkgem
5d16bb8103 update version 2024-11-07 15:28:16 +08:00
thinkgem
48dbec081d Vue登录界面useCorpModel更改为loginCodeCorpUnique 2024-11-07 15:15:48 +08:00
thinkgem
f9648bcaf8 新增 窗口右上角的头像下拉菜单,切换当前用户所属岗位,并联动切换当前菜单权限 2024-11-07 10:26:27 +08:00
thinkgem
68a7c395e0 提升登录性能;支持普通用户升级为超管用户。 2024-11-07 10:22:32 +08:00
thinkgem
0b4bb63dbe 优化 MenuDao.xml 接口 findByUserCode 替换为 findByRoleCodes 升级时注意 2024-11-07 10:19:25 +08:00
thinkgem
03c5d4a051 新增 当前用户 user.getRoleList() 支持返回 岗位关联角色数据 2024-11-07 10:19:13 +08:00
thinkgem
380ee54ae5 新增 岗位关联角色(用户->岗位->角色)权限控制,可通过 user.postRolePermi=true 参数开启 2024-11-07 10:17:24 +08:00
thinkgem
6e3b93f645 增加岗位切换接口;优化登录菜单权限查询,直接通过当前用户角色查询,减少联表,提升查询效率。 2024-11-07 10:15:59 +08:00
thinkgem
3eaa799455 优化 增加岗位保存时清理关联的用户缓存 2024-11-07 10:14:32 +08:00
thinkgem
283d27d0df 优化 超级管理员作为普通员工时的角色数据查询,允许修改超级管理员员工信息,不允许停用禁用删除超管 2024-11-07 10:12:54 +08:00
thinkgem
8c86d1534c 增加手机端单表代码生成模板 2024-11-05 11:26:05 +08:00
thinkgem
40054e4c6e quartz 2.4.0.rc3 2024-11-04 18:14:14 +08:00
thinkgem
2981699bd1 update 2024-11-04 17:52:11 +08:00
thinkgem
3484d93a42 jackson 2.18.1 2024-11-04 17:36:31 +08:00
thinkgem
9a70ddb758 优化 mvc 接受字段类型不一致信息显示,方便调试 2024-11-01 19:59:51 +08:00
thinkgem
805759db01 调整代码生成模板目录 2024-11-01 10:44:03 +08:00
thinkgem
481246f52e 优化表单内表格的行高和边距 2024-11-01 10:43:32 +08:00
thinkgem
ba077ecd41 update 2024-10-25 19:07:18 +08:00
329 changed files with 15909 additions and 7075 deletions

View File

@@ -1,23 +1,25 @@
### 是什么问题、该问题是怎么引起的?
1.
1. 这里替换为你的问题
### 重现步骤、期望结果、截图、代码
1.
1. 这里替换为你的操作步骤,期望结果
```
这里贴你的代码块
```
### 实际结果、报错信息、截图
1.
1. 这里替换为你的操作步骤的实际结果
```
这里贴错误信息
这里贴你的错误信息
```
### 环境版本:
- JDK版本1.8、11、17
- JDK版本1.8、11、17、21
- 浏览器版本Chrome xx、Firefox xx、IE xx
- 平台版本JeeSite 4.x.x、5.x.xpom.xml里查看

13
LICENSE
View File

@@ -206,15 +206,16 @@
1. 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款。
2. 不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
3. 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议、版权声明和其他原作者
3. 在使用本软件时,由于它集成了众多第三方开源软件,请共同遵守这些开源软件的使用许可条款规定。
4. 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议、版权声明和其他原作者
规定需要包含的说明(请尊重原作者的著作权,不要删除或修改文件中的`Copyright`和`@author`信息)
更不要,全局替换源代码中的 jeesite 或 ThinkGem 等字样,否则你将违反本协议条款承担责任。
4. 您若套用本软件的一些代码或功能参考,请保留源文件中的版权和作者,需要在您的软件介绍明显位置
5. 您若套用本软件的一些代码或功能参考,请保留源文件中的版权和作者,需要在您的软件介绍明显位置
说明出处,举例:本软件基于 JeeSite 快速开发平台并附带链接http://jeesite.com
5. 任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
6. 如果你对本软件有改进,希望可以贡献给我们,共同进步。
7. 本项目已申请软件著作权,请尊重开源,感谢阅读。
8. 无用户数限制,无在线人数限制,放心使用。
6. 任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
7. 如果你对本软件有改进,希望可以贡献给我们,共同进步。
8. 本项目已申请软件著作权,请尊重开源,感谢阅读。
9. 无用户数限制,无在线人数限制,放心使用。
版权所有:济南卓源软件有限公司

117
README.md
View File

@@ -4,10 +4,13 @@
</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://jeesite.com/docs/upgrade/" target="__blank"><img alt="JeeSite-5.9" src="https://img.shields.io/badge/JeeSite-V5.9-success.svg"></a>
<a href="https://spring.io/projects/spring-boot#learn" target="__blank"><img alt="SpringBoot-2.7/3.2" src="https://img.shields.io/badge/SpringBoot-2.7/3.2-blue.svg"></a>
<a href="https://gitee.com/thinkgem/jeesite5/stargazers" target="__blank"><img alt="star" src="https://gitee.com/thinkgem/jeesite5/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/thinkgem/jeesite-vue/stargazers" target="__blank"><img alt="star" src="https://gitee.com/thinkgem/jeesite-vue/badge/star.svg?theme=dark"></a>
<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://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>
<a href="https://gitee.com/thinkgem/jeesite-vue" target="__blank"><img alt="star" src="https://gitee.com/thinkgem/jeesite-vue/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/thinkgem/jeesite" target="__blank"><img alt="star" src="https://gitee.com/thinkgem/jeesite/badge/star.svg?theme=gvp"></a>
<a href="https://gitcode.com/thinkgem/jeesite" target="__blank"><img alt="star" src="https://gitcode.com/thinkgem/jeesite/star/badge.svg"></a>
</p>
------
@@ -24,28 +27,40 @@
* 使用文档:<https://jeesite.com/docs>
* 问题反馈:<http://jeesite.net> [【新手必读】](https://gitee.com/thinkgem/jeesite5/issues/I18ARR)
* 需求收集:<https://gitee.com/thinkgem/jeesite5/issues/new>
* QQ 群:`127515876``209330483``223507718``709534275``730390092``1373527``183903863(外包)`
* 微信群:添加客服微信 <http://s.jeesite.com> 邀请您进群
* 联系我们:<http://s.jeesite.com>
* 关注微信公众号,了解最新动态:
<p style="padding-left:40px">  
<img alt="JeeSite微信公众号" src="https://jeesite.com/assets/images/mp.png" width="200">
</p>
* 源码仓库地址1<https://gitee.com/thinkgem/jeesite5>
* 源码仓库地址2<https://github.com/thinkgem/jeesite5>
* 源码仓库地址3<https://gitcode.com/thinkgem/jeesite5>
* 分离版前端源码仓库地址1<https://gitee.com/thinkgem/jeesite-vue>
* 分离版前端源码仓库地址2<https://github.com/thinkgem/jeesite-vue>
* 分离版前端源码仓库地址3<https://gitcode.com/thinkgem/jeesite-vue>
* QQ 群:`127515876``209330483``223507718``709534275``730390092``1373527``183903863(外包)`
* 微信群:如果二维码过期,请尝试刷新图片,或者添加客服微信 jeesitex 邀请您进群
<p style="padding-left:40px"><a href="https://jeesite.com/assets/images/wxg_cur.png" target="_blank">  
<img alt="JeeSite微信群" src="https://jeesite.com/assets/images/wxg_cur.png" width="200"/></a>
</p>
* 源码仓库地址:
[Gitee](https://gitee.com/thinkgem/jeesite5)、
[GitCode](https://gitcode.com/thinkgem/jeesite5)、
[GitHub](https://github.com/thinkgem/jeesite5)
* 分离版源码仓库地址:
[Gitee](https://gitee.com/thinkgem/jeesite-vue)、
[GitCode](https://gitcode.com/thinkgem/jeesite-vue)、
[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)、
[GitHub](https://github.com/thinkgem/jeesite/tree/v5.springboot3)
## 平台介绍
* JeeSite 快速开发平台,不仅仅是一个后台开发框架,它是一个企业级快速开发解决方案,后端基于经典组合 Spring Boot、Shiro、MyBatis前端采用 Beetl、Bootstrap、AdminLTE 经典开发模式,或者分离版 Vue3、Vite、Ant Design Vue、TypeScript、Vben Admin 最先进技术栈。
* JeeSite 快速开发平台,低代码,轻量级,不仅仅是一个后台开发框架,它是一个企业级快速开发解决方案,后端基于经典组合 Spring Boot、Shiro、MyBatis前端采用分离版 Vue3、Vite、Ant Design Vue、TypeScript、Vben Admin 最先进技术栈,或者 Beetl、Bootstrap、AdminLTE 经典开发模式
* 提供在线数据源管理、数据表建模、代码生成等功能,可自动创建业务模块代码工程和微服务模块代码工程,自动生成前端代码和后端代码;包括核心功能模块如:组织机构、用户、角色、岗位、管理员、权限审计、菜单及按钮权限、数据权限、模块管理、系统参数、字典管理、系统监控、数据监控等;扩展功能如:工作流引擎、内容管理、消息推送、单点登录、第三方登录、在线作业调度、对象存储、可视化数据大屏、报表设计器、在线文件预览、国际化、全文检索、统一认证服务等。
* 本平台采用松耦合设计,微内核和插件架构,模块增减便捷,支持集群,支持 SaaS 架构,支持读写分离、分库分表、Spring Cloud 微服务架构;并内置了众多账号安全设置、密码策略、系统访问限制等安全解决方案,支持等保评测。
* 本平台采用松耦合设计,真正的轻量级,微内核,快速部署,插件架构,模块增减便捷,支持扩展 SaaS 架构、集群部署、读写分离、分库分表、Spring Cloud 微服务架构;并内置了众多账号安全设置、密码策略、系统访问限制等安全解决方案,支持等保评测。
* 本平台专注于为初级研发人员提供强大的支持,使他们能够高效、快速地开发出复杂的业务功能,同时为中高级人员腾出宝贵的时间,专注于更具战略性和创新性的任务。我们致力于让开发者能够全心投入业务逻辑中,而将繁琐的技术细节交由平台来封装处理。这不仅降低了技术实现的难度,还确保了系统架构的稳定性和安全性,进而帮助企业节省人力成本、缩短项目周期,并提高整体软件的安全性和质量。
@@ -55,13 +70,15 @@
* 2021 年终发布 Vue3 的前后分离版本,使得 JeeSite 拥有同一个后台服务 Web 来支撑分离版和全栈版两套前端技术栈。
* 对接常见 AI 大模型OpenAPI、Ollama、DeepSeek等支持检索增强生成 RAG 技术,实现企业知识库智能对话。
* 支持国产化软硬件环境,如国产芯片、操作系统、数据库、中间件、国密算法等。
## 核心优势
* JeeSite 非常易于二次开发,可控性高,整体架构清晰、技术稳定而先进、源代码书写规范、经典技术会的人多、易于维护、易于扩展、安全稳定。
* JeeSite 功能全,知识点非常多,也非常少。因为她使用的都是一些通用的技术,通俗的设计风格,大多数基础知识点,多数人都能掌握,所以每一个 JeeSite 的功能点都非常容易掌握。只要您学会使用这些功能和组件的应用,就可以顺利完成系统开发了。
* JeeSite 功能全,知识点非常多,也非常少。因为她使用的都是一些通用的技术,通俗的设计风格,大多数基础知识点,多数人都能掌握,所以每一个 JeeSite 的功能点都非常容易掌握。只要您学会使用这些功能和组件的应用,就可以顺利完成系统开发了。
* JeeSite 是一个低代码开发平台具有较高的封装度、扩展性封装不是限制您去做一些事情而是在便捷的同时也具有较好的扩展性在不具备一些功能的情况下JeeSite 提供了扩展接口,提供了原生调用方法。
@@ -80,17 +97,17 @@
## 技术选型
* 主框架Spring Boot 3.1、Spring Framework 6.0、Apache Shiro 2.0、J2Cache
* 持久层Apache MyBatis 3.5、Hibernate Validator 8.0、Alibaba Druid 1.2
* 视图层Spring MVC 6.0、Beetl 3.10替换JSP、Bootstrap 3.3、AdminLTE 2.4
* 前端组件jQuery 3.7、jqGrid 4.7、layer 3.5、zTree 3.5、jQuery Validation
* 分离前端版Node.js、TypeScript、Vue3、Vite、Ant Design Vue、Vue Vben Admin
* 工作流引擎Flowable 7.0、符合 BPMN 规范、在线流程设计器、中国式流程、退回、撤回、自由流
* Bootstrap 版 支持 IE9 及以上版本及其他所有现代浏览器,如:谷歌、火狐、国产浏览器 等
* Vue3 版 支持现代浏览器,如:谷歌 Chrome 86+、火狐、国产浏览器 等
* 技术选型(详细)<http://jeesite.com/docs/technology/>
* JeeSite Vue 版本:<https://gitee.com/thinkgem/jeesite-vue>
* Spring Boot 2.x 版本:<https://gitee.com/thinkgem/jeesite5/tree/v5.9>
* 主框架Spring Boot 3.4、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、火狐、国产浏览器 等
* 经典版:支持 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/>
## 更多介绍
@@ -102,6 +119,7 @@
## 生态系统
* 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/>
@@ -118,16 +136,16 @@
### 在线演示
1. 全栈版地址:<http://demo.jeesite.com>
2. Vue3分离版地址:<http://vue.jeesite.com>
1. 分离版地址:<https://vue.jeesite.com>
2. 经典版地址:<https://demo.jeesite.com>
### 本地运行
1. 环境准备:`JDK 17+``Maven 3.8+`、使用 `MySQL 5.7 or 8.x` 数据库、[其它数据库](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` 初始化数据库
5. 执行脚本:`/web/bin/run-tomcat.bat` 启动服务即可
3. 打开文件:`/web/src/main/resources/config/application.yml` 配置JDBC连接(建立一个新库)
4. 执行脚本:`/web/bin/init-data.bat(sh)` 初始化数据库(自动往新库里创建表和初始数据)
5. 执行脚本:`/web/bin/run-tomcat.bat(sh)` 启动服务即可
6. 浏览器访问:<http://127.0.0.1:8980/js> 账号 system 密码 admin
7. 部署常见问题:<https://jeesite.com/docs/faq/>
8. 分离端安装:<https://jeesite.com/docs/vue-install-deploy/>
@@ -136,7 +154,7 @@
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` 启动服务即可(自动初始化库)
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
@@ -144,7 +162,7 @@
### 容器运行
- 拉取 Docker 镜像(演示使用,JeeSite版本较久
- 拉取 Docker 镜像JeeSite版本不是最新
```sh
docker pull thinkgem/jeesite-web
```
@@ -160,7 +178,8 @@ docker run --name jeesite-web -p 8980:8980 -d --restart unless-stopped \
1. 部署运行文档:<https://jeesite.com/docs/install-deploy/>
2. 部署常见问题:<https://jeesite.com/docs/faq/>
3. 分离端安装<https://jeesite.com/docs/vue-install-deploy/>
3. 分离端运行文档<https://jeesite.com/docs/vue-install-deploy/>
4. 分离端常见问题:<https://jeesite.com/docs/vue-jeesite-vue/>
## 技术文章
@@ -169,9 +188,19 @@ docker run --name jeesite-web -p 8980:8980 -d --restart unless-stopped \
* 表结构数据字典:<https://jeesite.com/docs/code-gen/#表结构数据字典>
* 持久层设计:<https://jeesite.com/docs/dao-mybatis/>
* 后端工具:<https://jeesite.com/docs/sys-utils/>
**分离版**
* 源码解析:<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-comp/>
**经典版**
* 表单组件:<https://jeesite.com/docs/views-beetl/>
* 表格组件:<https://jeesite.com/docs/datagrid/>
* js工具:<https://jeesite.com/docs/jeesite-js/>
* 常用工具:<https://jeesite.com/docs/jeesite-js/>
## 专题文章
@@ -218,20 +247,22 @@ docker run --name jeesite-web -p 8980:8980 -d --restart unless-stopped \
1. 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款。
2. 不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
3.延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议、版权声明和其他原作者
3.使用本软件时,由于它集成了众多第三方开源软件,请共同遵守这些开源软件的使用许可条款规定。
4. 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议、版权声明和其他原作者
规定需要包含的说明(请尊重原作者的著作权,不要删除或修改文件中的`Copyright``@author`信息)
更不要,全局替换源代码中的 jeesite 或 ThinkGem 等字样,否则你将违反本协议条款承担责任。
4. 您若套用本软件的一些代码或功能参考,请保留源文件中的版权和作者,需要在您的软件介绍明显位置
5. 您若套用本软件的一些代码或功能参考,请保留源文件中的版权和作者,需要在您的软件介绍明显位置
说明出处,举例:本软件基于 JeeSite 快速开发平台并附带链接http://jeesite.com
5. 任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
6. 如果你对本软件有改进,希望可以贡献给我们,共同进步。
7. 本项目已申请软件著作权,请尊重开源,感谢阅读。
8. 无用户数限制,无在线人数限制,放心使用。
6. 任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
7. 如果你对本软件有改进,希望可以贡献给我们,共同进步。
8. 本项目已申请软件著作权,请尊重开源,感谢阅读。
9. 无用户数限制,无在线人数限制,放心使用。
## 技术服务与支持
## 技术支持与服务
* 本软件免费,我们也提供了相应的收费服务,因为:
* 没有资金的支撑就很难得到发展,特别是一个好的产品,如果 JeeSite 帮助了您,请为我们点赞。支持我们,您可以获得更多回馈,我们会把公益事业做的更好,开放更多资源,回报社区和社会。请给我们一些动力吧,在此非常感谢已支持我们的朋友!
* **联系我们**:请访问技术支持服务页面:<https://jeesite.com/docs/support/>
* **联系我们**:请访问技术支持服务页面:<http://s.jeesite.com>
## 今后如何升级?

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-parent</artifactId>
<version>5.9.1.springboot3-SNAPSHOT</version>
<version>5.11.1.springboot3-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
@@ -45,6 +45,13 @@
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<!-- Fury serialize -->
<dependency>
<groupId>org.apache.fury</groupId>
<artifactId>fury-core</artifactId>
<version>${fury.version}</version>
</dependency>
<!-- Jackson json -->
<dependency>
@@ -80,13 +87,6 @@
<version>${snakeyaml.version}</version>
</dependency>
<!-- Apache HTTP -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<!-- Email -->
<dependency>
<groupId>org.apache.commons</groupId>

View File

@@ -154,7 +154,7 @@ public class EncodeUtils {
return null;
}
try {
return URLEncoder.encode(part, encoding);
return URLEncoder.encode(part, StringUtils.isNotBlank(encoding) ? encoding : EncodeUtils.UTF_8);
} catch (UnsupportedEncodingException e) {
throw ExceptionUtils.unchecked(e);
}
@@ -175,7 +175,7 @@ public class EncodeUtils {
return null;
}
try {
return URLDecoder.decode(part, encoding);
return URLDecoder.decode(part, StringUtils.isNotBlank(encoding) ? encoding : EncodeUtils.UTF_8);
} catch (UnsupportedEncodingException e) {
throw ExceptionUtils.unchecked(e);
}
@@ -226,7 +226,6 @@ public class EncodeUtils {
// 如果开始不是HTMLXMLJOSN格式则再进行HTML的 "、<、> 转码。
if (!StringUtils.startsWithIgnoreCase(value, "<!--HTML-->") // HTML
&& !StringUtils.startsWithIgnoreCase(value, "<?xml ") // XML
&& !StringUtils.contains(value, "id=\"FormHtml\"") // JFlow
&& !(StringUtils.startsWith(value, "{") && StringUtils.endsWith(value, "}")) // JSON Object
&& !(StringUtils.startsWith(value, "[") && StringUtils.endsWith(value, "]")) // JSON Array
){
@@ -267,7 +266,8 @@ public class EncodeUtils {
"(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|((extractvalue|updatexml|if|mid|database|rand|user)([\\s]*?)\\()"
+ "|(\\b(select|update|and|or|delete|insert|trancate|substr|ascii|declare|exec|count|master|into"
+ "|drop|execute|case when|sleep|union|load_file)\\b)", Pattern.CASE_INSENSITIVE);
private static final Pattern orderByPattern = Pattern.compile("[a-z0-9_\\.\\, ]*", Pattern.CASE_INSENSITIVE);
private static final Pattern simplePattern = Pattern.compile("[a-z0-9_\\.\\, ]*", Pattern.CASE_INSENSITIVE);
private static final Pattern columnNamePattern = Pattern.compile("[a-z0-9_\\.`\"\\[\\]]*", Pattern.CASE_INSENSITIVE);
/**
* SQL过滤防止注入传入参数输入有select相关代码替换空。
@@ -284,8 +284,13 @@ public class EncodeUtils {
public static String sqlFilter(String text, String source){
if (text != null){
String value = text;
if ("orderBy".equals(source)) {
Matcher matcher = orderByPattern.matcher(value);
if (StringUtils.inString(source, "simple", "orderBy")) {
Matcher matcher = simplePattern.matcher(value);
if (!matcher.matches()) {
value = StringUtils.EMPTY;
}
} else if (StringUtils.inString(source, "columnName")) {
Matcher matcher = columnNamePattern.matcher(value);
if (!matcher.matches()) {
value = StringUtils.EMPTY;
}
@@ -304,4 +309,27 @@ public class EncodeUtils {
return null;
}
// 对邮箱和手机号进行安全处理
private static final Pattern emailPattern = Pattern.compile("([\\w\\W]?)([\\w\\W]+)([\\w\\W])(@[\\w\\W]+)");
private static final Pattern mobilePattern = Pattern.compile("(\\d{3})(\\d+)(\\d{3})");
/**
* 手机号码进行掩码处理
*/
public static String mobileMask(String mobile){
if (StringUtils.isBlank(mobile)){
return mobile;
}
return mobilePattern.matcher(mobile).replaceAll("$1****$3");
}
/**
* 对电子邮箱进行掩码处理
*/
public static String emailMask(String email){
if (StringUtils.isBlank(email)){
return email;
}
return emailPattern.matcher(email).replaceAll("$1****$3$4");
}
}

View File

@@ -0,0 +1,185 @@
/**
* 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 com.jeesite.common.io.PropertiesUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* RSA 加密解密工具类,非对称加密
* @author ThinkGem
*/
public class RsaUtils {
private static final String RSA = "RSA";
private static final String algorithm = "SHA256withRSA";
private static final boolean STORE_BASE64 = PropertiesUtils.getInstance()
.getPropertyToBoolean("encrypt.storeBase64", "false");
/**
* 生成 RSA 秘钥对
*/
public static String[] genKeys() {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
return new String[]{
EncodeUtils.encodeBase64(publicKey.getEncoded()),
EncodeUtils.encodeBase64(privateKey.getEncoded()),
};
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/**
* 将 Base64 公钥串,转化为公钥对象
* @author ThinkGem
*/
public static PublicKey toPublicKey(String publicKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(EncodeUtils.decodeBase64(publicKey));
return keyFactory.generatePublic(publicKeySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/**
* 将 Base64 私钥串,转化为私钥对象
* @author ThinkGem
*/
public static PrivateKey toPrivateKey(String privateKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(EncodeUtils.decodeBase64(privateKey));
return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/**
* 公钥加密
* @author ThinkGem
*/
public static byte[] encode(byte[] input, PublicKey publicKey) {
try {
Cipher encryptCipher = Cipher.getInstance(RSA);
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
return encryptCipher.doFinal(input);
} catch (InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException |
NoSuchAlgorithmException | BadPaddingException e) {
throw new RuntimeException(e);
}
}
/**
* 公钥加密
* @author ThinkGem
*/
public static String encode(String input, PublicKey publicKey) {
if (STORE_BASE64) {
return EncodeUtils.encodeBase64(encode(input.getBytes(StandardCharsets.UTF_8), publicKey));
}
return EncodeUtils.encodeHex(encode(input.getBytes(StandardCharsets.UTF_8), publicKey));
}
/**
* 私钥解密
* @author ThinkGem
*/
public static byte[] decode(byte[] input, PrivateKey privateKey) {
return decodeImpl(input, privateKey);
}
private static byte[] decodeImpl(byte[] input, PrivateKey privateKey) {
try {
Cipher decryptCipher = Cipher.getInstance(RSA);
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
return decryptCipher.doFinal(input);
} catch (InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException |
NoSuchAlgorithmException | BadPaddingException e) {
throw new RuntimeException(e);
}
}
/**
* 私钥解密
* @author ThinkGem
*/
public static String decode(String input, PrivateKey privateKey) {
if (STORE_BASE64) {
return new String(decode(EncodeUtils.decodeBase64(input), privateKey), StandardCharsets.UTF_8);
}
return new String(decode(EncodeUtils.decodeHex(input), privateKey), StandardCharsets.UTF_8);
}
/**
* 私钥签名
* @author ThinkGem
*/
public static byte[] sign(byte[] input, PrivateKey privateKey) {
try {
Signature sig = Signature.getInstance(algorithm);
sig.initSign(privateKey);
sig.update(input);
return sig.sign();
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
/**
* 私钥签名
* @author ThinkGem
*/
public static String sign(String input, PrivateKey privateKey) {
if (STORE_BASE64) {
return EncodeUtils.encodeBase64(sign(input.getBytes(StandardCharsets.UTF_8), privateKey));
}
return EncodeUtils.encodeHex(sign(input.getBytes(StandardCharsets.UTF_8), privateKey));
}
/**
* 公钥验签
* @author ThinkGem
*/
public static boolean verify(byte[] input, PublicKey publicKey, byte[] signature) {
try {
Signature sig = Signature.getInstance(algorithm);
sig.initVerify(publicKey);
sig.update(input);
return sig.verify(signature);
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
/**
* 公钥验签
* @author ThinkGem
*/
public static boolean verify(String input, PublicKey publicKey, String signature) {
if (STORE_BASE64) {
return verify(input.getBytes(StandardCharsets.UTF_8), publicKey, EncodeUtils.decodeBase64(signature));
}
return verify(input.getBytes(StandardCharsets.UTF_8), publicKey, EncodeUtils.decodeHex(signature));
}
}

View File

@@ -18,8 +18,8 @@ import java.util.UUID;
*/
public class IdGen {
private static SecureRandom random = new SecureRandom();
private static IdWorker idWorker = new IdWorker(-1, -1);
private static final SecureRandom random = new SecureRandom();
private static final IdWorker idWorker = new IdWorker(-1, -1);
/**
* 生成UUID, 中间无-分割.

View File

@@ -4,7 +4,6 @@
*/
package com.jeesite.common.io;
import com.jeesite.common.codec.EncodeUtils;
import com.jeesite.common.collect.SetUtils;
import com.jeesite.common.lang.ObjectUtils;
import com.jeesite.common.lang.StringUtils;
@@ -17,6 +16,8 @@ import org.springframework.core.io.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@@ -35,10 +36,8 @@ public class PropertiesUtils {
// 默认加载的文件可通过继承覆盖若有相同Key优先加载后面的
public static final String[] DEFAULT_CONFIG_FILE = new String[]{
"classpath:application.yml", "classpath:config/application.yml",
"classpath:bootstrap.yml", "classpath:config/bootstrap.yml",
"file:application.yml", "file:config/application.yml",
"file:bootstrap.yml", "file:config/bootstrap.yml",
"classpath:config/application.yml", "classpath:application.yml",
"file:config/application.yml", "file:application.yml",
};
private static final Logger logger = PropertiesUtils.initLogger();
@@ -64,9 +63,7 @@ public class PropertiesUtils {
//configSet.add("classpath:config/jeesite.yml");
// 获取全局设置默认的配置文件(以下是支持环境配置的属性文件)
Set<String> set = SetUtils.newLinkedHashSet();
for (String configFile : DEFAULT_CONFIG_FILE){
set.add(configFile);
}
set.addAll(Arrays.asList(DEFAULT_CONFIG_FILE));
// 获取 spring.config.location 外部自定义的配置文件
String customConfigs = System.getProperty("spring.config.location");
if (StringUtils.isNotBlank(customConfigs)){
@@ -81,7 +78,7 @@ public class PropertiesUtils {
}
}
// 获取 spring.profiles.active 活动环境名称的配置文件
String[] configFiles = set.toArray(new String[set.size()]);
String[] configFiles = set.toArray(new String[0]);
String profiles = System.getProperty("spring.profiles.active");
if (StringUtils.isBlank(profiles)){
PropertiesUtils propsTemp = new PropertiesUtils(configFiles);
@@ -101,7 +98,7 @@ public class PropertiesUtils {
}
}
}
configFiles = configSet.toArray(new String[configSet.size()]);
configFiles = configSet.toArray(new String[0]);
logger.debug("Trying: {}", (Object)configFiles);
INSTANCE = new PropertiesUtils(configFiles);
}
@@ -115,12 +112,11 @@ public class PropertiesUtils {
Resource resource = ResourceUtils.getResource(location);
if (resource.exists()){
if (location.endsWith(".properties")){
try (InputStreamReader is = new InputStreamReader(resource.getInputStream(), EncodeUtils.UTF_8)){
try (InputStreamReader is = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)){
properties.load(is);
configSet.add(location);
} catch (IOException e) {
System.err.println("Load " + location + " failure.");
e.printStackTrace();
logger.error("Load {} failure.", location, e);
}
}
else if (location.endsWith(".yml")){
@@ -133,8 +129,7 @@ public class PropertiesUtils {
}
configSet.add(location);
} catch (Exception e) {
System.err.println("Load " + location + " failure.");
e.printStackTrace();
logger.error("Load {} failure.", location, e);
}
}
}
@@ -174,7 +169,7 @@ public class PropertiesUtils {
private static final Pattern p1 = Pattern.compile("\\$\\{.*?\\}");
/**
* 获取属性值取不到从System.getProperty()获取都取不到返回null
* 获取属性值取不到从System.getProperty和System.getenv获取都取不到返回null
*/
public String getProperty(String key) {
if (environment != null){
@@ -196,9 +191,10 @@ public class PropertiesUtils {
String systemProperty = System.getProperty(key);
if (systemProperty != null) {
return systemProperty;
}else{
return System.getenv(key);
}
}
return null;
}
/**
@@ -315,7 +311,6 @@ public class PropertiesUtils {
/**
* 设置环境属性
* @param environment
*/
public static void setEnvironment(Environment environment) {
PropertiesUtils.environment = environment;

View File

@@ -28,6 +28,8 @@ public class ExceptionUtils {
ex = (Throwable) request.getAttribute("exception");
} else if (request.getAttribute(RequestDispatcher.ERROR_EXCEPTION) != null) {
ex = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
} else if (request.getAttribute("org.springframework.web.servlet.DispatcherServlet.EXCEPTION") != null) {
ex = (Throwable) request.getAttribute("org.springframework.web.servlet.DispatcherServlet.EXCEPTION");
}
return ex;
}

View File

@@ -4,7 +4,11 @@
*/
package com.jeesite.common.lang;
import com.jeesite.common.io.PropertiesUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.fury.Fury;
import org.apache.fury.ThreadSafeFury;
import org.apache.fury.config.Language;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
@@ -23,11 +27,22 @@ import java.lang.reflect.InvocationTargetException;
public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
private static final Logger logger = LoggerFactory.getLogger(ObjectUtils.class);
private static final boolean isJavaSerialize;
static {
String[] ver = StringUtils.split(System.getProperty("java.version"), StringUtils.DOT);
isJavaSerialize = ver.length > 0 && Integer.parseInt(ver[0]) > 1;
/**
* 当前类的实例持有者(静态内部类,延迟加载,懒汉式,线程安全的单例模式)
*/
private static final class Static {
private static final Boolean isJavaSerialize;
private static final ThreadSafeFury fury;
static {
isJavaSerialize = PropertiesUtils.getInstance()
.getPropertyToBoolean("isJavaSerialize", "false");
org.apache.fury.logging.LoggerFactory.useSlf4jLogging(true);
fury = Fury.builder().withLanguage(Language.JAVA)
.withRefTracking(true)
.requireClassRegistration(false)
.buildThreadSafeFury();
}
}
/**
@@ -71,14 +86,37 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
* 转换为 Long 类型
*/
public static Long toLong(final Object val) {
return toDouble(val).longValue();
if (val == null) {
return 0L;
}
try {
String str = val.toString();
if (StringUtils.isBlank(str)) {
return 0L;
}
if (StringUtils.contains(str, "*")) {
Long number = null, d = null;
for (String s : StringUtils.split(str, "*")) {
d = Long.parseLong(StringUtils.trim(s));
if (number == null) {
number = d;
} else {
number *= d;
}
}
return number;
}
return Long.parseLong(StringUtils.trim(str));
} catch (Exception e) {
return 0L;
}
}
/**
* 转换为 Integer 类型
*/
public static Integer toInteger(final Object val) {
return toDouble(val).intValue();
return toLong(val).intValue();
}
/**
@@ -151,8 +189,7 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
return null;
}
byte[] bytes = ObjectUtils.serialize(source);
Object target = ObjectUtils.unserialize(bytes);
return target;
return ObjectUtils.unserialize(bytes);
}
/**
@@ -162,7 +199,11 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
*/
public static byte[] serialize(Object object) {
try {
return ObjectUtils.serializeJava(object);
if (Static.isJavaSerialize) {
return ObjectUtils.serializeJava(object);
} else {
return ObjectUtils.serializeFury(object);
}
} catch (Exception e) {
logger.error("serialize: {}", e.getMessage());
}
@@ -176,7 +217,11 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
*/
public static Object unserialize(byte[] bytes) {
try {
return ObjectUtils.unserializeJava(bytes);
if (Static.isJavaSerialize) {
return ObjectUtils.unserializeJava(bytes);
} else {
return ObjectUtils.unserializeFury(bytes);
}
} catch (Exception e) {
logger.error("unserialize: {}", e.getMessage());
}
@@ -234,6 +279,43 @@ public class ObjectUtils extends org.apache.commons.lang3.ObjectUtils {
return object;
}
/**
* 序列化对象
* @param object
* @return
*/
public static byte[] serializeFury(Object object) {
if (object == null){
return null;
}
long beginTime = System.currentTimeMillis();
byte[] bytes = Static.fury.serialize(object);
long totalTime = System.currentTimeMillis() - beginTime;
if (totalTime > 30000){
logger.warn(object.getClass() + " serialize time: " + TimeUtils.formatTime(totalTime));
}
return bytes;
}
/**
* 反序列化对象
* @param bytes
* @return
*/
public static Object unserializeFury(byte[] bytes) {
if (bytes == null){
return null;
}
long beginTime = System.currentTimeMillis();
Object object = Static.fury.deserialize(bytes);
long totalTime = System.currentTimeMillis() - beginTime;
if (totalTime > 30000 && object != null){
logger.warn(object.getClass() + " unserialize time: " + TimeUtils.formatTime(totalTime));
}
return object;
}
// private static ThreadLocal<FSTConfiguration> fstConfiguration =
// new NamedThreadLocal<FSTConfiguration>("FSTConfiguration") {
// @Override

View File

@@ -9,7 +9,6 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
@@ -152,7 +151,7 @@ public class JsonMapper extends ObjectMapper {
try {
return this.writeValueAsString(object);
} catch (IOException e) {
logger.warn("write to json string error:" + object, e);
logger.warn("write to json string error: {}", object, e);
return null;
}
}
@@ -166,7 +165,7 @@ public class JsonMapper extends ObjectMapper {
try {
return this.writerWithView(jsonView).writeValueAsString(object);
} catch (IOException e) {
logger.warn("write to json string error:" + object, e);
logger.warn("write to json string error: {}", object, e);
return null;
}
}
@@ -182,8 +181,8 @@ public class JsonMapper extends ObjectMapper {
* 反序列化POJO或简单Collection如List<String>.
* 如果JSON字符串为Null或"null"字符串, 返回Null.
* 如果JSON字符串为"[]", 返回空集合.
* 如需反序列化复杂Collection如List<MyBean>, 请使用fromJson(String,JavaType)
* @see #fromJson(String, JavaType)
* 如需反序列化复杂Collection如List<MyBean>, 请使用fromJson(String, Class)
* @see #fromJson(String, Class)
*/
public <T> T fromJsonString(String jsonString, Class<T> clazz) {
if (StringUtils.isEmpty(jsonString) || "<CLOB>".equals(jsonString)) {
@@ -192,7 +191,7 @@ public class JsonMapper extends ObjectMapper {
try {
return this.readValue(jsonString, clazz);
} catch (IOException e) {
logger.warn("parse json string error:" + jsonString, e);
logger.warn("parse json string error: {}", jsonString, e);
return null;
}
}
@@ -209,7 +208,7 @@ public class JsonMapper extends ObjectMapper {
try {
return (T) this.readValue(jsonString, javaType);
} catch (IOException e) {
logger.warn("parse json string error:" + jsonString, e);
logger.warn("parse json string error: {}", jsonString, e);
return null;
}
}
@@ -230,10 +229,8 @@ public class JsonMapper extends ObjectMapper {
public <T> T update(String jsonString, T object) {
try {
return (T) this.readerForUpdating(object).readValue(jsonString);
} catch (JsonProcessingException e) {
logger.warn("update json string:" + jsonString + " to object:" + object + " error.", e);
} catch (Exception e) {
logger.warn("update json string:" + jsonString + " to object:" + object + " error.", e);
logger.warn("update json string: {} to object: {} error.", jsonString, object, e);
}
return null;
}

View File

@@ -77,7 +77,6 @@ public class ReflectUtils {
//Method method = getAccessibleMethodByName(object, methodName, 0);
//if (method == null) { return; }
//Class<?> returnType = method.getReturnType();
System.out.println(object.getClass());
MethodAccess ma = MethodAccess.get(object.getClass());
Class<?> returnType = ma.getReturnTypes()[ma.getIndex(methodName)];
childObj = returnType.getDeclaredConstructor().newInstance();

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.common.utils.excel.fieldtype;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
/**
* BigDecimal类型转换
* @author ThinkGem
* @version 2020-3-5
* @example fieldType = BigDecimalType.class
*/
public class BigDecimalType implements FieldType {
/**
* 获取对象值(导入)
*/
@Override
public Object getValue(String val) {
return new BigDecimal(val);
}
/**
* 获取对象值(导出)
*/
@Override
public String setValue(Object val) {
return val == null ? StringUtils.EMPTY : val.toString();
}
/**
* 获取对象值格式(导出)
*/
@Override
public String getDataFormat() {
return "0.00";
}
}

View File

@@ -4,218 +4,261 @@
*/
package com.jeesite.common.web.http;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import com.jeesite.common.codec.EncodeUtils;
import com.jeesite.common.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.io.InputStream;
import java.net.Socket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.time.Duration;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import com.jeesite.common.codec.EncodeUtils;
/**
* HTTP客户端工具类支持HTTPS
* HTTP 客户端工具类支持HTTPS
* @author ThinkGem
* @version 2017-3-27
* @version 2025-03-26
*/
public class HttpClientUtils {
private final static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);
private static final HttpClient client = createHttpClient(60);
/**
* http的get请求
* @param url
* HTTP 的 GET 请求
*/
public static String get(String url) {
return get(url, EncodeUtils.UTF_8);
}
/**
* http的get请求
* @param url
*/
public static String get(String url, String charset) {
HttpGet httpGet = new HttpGet(url);
return executeRequest(httpGet, charset);
}
/**
* http的get请求增加异步请求头参数
* @param url
*/
public static String ajaxGet(String url) {
return ajaxGet(url, EncodeUtils.UTF_8);
}
/**
* http的get请求增加异步请求头参数
* @param url
*/
public static String ajaxGet(String url, String charset) {
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("X-Requested-With", "XMLHttpRequest");
return executeRequest(httpGet, charset);
return get(url, null, null);
}
/**
* http的post请求,传递map格式参数
* HTTP 的 GET 请求,传递 Map 格式参数
*/
public static String get(String url, Map<String, String> dataMap) {
return get(url, dataMap, EncodeUtils.UTF_8);
}
/**
* HTTP 的 GET 请求,传递 Map 格式参数,支持指定编码
*/
public static String get(String url, Map<String, String> dataMap, String charset) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(buildUrl(url, dataMap, charset)))
.GET()
.build();
return executeRequest(request);
}
/**
* HTTP 的 GET 请求,增加 ajax 请求头
*/
public static String ajaxGet(String url) {
return ajaxGet(url, null, null);
}
/**
* HTTP 的 GET 请求,增加 ajax 请求头,传递 Map 格式参数
*/
public static String ajaxGet(String url, Map<String, String> dataMap) {
return ajaxGet(url, dataMap, EncodeUtils.UTF_8);
}
/**
* HTTP 的 GET 请求,增加 ajax 请求头,传递 Map 格式参数,支持指定编码
*/
public static String ajaxGet(String url, Map<String, String> dataMap, String charset) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(buildUrl(url, dataMap, charset)))
.header("X-Requested-With", "XMLHttpRequest")
.GET()
.build();
return executeRequest(request);
}
/**
* 构建表单数据Map 转换 params支持指定编码
*/
private static String buildUrl(String url, Map<String, String> dataMap, String charset) {
if (dataMap == null) {
return url;
}
StringBuilder sb = new StringBuilder(url);
if (!url.contains("?")) {
sb.append("?");
} else if (!url.endsWith("&")) {
sb.append("&");
}
return sb + buildFormData(dataMap, charset);
}
/**
* HTTP 的 POST 请求,传递 Map 格式参数
*/
public static String post(String url, Map<String, String> dataMap) {
return post(url, dataMap, EncodeUtils.UTF_8);
}
/**
* http的post请求,传递map格式参数
* HTTP 的 POST 请求,传递 Map 格式参数,支持指定编码
*/
public static String post(String url, Map<String, String> dataMap, String charset) {
HttpPost httpPost = new HttpPost(url);
try {
if (dataMap != null){
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : dataMap.entrySet()) {
nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nvps, charset);
formEntity.setContentEncoding(charset);
httpPost.setEntity(formEntity);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return executeRequest(httpPost, charset);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(buildFormData(dataMap, charset)))
.build();
return executeRequest(request);
}
/**
* http的post请求增加异步请求头参数,传递map格式参数
* HTTP 的 POST 请求,增加 ajax 请求头,传递 Map 格式参数
*/
public static String ajaxPost(String url, Map<String, String> dataMap) {
return ajaxPost(url, dataMap, EncodeUtils.UTF_8);
}
/**
* http的post请求增加异步请求头参数,传递map格式参数
* HTTP 的 POST 请求,增加 ajax 请求头,传递 Map 格式参数,支持指定编码
*/
public static String ajaxPost(String url, Map<String, String> dataMap, String charset) {
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("X-Requested-With", "XMLHttpRequest");
try {
if (dataMap != null){
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : dataMap.entrySet()) {
nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nvps, charset);
formEntity.setContentEncoding(charset);
httpPost.setEntity(formEntity);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return executeRequest(httpPost, charset);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("X-Requested-With", "XMLHttpRequest")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(buildFormData(dataMap, charset)))
.build();
return executeRequest(request);
}
/**
* http的post请求增加异步请求头参数传递json格式参数
* 构建表单数据Map 转换 params支持指定编码
*/
private static String buildFormData(Map<String, String> dataMap, String charset) {
return dataMap.entrySet().stream()
.map(entry -> entry.getKey() + "="
+ EncodeUtils.encodeUrl(entry.getValue(), charset))
.reduce((a, b) -> a + "&" + b)
.orElse(StringUtils.EMPTY);
}
/**
* HTTP 的 POST 请求,使用 json 请求头,传递 json 格式参数
*/
public static String ajaxPostJson(String url, String jsonString) {
return ajaxPostJson(url, jsonString, EncodeUtils.UTF_8);
}
/**
* http的post请求增加异步请求头参数传递json格式参数
* HTTP 的 POST 请求,使用 json 请求头,传递 json 格式参数,支持指定编码
*/
public static String ajaxPostJson(String url, String jsonString, String charset) {
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("X-Requested-With", "XMLHttpRequest");
// try {
StringEntity stringEntity = new StringEntity(jsonString, charset);// 解决中文乱码问题
stringEntity.setContentEncoding(charset);
stringEntity.setContentType("application/json");
httpPost.setEntity(stringEntity);
// } catch (UnsupportedEncodingException e) {
// e.printStackTrace();
// }
return executeRequest(httpPost, charset);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("X-Requested-With", "XMLHttpRequest")
.header("Content-Type", "application/json; charset="
+ (StringUtils.isNotBlank(charset) ? charset : EncodeUtils.UTF_8))
.POST(HttpRequest.BodyPublishers.ofString(jsonString))
.build();
return executeRequest(request);
}
/**
* 执行一个http请求传递HttpGet或HttpPost参数
* 执行一个 http 请求,传递 HttpRequest 参数
*/
public static String executeRequest(HttpUriRequest httpRequest) {
return executeRequest(httpRequest, EncodeUtils.UTF_8);
public static String executeRequest(HttpRequest request) {
try {
return client.send(request, HttpResponse.BodyHandlers.ofString()).body();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 执行一个http请求传递HttpGet或HttpPost参数
* HTTP 的 GET 请求,返回文件流
*/
public static String executeRequest(HttpUriRequest httpRequest, String charset) {
CloseableHttpClient httpclient;
if ("https".equals(httpRequest.getURI().getScheme())){
httpclient = createSSLInsecureClient();
}else{
httpclient = HttpClients.createDefault();
}
String result = "";
try {
try {
CloseableHttpResponse response = httpclient.execute(httpRequest);
HttpEntity entity = null;
try {
entity = response.getEntity();
result = EntityUtils.toString(entity, charset);
} finally {
EntityUtils.consume(entity);
response.close();
}
} finally {
httpclient.close();
}
}catch(IOException ex){
ex.printStackTrace();
}
return result;
public static InputStream getInputStream(String url) {
return getInputStream(url, null);
}
/**
* 创建 SSL连接
* HTTP 的 GET 请求,传递 Map 格式参数,返回文件流
*/
public static CloseableHttpClient createSSLInsecureClient() {
public static InputStream getInputStream(String url, Map<String, String> dataMap) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(buildUrl(url, dataMap, EncodeUtils.UTF_8)))
.GET()
.build();
return executeRequestInputStream(request);
}
/**
* 执行一个 http 请求,传递 HttpRequest 参数,返回文件流
*/
public static InputStream executeRequestInputStream(HttpRequest request) {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (GeneralSecurityException ex) {
throw new RuntimeException(ex);
HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
if (response.statusCode() == 200) {
return response.body();
} else {
logger.info("URL: {} statusCode: {}", request.uri().toString(), response.statusCode());
return null;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static HttpClient createHttpClient(long seconds) {
HttpClient client;
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new UnsafeX509ExtendedTrustManager()}, new SecureRandom());
client = HttpClient.newBuilder()
.sslContext(sslContext)
.connectTimeout(Duration.ofSeconds(seconds))
.build();
} catch (Exception e) {
logger.info(e.getMessage(), e);
client = HttpClient.newHttpClient();
}
return client;
}
private static final class UnsafeX509ExtendedTrustManager extends X509ExtendedTrustManager {
private static final X509Certificate[] EMPTY_CERTIFICATES = new X509Certificate[0];
@Override
public void checkClientTrusted(X509Certificate[] certificates, String authType) {
}
@Override
public void checkClientTrusted(X509Certificate[] certificates, String authType, Socket socket) {
}
@Override
public void checkClientTrusted(X509Certificate[] certificates, String authType, SSLEngine sslEngine) {
}
@Override
public void checkServerTrusted(X509Certificate[] certificates, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certificates, String authType, Socket socket) {
}
@Override
public void checkServerTrusted(X509Certificate[] certificates, String authType, SSLEngine sslEngine) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return EMPTY_CERTIFICATES;
}
}

View File

@@ -17,6 +17,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.Validate;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@@ -57,6 +58,11 @@ public class ServletUtils {
// 是否打印错误信息参数到视图页面(生产环境关闭)
private static final Boolean PRINT_ERROR_INFO = PROPS.getPropertyToBoolean("error.page.printErrorInfo", "true");
// 允许重定向的地址不设置为全部允许设置this只允许本项目内部跳转多个用逗号隔开例如this,http://*.jeesite.com
private static final String[] ALLOW_REDIRECTS = PROPS.getPropertyToArray("shiro.allowRedirects", "");
private static final Boolean SCHEME_HTTPS = PROPS.getPropertyToBoolean("server.schemeHttps", "false");
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
/**
* 获取当前请求对象
* web.xml: <listener><listener-class>
@@ -384,10 +390,15 @@ public class ServletUtils {
}
/**
* 获取请求的域名(含端口)
* 获取当前请求的域名(含端口)
* @author ThinkGem
*/
public static String getRequestDomain(String url) {
public static String getThisDomain(HttpServletRequest request) {
String url = request.getRequestURL().toString();
String scheme = StringUtils.substringBefore(url, "://");
if (SCHEME_HTTPS && StringUtils.equals(scheme, "http")) {
scheme = "https";
}
String domain = StringUtils.substringAfter(url, "://");
if (StringUtils.contains(domain, "/")) {
domain = StringUtils.substringBefore(domain, "/");
@@ -395,6 +406,28 @@ public class ServletUtils {
return scheme + "://" + domain;
}
/**
* 验证地址是否允许重定向
* @author ThinkGem
*/
public static boolean isAllowRedirects(HttpServletRequest request, String url) {
if (ALLOW_REDIRECTS == null || ALLOW_REDIRECTS.length == 0) {
return true;
}
boolean allow = false;
for (String pattern : ALLOW_REDIRECTS) {
String p = StringUtils.trim(pattern);
if ("this".equals(p)) {
p = getThisDomain(request);
}
if (PATH_MATCHER.match(p + "/**", url)){
allow = true;
break;
}
}
return allow;
}
/**
* 获得请求参数值
*/

View File

@@ -235,3 +235,6 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor
.ui-jqgrid .ui-jqgrid-frozen .ui-jqgrid-htable th div {height:46px!important;}
.ui-jqgrid .ui-jqgrid-htable th div {padding:15px 0 15px 2px;}
.ui-jqgrid tr.jqgrow td {height: 49px;}
.table-form .ui-jqgrid .ui-jqgrid-frozen .ui-jqgrid-htable th div {height:36px!important;}
.table-form .ui-jqgrid .ui-jqgrid-htable th div {padding:9px 0 8px 2px;}
.table-form .ui-jqgrid tr.jqgrow td {height: 39px;}

View File

@@ -199,7 +199,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor
.form-control:focus,.select2-container--default.select2-container--focus .select2-selection--multiple,
.select2-container--default .select2-search--dropdown .select2-search__field,
.select2-container--default.select2-container--focus .select2-selection--single,
.select2-container--default.select2-container--focus .select2-selection--multiple {border-color:#557bfa!important;box-shadow:0 0 0 2px rgba(24,80,236,.2);}
.select2-container--default.select2-container--focus .select2-selection--multiple {border-color:#557bfa!important;box-shadow:0 0 0 2px rgba(5, 88, 255, 0.06);}
.table thead tr, .ui-jqgrid-htable thead tr, .ui-jqgrid-hdiv, .ui-jqgrid-hbox {background-color:#f6f6f6;}
.ui-jqgrid .ui-jqgrid-labels th, .ui-jqgrid tr.ui-row-ltr td, .ui-jqgrid tr.ui-row-rtl td, .ui-jqgrid tr.ui-row-ltr td:last-child,
.ui-jqgrid .ui-jqgrid-htable th.ui-th-column-header, .ui-jqgrid .ui-jqgrid-htable th.ui-th-column,

View File

@@ -221,7 +221,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor
.form-control:focus,.select2-container--default.select2-container--focus .select2-selection--multiple,
.select2-container--default .select2-search--dropdown .select2-search__field,
.select2-container--default.select2-container--focus .select2-selection--single,
.select2-container--default.select2-container--focus .select2-selection--multiple {border-color:#557bfa!important;box-shadow:0 0 0 2px rgba(24,80,236,.2);}
.select2-container--default.select2-container--focus .select2-selection--multiple {border-color:#557bfa!important;box-shadow:0 0 0 2px rgba(5, 88, 255, 0.06);}
.table thead tr, .ui-jqgrid-htable thead tr, .ui-jqgrid-hdiv, .ui-jqgrid-hbox {background-color:#fafafa;}
.ui-jqgrid .ui-jqgrid-labels th, .ui-jqgrid tr.ui-row-ltr td, .ui-jqgrid tr.ui-row-rtl td, .ui-jqgrid tr.ui-row-ltr td:last-child,
.ui-jqgrid .ui-jqgrid-htable th.ui-th-column-header, .ui-jqgrid .ui-jqgrid-htable th.ui-th-column,
@@ -235,3 +235,6 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor
.ui-jqgrid .ui-jqgrid-frozen .ui-jqgrid-htable th div {height:46px!important;}
.ui-jqgrid .ui-jqgrid-htable th div {padding:15px 0 15px 2px;}
.ui-jqgrid tr.jqgrow td {height: 49px;}
.table-form .ui-jqgrid .ui-jqgrid-frozen .ui-jqgrid-htable th div {height:36px!important;}
.table-form .ui-jqgrid .ui-jqgrid-htable th div {padding:9px 0 8px 2px;}
.table-form .ui-jqgrid tr.jqgrow td {height: 39px;}

View File

@@ -203,7 +203,7 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor
::-webkit-scrollbar {background:#2a2a2a;}
::-webkit-scrollbar-thumb {background:#6a6a6a;}
.btn-default {background-color:#323232;border-color:#323232;color:#b5b5b5;}
.btn-default {background-color:#323232;border-color:#323232;color:#b5b5b5;} .btn-default i {color:#dfdfdf;}
.btn-default.active, .btn-default:active {background-color:#3e3e3e;border-color:#3e3e3e;color:#dfdfdf;}
.btn-default:hover, .btn-default:active, .btn-default.hover, .btn-default:focus,
.btn-default.active.focus, .btn-default.active:focus, .btn-default.active:hover, .btn-default:active.focus,
@@ -296,6 +296,9 @@ a, a:hover, a:active, a:focus, .form-unit, th[aria-selected=true] .ui-jqgrid-sor
.ui-jqgrid .ui-jqgrid-frozen .ui-jqgrid-htable th div {height:46px!important;}
.ui-jqgrid .ui-jqgrid-htable th div {padding:15px 0 15px 2px;}
.ui-jqgrid tr.jqgrow td {height: 49px;}
.table-form .ui-jqgrid .ui-jqgrid-frozen .ui-jqgrid-htable th div {height:36px!important;}
.table-form .ui-jqgrid .ui-jqgrid-htable th div {padding:9px 0 8px 2px;}
.table-form .ui-jqgrid tr.jqgrow td {height: 39px;}
.ui-jqgrid tr.jqgroup td, .ui-jqgrid tr.footrow td, .ui-jqgrid tr.jqfoot td {background:#323232;}
.ui-jqgrid .actions .moreItems {background:#1a1a1a;border-color:#3c3c3c;box-shadow:none;}

View File

@@ -1,170 +0,0 @@
=ZTree v3.x (JQuery Tree插件) 更新日志=
<font color="red">为了更好的优化及扩展zTree, 因此决定升级为v3.x并且对之前的v2.x不兼容会有很多结构上的修改对此深感无奈与抱歉请大家谅解。</font>
<font color="red">
具体修改内容可参考:
* [http://www.ztree.me/v3/api.php zTree v3.0 API 文档]
* [http://www.ztree.me/v3/demo.php#_101 zTree v3.0 Demo 演示]
* [http://www.ztree.me/v3/faq.php#_101 zTree v3.0 常见问题]
</font>
<font color=#041594>
*2013.03.11* v3.5.12
* 【修改】由于 jquery 1.9 中移除 event.srcElement 导致的 js 报错的bug。
* 【修改】在异步加载模式下,使用 moveNode 方法,且 moveType != "inner" 时,也会导致 targetNode 自动加载子节点的 bug
* 【修改】对已经显示的节点(nochecked=true)使用 showNodes 或 showNode 方法后导致勾选框出现的bug。
* 【修改】对已经隐藏的节点(nochecked=false)使用 hideNodes 或 hideNode 方法后导致勾选框消失的bug。
* 【修改】getNodesByParamFuzzy 支持 大小写模糊。
* 【修改】className 结构,提取 _consts.className.BUTTON / LEVEL / ICO_LOADING / SWITCH便于快速修改 css 冲突。
例如:与 WordPress 产生冲突后,直接修改 core 中的 "button" 和 "level" 即可。 Issue: https://github.com/zTree/zTree_v3/issues/2
*2013.01.28* v3.5.02
* 【增加】setting.check.chkDisabledInherit 属性,用于设置 chkDisabled 在初始化时子节点是否可以继承父节点的 chkDisabled 属性
* 【删除】内部 noSel 方法,使用 selectstart事件 和 "-moz-user-select"样式 处理禁止 节点文字被选择的功能
* 【修改】不兼容 jQuery 1.9 的bug
* 【修改】onDrop 的触发规则,保证异步加载模式下,可以在延迟加载结束后触发,避免 onDrop 中被拖拽的节点是已经更新后的数据。
* 【修改】setChkDisabled 方法,增加 inheritParent, inheritChildren 参数设置是否让父子节点继承 disabled
* 【修改】异步加载时 拼接参数的方法,由 string 修改为 json 对象
* 【修正】1-2-3 3级节点时如果 2级节点 全部设置为 nocheck 或 chkDisabled后勾选3级节点时1级节点的半勾选状态错误的 bug
* 【修改】Demo: checkbox_nocheck.html & checkbox_chkDisabled.html;
* 【修改】Demo: edit_super.html增加 showRenameBtn & showRemoveBtn 的演示
* 【修改】Demo: asyncForAll, 将 post 修改为 get为了避免由于 IE10 的 bug 造成的客户端 以及 服务端崩溃
IE10 ajax Post 无法提交参数的bug (http://bugs.jquery.com/ticket/12790)
*2012.12.21* v3.5.01
* 【优化】clone 方法
* 【修正】对于初始化无 children 属性的父节点进行 reAsyncChildNodes 操作时出错的 bug
* 【修正】beforeRename 回调中使用 cancelEditName 方法后,再 return false 导致无法重新进行编辑的 bug
* 【修正】exedit 扩展包让 setting.data.key.url 失效的 bug
* 【修正】setting.check.autoCheckTrigger 设置为 true 时onCheck 回调缺少 event 参数的 bug
* 【修正】singlepath.html Demo 中的 bug
*2012.11.20* v3.5
* 【优化】原先的 clone 方法 (特别感谢:愚人码头)
* 【修改】隐藏父节点后,使用 expandAll 方法导致 父节点展开的 bug
* 【修改】使用 jQuery v1.7 以上时,设置 zTree 容器 ul 隐藏visibility: hidden;)后, 调用 selectNode 导致 IE 浏览器报错 Can't move focus 的 bug
* 【修改】正在异步加载时,执行 destory 或 init 方法后,异步加载的节点影响新树的 bug
* 【修改】方法 reAsyncChildNodes 在 refresh 的时候未清空内部 cache 导致内存泄露 的 bug
* 【修改】批量节点拖拽到其他父节点内inner导致顺序反转 的 bug
* 【修改】对于 使用 html格式的 节点无法触发 双击事件 的 bug
* 【修改】onCheck 回调中的 event ,保证与触发事件中的 event 一致
* 【修改】异步加载时,在 onNodeCreated 中执行 selectNode 后,导致节点折叠的 bug
* 【修改】API 中 dataFilter 的参数名称 childNodes -> responseData
* 【修改】API 中 iconSkin 的 举例内容
* 【修改】API 中 chkDisabled 的说明
* 【修改】Demo 中 index.html 内的 loadReady 重复绑定问题
*2012.09.03* v3.4
* 【增加】 Demo —— OutLook 样式的左侧菜单
* 【增加】清空 zTree 的方法 $.fn.zTree.destory(treeId) & zTree.destory()
* 【修改】core核心文件内 _eventProxy 方法中获取 tId 的方法,提高 DOM 的灵活性
* 【修改】初始化时 多层父节点的 checkbox 半选状态计算错误的 bug
* 【修改】同时选中父、子节点后,利用 getSelectedNodes 获取选中节点并利用 removeNode 删除时报错的 bug
* 【修改】treeNode.chkDisabled / nocheck 属性,支持字符串格式的 "false"/"true"
* 【修改】异步加载模式下无法利用 server 返回 xml 并且 在 dataFilter 中继续处理的 bug
* 【修改】title 只允许设置为 string 类型值的问题。 修正后允许设置为 number 类型的值
* 【修改】zId 计数规则 & Cache 保存,减少 IE9 的 bug 造成的内存泄漏
* 【修改】API 页面搜索功能导致 IE 崩溃的 bug
*2012.07.16* v3.3
* 【增加】扩展库 exhide -- 节点隐藏功能
* 【修改】getNodesByFilter 方法,添加 invokeParam 自定义参数
* 【修改】拖拽中测试代码未删除,导致出现黄颜色的 iframe 遮罩层的 bug
* 【修改】延迟加载方法 对于使用 expandAll 进行全部展开时,导致 onNodeCreated 回调 和 addDiyDom 方法触发过早的 bug
* 【修改】使用 moveNode 移动尚未生成 DOM 的节点时,视图会出现异常的 bug
* 【修改】删除节点后,相关节点的 isFirstNode 属性未重置的 bug
* 【修改】getPreNode(),getNextNode() 方法在对于特殊情况时计算错误的 bug
* 【修改】设置 title 之后,如果重新将 title 内容设置为空后,会导致无法更新 title 的 bug
* 【修改】针对 setting.check.chkStyle=="radio" && setting.check.radioType=="all" 的情况时getTreeCheckedNodes方法优化找到一个结果就 break
* 【修改】zTreeObj.getCheckedNodes(false) 在 radioType = "all" 时计算错误的 bug
* 【修改】完善 API 中 beforeDrop / onDrop 的关于 treeId 的说明
*2012.05.13* v3.2
* 【增加】setting.data.key.url 允许修改 treeNode.url 属性
* 【增加】getNodesByFilter(filter, isSingle) 方法
* 【增加】"与其他 DOM 拖拽互动" 的 Demo (http://www.ztree.me/v3/demo.php#_511)
* 【增加】"异步加载模式下全部展开" 的 Demo (http://www.ztree.me/v3/demo.php#_512)
* 【修改】代码结构,将 addNodes、removeNode、removeChildNodes 方法 和 beforeRemove、onRemove 回调 转移到 core 内
* 【修改】IE7的环境下无子节点的父节点反复展开出现多余空行的 bug
* 【修改】异步加载时,如果出现网络异常等,会导致 图标显示错误的 bug
* 【修改】dataFilter中 return null 导致异常 的 bug
* 【修改】removeChildNodes 方法清空子节点后,无法正常添加节点的 bug
* 【修改】moveNode 后节点中的自定义元素的事件丢失的 bug
* 【修改】moveNode 方法中设置 isSilent = true 时,如果移动到已展开的 父节点后,出现异常的 bug
* 【修改】onClick/onDrag/onDrop 回调中 event 不是原始 event 的 bug
* 【修改】onDrop 回调中 当拖拽无效时,无法获得 treeNodes 的 bug
* 【修改】onDrop 无法判断拖拽是 移动还是复制的问题
* 【修改】未开启异步加载模式时,拖拽节点到子节点为空的父节点内时 出现异常 的 bug
* 【修改】拖拽过程中,反复在 父节点图标上划动时,会出现停顿的 bug
(需要css 结构—— button -> span.button)
* 【修改】拖拽操作时箭头 与 targetNode 背景之间的细节现实问题,便于用户拖拽时更容易区分 prev、next 和 inner 操作
* 【修改】拖拽操作时IE6/7 下 在 节点<a> 右侧 10px 内会导致 targetNode = root 的 bug
* 【修改】编辑模式下 默认的编辑按钮、删除按钮点击后,如果相应的 before 回调 return false 时会触发 onClick 回调的 bug
*2012.02.14* v3.1
* 【增加】ajax 的参数 setting.async.contentType ,让提交参数适用于 json 数据提交 (主要适用于 .Net 的开发)。
* 【增加】setting.edit.editNameSelectAll, 用于设定编辑节点名称时初次显示 input 后 text 内容为全选
* 【修改】异步加载 规则,不再仅仅依靠父节点的子节点数来判定,增加内部属性 zAsync保证默认状态下父节点及时无子节点也只能异步加载一次除非使用 reAsyncChildNodes 方法强行控制异步加载。
* 【修改】放大浏览器后导致 界面出现多余连接线的bug 需要更新icon 图标和 css
* 【修改】在编辑状态如果节点名超过编辑框宽度左右键在框内不起作用的bugIE 6 7 8 出现)
CSS 中 filter:alpha(opacity=80) 造成的,应该是 ie 的 bug需要更新 css 文件
* 【修改】title 设置后,如果属性不存在,则默认为 title 为空,便于数据容错和用户灵活使用
* 【修改】editName 方法如果针对尚未展开的 父节点,会导致该父节点自动展开的 bug
* 【修改】title 中存在标签时导致 title 显示异常的bug例如蓝色字22%"'`<input/>`
*2012.01.10* v3.0
* 【增加】setting.check.autoCheckTrigger 默认值 false可以设置联动选中时是否触发事件回调函数
* 【增加】setting.callback.beforeEditName 回调函数,以保证用户可以捕获点击编辑按钮的事件
* 【增加】treeNode.chkDisabled 属性,显示 checkbox 但是用户无法修改 checkbox 状态,并且该 checkbox 会影响父节点的 checkbox 的半选状态
* 【增加】setting.check.nocheckInherit 属性,用户设置子节点继承 nocheck 属性,用于批量初始化节点,不适用于已经显示的节点
* 【增加】setting.edit.drag.autoExpandTrigger 默认值 false可以设置自动展开、折叠操作时是否触发事件回调函数
* 【增加】setting.view.nameIsHTML 默认值 false允许用户对 name 设置 DOM 对象
* 【增加】treeNode.click 属性的说明文档
* 【增加】treeObj.setChkDisabled 方法用于设置 checkbox / radio disabled 状态
* 【增加】treeNode.halfCheck 属性,用于强制设定节点的半选状态
* 【修改】异步加载 & 编辑功能 共存时,拖拽节点 或 增加节点 导致 ie 上报错的 bug apply 方法引起)
* 【修改】zTreeStyle 样式冲突
* 【修改】setting.data.key.title 默认值设置为 "",初始化时自动赋值为 setting.data.key.name 这样可避免希望 title 与 name 一致的用户反复设置参数
* 【修改】点击叶子节点的连接线会触发 expand 事件的 bug
* 【修改】IE 下 点击叶子节点连线会出现虚线框的 bug
* 【修改】updateNode 导致 checkbox 半选状态错误的 bug
* 【修改】checkNode 方法实现 toggle 操作, 取消 expandAll 方法的 toggle 操作
* 【修改】zTree 内鼠标移动会抢页面上 input 内的焦点的 bug
* 【修改】beforeRename / onRename 的触发方式——即使名称内容未改变也会触发,便于用户配合 beforeEditName 捕获编辑状态的结束,赋予用户更多调整规则的权利
* 【修改】与 easyUI 共存时无法拖拽的bug
* 【修改】beforeRename 在 Firefox 下如果利用 alert会触发两次的 bug
* 【修改】checkNode/expandNode/removeNode 方法,默认不触发回调函数,恢复 v2.6 的默认状态,同时增加 callbackFlag 参数,设置为 true 时,可以触发回调函数
* 【修改】IE9下“根据参数查找节点”的Demo 报错行14 重新声明常量属性(Demo 自身的问题定义了history变量)
* 【修改】初始化 zTree 时 onNodeCreated 事件回调函数中无法 用 getZTreeObj 获取 zTree 对象的 bug
* 【修改】setting.edit.drag.prev / next / inner 参数,增加被拖拽的节点集合
* 【修改】异步加载模式下otherParam 使用Array数组会出错的 bug。例如 ["id", "1", "name", "test"]
* 【修改】FireFox 下多棵树拖拽异常的 bug
* 【修改】exedit 中调用 excheck库的方法时没有进行容错处理导致如果只加入 exedit 而没有 excheck的时候会出现 js 错误
* 【修改】显示 checkbox 的 zTree 在编辑模式下,移动节点不会更新父节点半选状态的 bug
* 【修改】treeNode.childs --> children; treeObject.removeChilds --> removeChildNodes; setting.data.key.childs --> children英文不好惹的祸抱歉了
* 【修改】onRemove 回调中得到的 treeNode 还可以查找 preNode、nextNode 的bug。 修正后getPreNode 和 getNextNode 都返回 null 为了便于查找父节点getParentNode 仍保留
* 【修改】简单数据模式下,如果 id 与 pId 的值相同会导致该节点无法正常加载的 bug
* 【修改】移动或删除中间节点会导致最后一个节点连接线图标变小的 bug
*2011.09.05* v3.0 beta
* 【修改】zTree 的 js 代码架构全面修改,并且拆分
* 【修改】zTree 的 css 样式全面修改对浏览器可以更好地兼容同时解决了以前1个像素差的问题
* 【优化】采用延迟加载技术,一次性加载大数据量的节点性能飞速提升
* 【增加】支持多节点同时选中、拖拽
* 【增加】checkNode、checkAllNodes 等多种方法
* 【增加】IE6 自动取消动画展开、折叠的功能
* 【修正】异步加载 & 编辑模式 能够更完美的共存
* 【修正】setting 配置更加合理,并且增加了若干项配置参数
* 【修正】treeNode 节点数据的属性更加合理,并且增加了一些方法
* 【修正】拖拽操作更加灵活方便,更容易制定自己的规则
* 【修正】其他若干修改,详细对比请参考 url[http://www.ztree.me/v3/faq.php#_101 zTree v3.0 常见问题]

View File

@@ -0,0 +1,46 @@
/**
* 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.EncodeUtils;
import com.jeesite.common.codec.RsaUtils;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* RSA 加密解密工具类,非对称加密
* @author ThinkGem
*/
public class RsaUtilsTest {
public static void main(String[] args) {
String s = "Hello word! 你好,中文!";
System.out.println(s);
String[] keys = RsaUtils.genKeys();
System.out.println("公钥:" + keys[0]);
PublicKey publicKey = RsaUtils.toPublicKey(keys[0]);
System.out.println("私钥:" + keys[1]);
PrivateKey privateKey = RsaUtils.toPrivateKey(keys[1]);
byte[] data = RsaUtils.encode(s.getBytes(), publicKey);
String dataString = EncodeUtils.encodeBase64(data);
System.out.println("加密数据:" + dataString);
byte[] data2 = RsaUtils.decode(data, privateKey);
String dataString2 = new String(data2, StandardCharsets.UTF_8);
System.out.println("解密数据:" + dataString2);
byte[] sign = RsaUtils.sign(s.getBytes(), privateKey);
System.out.println("数据签名:" + EncodeUtils.encodeBase64(sign));
boolean b = RsaUtils.verify(s.getBytes(), publicKey, sign);
System.out.println("数据验签:" + b);
}
}

View File

@@ -0,0 +1,26 @@
/**
* 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.ObjectUtils;
import java.text.ParseException;
/**
* 对象操作工具测试类
* @author ThinkGem
* @version 2025-02-05
*/
public class ObjectUtilsTest {
public static void main(String[] args) throws ParseException {
String str = "1738746499603094500";
System.out.println(ObjectUtils.toDouble(str));
System.out.println(ObjectUtils.toFloat(str));
System.out.println(ObjectUtils.toLong(str));
System.out.println(ObjectUtils.toInteger(str));
}
}

View File

@@ -4,8 +4,12 @@
*/
package com.jeesite.test.web.http;
import com.jeesite.common.mapper.JsonMapper;
import com.jeesite.common.web.http.HttpClientUtils;
import java.util.HashMap;
import java.util.Map;
/**
* HTTP客户端测试工具类支持HTTPS
* @author ThinkGem
@@ -14,8 +18,15 @@ import com.jeesite.common.web.http.HttpClientUtils;
public class HttpClientUtilsTest {
public static void main(String[] args) {
String content = HttpClientUtils.get("https://jeesite.com");
System.out.println(content);
String url = "https://vue.jeesite.com/js/a/sys/corpAdmin/treeData";
Map<String, String> dataMap = new HashMap<>();
dataMap.put("isShowCode", "true");
dataMap.put("param1", "你好");
System.out.println(HttpClientUtils.get(url, dataMap));
System.out.println(HttpClientUtils.ajaxGet(url, dataMap));
System.out.println(HttpClientUtils.post(url, dataMap));
System.out.println(HttpClientUtils.ajaxPost(url, dataMap));
System.out.println(HttpClientUtils.ajaxPostJson(url, JsonMapper.toJson(dataMap)));
}
}

View File

@@ -329,7 +329,7 @@
</word>
<word>
<id>a63e3fda50530388ba263296184d8a6919a75791</id>
<length>1000</length>
<length>4000</length>
<decimal>null</decimal>
<array>false</array>
<array_dimension>null</array_dimension>

View File

@@ -16,7 +16,7 @@ CREATE TABLE js_app_comment
update_date timestamp NOT NULL,
remarks vargraphic(500),
create_by_name varchar(200),
device_info varchar(1000),
device_info varchar(4000),
reply_date date,
reply_content vargraphic(500),
reply_user_code varchar(64),

View File

@@ -16,7 +16,7 @@ CREATE TABLE js_app_comment
update_date datetime NOT NULL,
remarks varchar(500),
create_by_name varchar(200),
device_info varchar(1000),
device_info varchar(4000),
reply_date date,
reply_content varchar(500),
reply_user_code varchar(64),

View File

@@ -16,7 +16,7 @@ CREATE TABLE [js_app_comment]
[update_date] datetime NOT NULL,
[remarks] nvarchar(500),
[create_by_name] varchar(200),
[device_info] varchar(1000),
[device_info] varchar(4000),
[reply_date] date,
[reply_content] nvarchar(500),
[reply_user_code] varchar(64),

View File

@@ -17,7 +17,7 @@ CREATE TABLE js_app_comment
update_date datetime NOT NULL COMMENT '更新时间',
remarks varchar(500) COMMENT '备注信息',
create_by_name varchar(200) COMMENT '提问人员姓名',
device_info varchar(1000) COMMENT '设备信息',
device_info varchar(4000) COMMENT '设备信息',
reply_date date COMMENT '回复时间',
reply_content varchar(500) COMMENT '回复意见',
reply_user_code varchar(64) COMMENT '回复人员',

View File

@@ -16,7 +16,7 @@ CREATE TABLE js_app_comment
update_date timestamp NOT NULL,
remarks nvarchar2(500),
create_by_name varchar2(200),
device_info varchar2(1000),
device_info varchar2(4000),
reply_date date,
reply_content nvarchar2(500),
reply_user_code varchar2(64),

View File

@@ -16,7 +16,7 @@ CREATE TABLE js_app_comment
update_date timestamp NOT NULL,
remarks varchar(500),
create_by_name varchar(200),
device_info varchar(1000),
device_info varchar(4000),
reply_date date,
reply_content varchar(500),
reply_user_code varchar(64),

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-parent</artifactId>
<version>5.9.1.springboot3-SNAPSHOT</version>
<version>5.11.1.springboot3-SNAPSHOT</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>

View File

@@ -4,17 +4,15 @@
*/
package com.jeesite.modules.app.entity;
import java.util.Date;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.jeesite.common.entity.DataEntity;
import com.jeesite.common.mybatis.annotation.Column;
import com.jeesite.common.mybatis.annotation.Table;
import com.jeesite.common.mybatis.mapper.query.QueryType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.util.Date;
/**
* APP意见反馈Entity
@@ -26,13 +24,14 @@ import com.jeesite.common.mybatis.mapper.query.QueryType;
@Column(name="category", attrName="category", label="问题分类"),
@Column(name="content", attrName="content", label="问题和意见"),
@Column(name="contact", attrName="contact", label="联系方式"),
@Column(includeEntity=DataEntity.class),
@Column(name="create_by_name", attrName="createByName", label="提问人员姓名", queryType=QueryType.LIKE),
@Column(name="device_info", attrName="deviceInfo", label="设备信息"),
@Column(name="reply_date", attrName="replyDate", label="回复时间"),
@Column(name="reply_content", attrName="replyContent", label="回复意见"),
@Column(name="reply_user_code", attrName="replyUserCode", label="回复人员"),
@Column(name="reply_user_name", attrName="replyUserName", label="回复人员姓名", queryType=QueryType.LIKE),
@Column(name="status", attrName="status", label="状态", isUpdate=true), // save时允许更新status字段
@Column(includeEntity=DataEntity.class),
}, orderBy="a.create_date DESC"
)
public class AppComment extends DataEntity<AppComment> {
@@ -84,7 +83,7 @@ public class AppComment extends DataEntity<AppComment> {
this.contact = contact;
}
@Size(min=0, max=1000, message="设备信息长度不能超过 1000 个字符")
@Size(min=0, max=4000, message="设备信息长度不能超过 4000 个字符")
public String getDeviceInfo() {
return deviceInfo;
}

View File

@@ -4,11 +4,13 @@
*/
package com.jeesite.modules.app.web;
import java.util.Date;
import com.jeesite.common.config.Global;
import com.jeesite.common.entity.Page;
import com.jeesite.common.web.BaseController;
import com.jeesite.modules.app.entity.AppComment;
import com.jeesite.modules.app.service.AppCommentService;
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;
@@ -20,11 +22,7 @@ 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.AppComment;
import com.jeesite.modules.app.service.AppCommentService;
import java.util.Date;
/**
* APP意见反馈Controller
@@ -97,7 +95,6 @@ public class AppCommentController extends BaseController {
}
}
appCommentService.save(appComment);
appCommentService.updateStatus(appComment);
return renderResult(Global.TRUE, text("保存意见成功!"));
}

View File

@@ -16,7 +16,7 @@ CREATE TABLE ${_prefix}app_comment
update_date timestamp NOT NULL,
remarks vargraphic(500),
create_by_name varchar(200),
device_info varchar(1000),
device_info varchar(4000),
reply_date date,
reply_content vargraphic(500),
reply_user_code varchar(64),

View File

@@ -16,7 +16,7 @@ CREATE TABLE ${_prefix}app_comment
update_date datetime NOT NULL,
remarks varchar(500),
create_by_name varchar(200),
device_info varchar(1000),
device_info varchar(4000),
reply_date date,
reply_content varchar(500),
reply_user_code varchar(64),

View File

@@ -16,7 +16,7 @@ CREATE TABLE [${_prefix}app_comment]
[update_date] datetime NOT NULL,
[remarks] nvarchar(500),
[create_by_name] varchar(200),
[device_info] varchar(1000),
[device_info] varchar(4000),
[reply_date] date,
[reply_content] nvarchar(500),
[reply_user_code] varchar(64),

View File

@@ -17,7 +17,7 @@ CREATE TABLE ${_prefix}app_comment
update_date datetime NOT NULL COMMENT '更新时间',
remarks varchar(500) COMMENT '备注信息',
create_by_name varchar(200) COMMENT '提问人员姓名',
device_info varchar(1000) COMMENT '设备信息',
device_info varchar(4000) COMMENT '设备信息',
reply_date date COMMENT '回复时间',
reply_content varchar(500) COMMENT '回复意见',
reply_user_code varchar(64) COMMENT '回复人员',

View File

@@ -16,7 +16,7 @@ CREATE TABLE ${_prefix}app_comment
update_date timestamp NOT NULL,
remarks nvarchar2(500),
create_by_name varchar2(200),
device_info varchar2(1000),
device_info varchar2(4000),
reply_date date,
reply_content nvarchar2(500),
reply_user_code varchar2(64),

View File

@@ -16,7 +16,7 @@ CREATE TABLE ${_prefix}app_comment
update_date timestamp NOT NULL,
remarks varchar(500),
create_by_name varchar(200),
device_info varchar(1000),
device_info varchar(4000),
reply_date date,
reply_content varchar(500),
reply_user_code varchar(64),

View File

@@ -21,4 +21,9 @@
5.8.0
5.8.1
5.9.0
5.9.1
5.9.1
5.9.2
5.10.0
5.10.1
5.11.0
5.11.1

View File

@@ -8,9 +8,9 @@
<div class="box-tools pull-right">
<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
<% if(hasPermi('app:appComment:edit')){ %>
<a href="${ctx}/app/appComment/form" class="btn btn-default btnTool" title="${text('新增意见')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<a href="${ctx}/app/appComment/form" class="btn btn-primary btnTool" title="${text('新增意见')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<% } %>
<a href="#" class="btn btn-default" id="btnSetting" title="${text('设置')}"><i class="fa fa-navicon"></i></a>
<a href="#" class="btn-setting" id="btnSetting" title="${text('设置')}"><i class="fa icon-settings"></i></a>
</div>
</div>
<div class="box-body">
@@ -82,11 +82,11 @@ $('#dataGrid').dataGrid({
{header:'${text("操作")}', name:'actions', width:120, formatter: function(val, obj, row, act){
var actions = [];
//# if(hasPermi('app:appComment:edit')){
actions.push('<a href="${ctx}/app/appComment/form?id='+row.id+'" class="btnList" title="${text("编辑意见")}"><i class="fa fa-pencil"></i></a>&nbsp;');
actions.push('<a href="${ctx}/app/appComment/form?id='+row.id+'" class="btnList" title="${text("编辑意见")}"><i class="fa icon-note"></i></a>&nbsp;');
if (row.status == Global.STATUS_NORMAL){
actions.push('<a href="${ctx}/app/appComment/disable?id='+row.id+'" class="btnList" title="${text("停用意见")}" data-confirm="${text("确认要停用该意见吗?")}"><i class="glyphicon glyphicon-ban-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/app/appComment/disable?id='+row.id+'" class="btnList" title="${text("停用意见")}" data-confirm="${text("确认要停用该意见吗?")}"><i class="fa icon-ban"></i></a>&nbsp;');
} else if (row.status == Global.STATUS_DISABLE){
actions.push('<a href="${ctx}/app/appComment/enable?id='+row.id+'" class="btnList" title="${text("启用意见")}" data-confirm="${text("确认要启用该意见吗?")}"><i class="glyphicon glyphicon-ok-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/app/appComment/enable?id='+row.id+'" class="btnList" title="${text("启用意见")}" data-confirm="${text("确认要启用该意见吗?")}"><i class="fa icon-check"></i></a>&nbsp;');
}
actions.push('<a href="${ctx}/app/appComment/delete?id='+row.id+'" class="btnList" title="${text("删除意见")}" data-confirm="${text("确认要删除该意见吗?")}"><i class="fa fa-trash-o"></i></a>&nbsp;');
//# }

View File

@@ -8,9 +8,9 @@
<div class="box-tools pull-right">
<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
<% if(hasPermi('app:appUpgrade:edit')){ %>
<a href="${ctx}/app/appUpgrade/form" class="btn btn-default btnTool" title="${text('新增版本')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<a href="${ctx}/app/appUpgrade/form" class="btn btn-primary btnTool" title="${text('新增版本')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<% } %>
<a href="#" class="btn btn-default" id="btnSetting" title="${text('设置')}"><i class="fa fa-navicon"></i></a>
<a href="#" class="btn-setting" id="btnSetting" title="${text('设置')}"><i class="fa icon-settings"></i></a>
</div>
</div>
<div class="box-body">
@@ -82,11 +82,11 @@ $('#dataGrid').dataGrid({
{header:'${text("操作")}', name:'actions', width:120, formatter: function(val, obj, row, act){
var actions = [];
//# if(hasPermi('app:appUpgrade:edit')){
actions.push('<a href="${ctx}/app/appUpgrade/form?id='+row.id+'" class="btnList" title="${text("编辑版本")}"><i class="fa fa-pencil"></i></a>&nbsp;');
actions.push('<a href="${ctx}/app/appUpgrade/form?id='+row.id+'" class="btnList" title="${text("编辑版本")}"><i class="fa icon-note"></i></a>&nbsp;');
if (row.status == Global.STATUS_NORMAL){
actions.push('<a href="${ctx}/app/appUpgrade/disable?id='+row.id+'" class="btnList" title="${text("停用版本")}" data-confirm="${text("确认要停用该版本吗?")}"><i class="glyphicon glyphicon-ban-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/app/appUpgrade/disable?id='+row.id+'" class="btnList" title="${text("停用版本")}" data-confirm="${text("确认要停用该版本吗?")}"><i class="fa icon-ban"></i></a>&nbsp;');
} else if (row.status == Global.STATUS_DISABLE){
actions.push('<a href="${ctx}/app/appUpgrade/enable?id='+row.id+'" class="btnList" title="${text("启用版本")}" data-confirm="${text("确认要启用该版本吗?")}"><i class="glyphicon glyphicon-ok-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/app/appUpgrade/enable?id='+row.id+'" class="btnList" title="${text("启用版本")}" data-confirm="${text("确认要启用该版本吗?")}"><i class="fa icon-check"></i></a>&nbsp;');
}
actions.push('<a href="${ctx}/app/appUpgrade/delete?id='+row.id+'" class="btnList" title="${text("删除版本")}" data-confirm="${text("确认要删除该版本吗?")}"><i class="fa fa-trash-o"></i></a>&nbsp;');
//# }

147
modules/cms-ai/README.md Normal file
View File

@@ -0,0 +1,147 @@
## 模块简介
本模块基于 Spring AI 和 JeeSite 内容管理系统CMS并结合了检索增强生成Retrieval-Augmented Generation, RAG技术
和先进的人工智能算法AI打造了一个强大的企业级知识管理和智能对话平台。该模块专为企业设计旨在通过高效的知识获取和精准的对话能力
提升企业的信息管理效率和员工的工作效能。
检索增强生成 RAG 技术使系统能够自动从海量的企业文档中检索最相关的信息,并将其融入到生成的回答中,确保每一次查询都
能获得最新且准确的结果。这种检索与生成相结合的方式,不仅提高了信息检索的准确性,还增强了回答的上下文关联性,
特别适合处理复杂的企业知识库。
此外该模块支持在线大模型和本地部署的大模型Ollama、DeepSeek、通义千问理论上支持所有 OpenAPI 标准接口的 AI 提供商。
并能无缝集成多种嵌入式 AI 模型的向量数据库,如 Chroma、PGVector、Elasticsearch、Milvus 等,实现高效的数据存储、检索及分析。
无论是大规模数据集还是高度专业化的领域知识JeeSite CMS + RAG + AI 都能提供定制化解决方案,满足企业多样化的业务需求和技术要求。
企业可以轻松管理和访问复杂的信息资源,促进内部知识共享和创新,从而在竞争激烈的市场环境中保持领先地位。
优势:本模块结构清晰,代码简洁易懂,不管是正式项目、或是学习 AI 技术、都能轻松应对读懂源代码。
## 在线演示
* 地址:<https://vue.jeesite.com/cms/chat/index>
## 源码下载
* 后端:<https://gitee.com/thinkgem/jeesite5/tree/v5.springboot3/modules/cms-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 提供商。
* 本地模型:使用 [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) 文件,有注释。
## 向量数据库配置
支持的向量库列表:<https://docs.spring.io/spring-ai/reference/1.0/api/vectordbs.html>
* Chroma
* PGVector
* Elasticsearch
* Milvus
* ...
具体配置项详见 [jeesite-cms-ai.yml](https://gitee.com/thinkgem/jeesite5/blob/v5.springboot3/modules/cms-ai/src/main/resources/config/jeesite-cms-ai.yml) 文件,有注释。
### 安装 Chroma
```sh
docker run --name chroma -p 8000:8000 ghcr.io/chroma-core/chroma:0.5.20
```
### 安装 PGVector
```sh
docker run -d --name pgvector -p 5433:5432 -e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres pgvector/pgvector:pg17
```
* 进入容器
```sql
docker exec -it pgvector psql -U postgres
```
* 建库语句
```sql
CREATE DATABASE "jeesite-ai";
-- 激活数据库
\connect "jeesite-ai";
-- 建立数据表和索引
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- 使用 all-minilm 模型时创建
DROP TABLE IF EXISTS vector_store_384;
CREATE TABLE IF NOT EXISTS vector_store_384 (
id varchar(64) DEFAULT uuid_generate_v4() PRIMARY KEY,
content text,
metadata json,
embedding vector(384)
);
CREATE INDEX ON vector_store_384 USING HNSW (embedding vector_cosine_ops);
-- 使用 nomic-embed-text 模型时创建
DROP TABLE IF EXISTS vector_store_786;
CREATE TABLE IF NOT EXISTS vector_store_786 (
id varchar(64) DEFAULT uuid_generate_v4() PRIMARY KEY,
content text,
metadata json,
embedding vector(768)
);
CREATE INDEX ON vector_store_786 USING HNSW (embedding vector_cosine_ops);
-- 使用 bge-m3 模型时创建
DROP TABLE IF EXISTS vector_store_1024;
CREATE TABLE IF NOT EXISTS vector_store_1024 (
id varchar(64) DEFAULT uuid_generate_v4() PRIMARY KEY,
content text,
metadata json,
embedding vector(1024)
);
CREATE INDEX ON vector_store_1024 USING HNSW (embedding vector_cosine_ops);
```
## 创建 AI 菜单
系统管理 -> 系统设置 -> 菜单管理 -> 新增
* 菜单名称AI 助手
* 菜单地址:/cms/chat/index
## 工具调用 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 实现你的业务联动。
## 授权协议声明
1. 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款。
2. 不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
3. 在使用本软件时,由于它集成了众多第三方开源软件,请共同遵守这些开源软件的使用许可条款规定。
4. 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议、版权声明和其他原作者
规定需要包含的说明(请尊重原作者的著作权,不要删除或修改文件中的`Copyright``@author`信息)
更不要,全局替换源代码中的 jeesite 或 ThinkGem 等字样,否则你将违反本协议条款承担责任。
5. 您若套用本软件的一些代码或功能参考,请保留源文件中的版权和作者,需要在您的软件介绍明显位置
说明出处,举例:本软件基于 JeeSite 快速开发平台并附带链接http://jeesite.com
6. 任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
7. 如果你对本软件有改进,希望可以贡献给我们,共同进步。
8. 本项目已申请软件著作权,请尊重开源,感谢阅读。
9. 无用户数限制,无在线人数限制,放心使用。
## 技术支持与服务
* 本软件免费,我们也提供了相应的收费服务,因为:
* 没有资金的支撑就很难得到发展,特别是一个好的产品,如果 JeeSite 帮助了您,请为我们点赞。支持我们,您可以获得更多回馈,我们会把公益事业做的更好,开放更多资源,回报社区和社会。请给我们一些动力吧,在此非常感谢已支持我们的朋友!
* **联系我们**:请访问技术支持与服务页面:<http://s.jeesite.com>

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

3590
modules/cms-ai/db/cms-ai.erm Normal file

File diff suppressed because it is too large Load Diff

150
modules/cms-ai/pom.xml Normal file
View File

@@ -0,0 +1,150 @@
<?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</artifactId>
<version>5.11.1.springboot3-SNAPSHOT</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<artifactId>jeesite-module-cms-ai</artifactId>
<packaging>jar</packaging>
<name>JeeSite Module CMS+RAG+AI</name>
<url>http://jeesite.com</url>
<inceptionYear>2013-Now</inceptionYear>
<properties>
<spring-ai.version>1.0.0-M7</spring-ai.version>
</properties>
<dependencies>
<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>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
<!-- Chroma 向量数据库 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-chroma</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<!-- PG 向量数据库
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency> -->
<!-- ES 向量数据库
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
</dependency> -->
<!-- Milvus 向量数据库
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-milvus</artifactId>
<exclusions>
<exclusion>
<artifactId>slf4j-reload4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-resolver-dns-native-macos</artifactId>
<classifier>osx-aarch_64</classifier>
</dependency> -->
<!-- HTML 转 Markdown -->
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-html2md-converter</artifactId>
<version>0.64.8</version>
</dependency>
<!-- Office、zip 等文件内容解析 -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parsers-standard-package</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<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>
<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,59 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.modules.cms.ai.config;
import com.jeesite.common.datasource.DataSourceHolder;
import com.jeesite.common.lang.StringUtils;
import com.jeesite.modules.cms.ai.properties.CmsAiProperties;
import com.jeesite.modules.cms.ai.tools.CmsAiTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* AI 聊天配置类
* @author ThinkGem
*/
@Configuration
@EnableConfigurationProperties(CmsAiProperties.class)
public class CmsAiChatConfig {
/**
* 聊天对话客户端
* @author ThinkGem
*/
@Bean
public ChatClient chatClient(ChatClient.Builder builder, CmsAiProperties properties) {
if (StringUtils.isNotBlank(properties.getDefaultSystem())) {
builder.defaultSystem(properties.getDefaultSystem());
}
if (properties.getToolCalls()) {
builder.defaultTools(new CmsAiTools());
}
return builder.build();
}
// @Bean
// public BatchingStrategy batchingStrategy() {
// return new TokenCountBatchingStrategy(EncodingType.CL100K_BASE, Integer.MAX_VALUE, 0.1);
// }
/**
* PG向量库数据源
* @author ThinkGem
*/
@Bean
@Primary
@ConditionalOnProperty(name = "jdbc.ds_pgvector.type")
public JdbcTemplate pgVectorStoreJdbcTemplate() {
return DataSourceHolder.getRoutingDataSource()
.getJdbcTemplate("ds_pgvector");
}
}

View File

@@ -0,0 +1,129 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
*/
package com.jeesite.modules.cms.ai.config;
import com.jeesite.common.mapper.JsonMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 推理模型OpenAI兼容处理
* @author ThinkGem
*/
@Configuration
public class WebClientThinkConfig {
private final Logger logger = LoggerFactory.getLogger(WebClientThinkConfig.class);
@Bean
@ConditionalOnMissingBean
public WebClientCustomizer webClientCustomizerThink() {
return webClientBuilder -> {
ExchangeFilterFunction requestFilter = ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
logger.trace("Request url: {}: {}", clientRequest.method(), clientRequest.url());
return Mono.just(clientRequest);
});
ExchangeFilterFunction responseFilter = ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
logger.trace("Response status: {}", clientResponse.statusCode());
AtomicBoolean thinkingFlag = new AtomicBoolean(false);
Flux<DataBuffer> modifiedBody = clientResponse.bodyToFlux(DataBuffer.class)
.map(buf -> {
byte[] bytes = new byte[buf.readableByteCount()];
buf.read(bytes);
DataBufferUtils.release(buf);
return new String(bytes, StandardCharsets.UTF_8);
})
.flatMap(eventString -> {
logger.trace("Original response: ==> {}", eventString);
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.substring("data: ".length()).trim();
if (!(StringUtils.startsWith(jsonPart, "{")
&& StringUtils.endsWith(jsonPart, "}")
&& !"data: [DONE]".equals(line))) {
lines.add(line);
continue;
}
Map<String, Object> map = JsonMapper.fromJson(jsonPart, Map.class);
if (map == null) {
lines.add(line);
continue;
}
// 修改内容字段
List<Object> choices = (List<Object>)map.get("choices");
if (choices == null) {
lines.add(line);
continue;
}
for (Object o : choices) {
Map<String, Object> choice = (Map<String, Object>) o;
if (choice == null) {
continue;
}
Map<String, Object> delta = (Map<String, Object>) choice.get("delta");
if (delta == null) {
continue;
}
String reasoningContent = (String) delta.get("reasoning_content");
String content = (String) delta.get("content");
if (reasoningContent != null) {
if (!thinkingFlag.get()) {
thinkingFlag.set(true);
delta.put("content", "<think>\n" + reasoningContent);
} else {
delta.put("content", reasoningContent);
}
} else {
if (thinkingFlag.get()) {
thinkingFlag.set(false);
delta.put("content", "</think>" + (content == null ? "" : content));
}
}
}
// 重新生成事件字符串
lines.add("data: " + JsonMapper.toJson(map));
}
String finalLine = StringUtils.join(lines, "\n");
logger.trace("Modified response: ==> {}", finalLine);
return Mono.just(finalLine);
})
.map(str -> {
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
return new DefaultDataBufferFactory().wrap(bytes);
});
ClientResponse modifiedResponse = ClientResponse.from(clientResponse)
.headers(headers -> headers.remove(HttpHeaders.CONTENT_LENGTH))
.body(modifiedBody)
.build();
return Mono.just(modifiedResponse);
});
webClientBuilder.filter(requestFilter).filter(responseFilter);
};
}
}

View File

@@ -0,0 +1,27 @@
package com.jeesite.modules.cms.ai.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("spring.ai")
public class CmsAiProperties {
private Boolean toolCalls = false;
private String defaultSystem = "";
public Boolean getToolCalls() {
return toolCalls;
}
public void setToolCalls(Boolean toolCalls) {
this.toolCalls = toolCalls;
}
public String getDefaultSystem() {
return defaultSystem;
}
public void setDefaultSystem(String defaultSystem) {
this.defaultSystem = defaultSystem;
}
}

View File

@@ -0,0 +1,186 @@
/**
* 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;
import com.jeesite.common.collect.ListUtils;
import com.jeesite.common.collect.MapUtils;
import com.jeesite.common.config.Global;
import com.jeesite.common.io.IOUtils;
import com.jeesite.common.lang.StringUtils;
import com.jeesite.common.lang.TimeUtils;
import com.jeesite.common.utils.PageUtils;
import com.jeesite.common.web.http.HttpClientUtils;
import com.jeesite.modules.cms.entity.Article;
import com.jeesite.modules.cms.service.ArticleVectorStore;
import com.jeesite.modules.cms.utils.CmsUtils;
import com.vladsch.flexmark.html.renderer.LinkType;
import com.vladsch.flexmark.html.renderer.ResolvedLink;
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
import com.vladsch.flexmark.html2md.converter.HtmlLinkResolver;
import com.vladsch.flexmark.html2md.converter.HtmlLinkResolverFactory;
import com.vladsch.flexmark.html2md.converter.HtmlNodeConverterContext;
import org.apache.tika.Tika;
import org.apache.tika.config.TikaConfig;
import org.apache.tika.exception.TikaException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.stereotype.Service;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* CMS 文章向量库存储
* @author ThinkGem
*/
@Service
public class ArticleVectorStoreImpl implements ArticleVectorStore {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private VectorStore vectorStore;
/**
* 保存文章到向量库
* @author ThinkGem
*/
@Override
public void save(Article article) {
Map<String, Object> metadata = MapUtils.newHashMap();
metadata.put("id", article.getId());
metadata.put("siteCode", article.getCategory().getSite().getSiteCode());
metadata.put("categoryCode", article.getCategory().getCategoryCode());
metadata.put("categoryName", article.getCategory().getCategoryName());
metadata.put("title", article.getTitle());
metadata.put("href", article.getHref());
metadata.put("keywords", article.getKeywords());
metadata.put("description", article.getDescription());
metadata.put("url", article.getUrl());
metadata.put("status", article.getStatus());
metadata.put("createBy", article.getCreateBy());
metadata.put("createDate", article.getCreateDate());
metadata.put("updateBy", article.getUpdateBy());
metadata.put("updateDate", article.getUpdateDate());
List<String> attachmentList = ListUtils.newArrayList();
HtmlLinkResolverFactory linkResolverFactory = new HtmlLinkResolverFactory() {
@Override
public @Nullable Set<Class<?>> getAfterDependents() {
return Set.of();
}
@Override
public @Nullable Set<Class<?>> getBeforeDependents() {
return Set.of();
}
@Override
public boolean affectsGlobalScope() {
return false;
}
@Override
public HtmlLinkResolver apply(HtmlNodeConverterContext htmlNodeConverterContext) {
return (node, context, resolvedLink) -> {
if ("a".equalsIgnoreCase(node.nodeName())) {
String href = node.attributes().get("href"); String url = href;
if (StringUtils.contains(url, "://")) {
try (InputStream is = HttpClientUtils.getInputStream(url, null)) {
String text = getDocumentText(is);
attachmentList.add(url + text);
} catch (IOException | TikaException e) {
logger.error(e.getMessage(), e);
}
} else {
String ctxPath = Global.getCtxPath();
if (StringUtils.isNotBlank(ctxPath) && StringUtils.startsWith(url, ctxPath)){
url = url.substring(ctxPath.length());
}
try (InputStream is = IOUtils.getFileInputStream(Global.getUserfilesBaseDir(url))){
String text = getDocumentText(is);
attachmentList.add(url + text);
} catch (IOException | TikaException e) {
logger.error(e.getMessage(), e);
}
}
return new ResolvedLink(LinkType.LINK, href);
}
return resolvedLink;
};
}
/**
* 获取文章附件中的内容
* @author ThinkGem
*/
private static @NotNull String getDocumentText(InputStream is) throws IOException, TikaException {
TikaConfig config = TikaConfig.getDefaultConfig();
String content = new Tika(config).parseToString(is);
return content.lines()
.map(String::strip).filter(line -> !line.isEmpty())
.reduce((a, b) -> a + System.lineSeparator() + b)
.orElse(StringUtils.EMPTY);
}
};
String content = article.getTitle() + ", " + article.getKeywords() + ", "
+ article.getDescription() + ", " + FlexmarkHtmlConverter.builder()
.linkResolverFactory(linkResolverFactory).build()
.convert(article.getArticleData().getContent())
+ ", attachment: " + attachmentList;
List<Document> documents = List.of(new Document(article.getId(), content, metadata));
List<Document> splitDocuments = new TokenTextSplitter().apply(documents);
this.delete(article); // 删除原数据
ListUtils.pageList(splitDocuments, 64, params -> {
vectorStore.add((List<Document>)params[0]); // 增加新数据
return null;
});
}
/**
* 删除向量库文章
* @author ThinkGem
*/
@Override
public void delete(Article article) {
if (StringUtils.isNotBlank(article.getId())) {
vectorStore.delete(new FilterExpressionBuilder().eq("id", article.getId()).build());
}
}
/**
* 重建向量库文章
* @author ThinkGem
*/
public String rebuild(Article article) {
logger.debug("开始重建向量库。 siteCode: {}, categoryCode: {}",
article.getCategory().getSite().getSiteCode(),
article.getCategory().getCategoryCode());
long start = System.currentTimeMillis();
try{
article.setIsQueryArticleData(true); // 查询文章内容
PageUtils.findList(article, null, e -> {
List<Article> list = CmsUtils.getArticleService().findList((Article) e);
if (!list.isEmpty()) {
list.forEach(this::save);
return true;
}
return false;
});
}catch(Exception ex){
logger.error("重建向量库失败", ex);
return "重建向量库失败:" + ex.getMessage();
}
String message = "重建向量库完成! 用时" + TimeUtils.formatTime(System.currentTimeMillis() - start) + "";
logger.debug(message);
return message;
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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;
import com.jeesite.common.cache.CacheUtils;
import com.jeesite.common.collect.ListUtils;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* AI 对话消息存储
* @author ThinkGem
*/
@Service
public class CacheChatMemory implements ChatMemory {
private static final String CMS_CHAT_MSG_CACHE = "cmsChatMsgCache";
@Override
public void add(String conversationId, List<Message> messages) {
List<Message> conversationHistory = CacheUtils.get(CMS_CHAT_MSG_CACHE, conversationId);
if (conversationHistory == null) {
conversationHistory = ListUtils.newArrayList();
}
conversationHistory.addAll(messages);
CacheUtils.put(CMS_CHAT_MSG_CACHE, conversationId, conversationHistory);
}
@Override
public List<Message> get(String conversationId, int lastN) {
List<Message> all = CacheUtils.get(CMS_CHAT_MSG_CACHE, conversationId);
return all != null ? all.stream().skip(Math.max(0, all.size() - lastN)).toList() : List.of();
}
@Override
public void clear(String conversationId) {
CacheUtils.remove(CMS_CHAT_MSG_CACHE, conversationId);
}
}

View File

@@ -0,0 +1,160 @@
/**
* 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;
import com.jeesite.common.cache.CacheUtils;
import com.jeesite.common.collect.MapUtils;
import com.jeesite.common.idgen.IdGen;
import com.jeesite.common.lang.DateUtils;
import com.jeesite.common.lang.StringUtils;
import com.jeesite.common.service.BaseService;
import com.jeesite.modules.sys.utils.UserUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.SignalType;
import java.util.List;
import java.util.Map;
/**
* AI 聊天服务类
* @author ThinkGem
*/
@Service
public class CmsAiChatService 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
private VectorStore vectorStore;
/**
* 获取聊天对话消息
* @author ThinkGem
*/
public List<Message> getChatMessage(String conversationId) {
return chatMemory.get(conversationId, 100);
}
private static String getChatCacheKey() {
String key = UserUtils.getUser().getId();
if (StringUtils.isBlank(key)) {
key = UserUtils.getSession().getId().toString();
}
return key;
}
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;
}
/**
* 新建或更新聊天对话
* @author ThinkGem
*/
public Map<String, Object> saveChatConversation(String conversationId, String title) {
if (StringUtils.isBlank(conversationId)) {
conversationId = IdGen.nextId();
}
if (StringUtils.isBlank(title)) {
title = "新对话 " + DateUtils.getTime();
}
Map<String, Object> map = MapUtils.newHashMap();
map.put("id", conversationId);
map.put("title", title);
Map<String, Map<String, Object>> cache = getChatCacheMap();
cache.put(conversationId, map);
CacheUtils.put(CMS_CHAT_CACHE, getChatCacheKey(), cache);
return map;
}
/**
* 删除聊天对话
* @author ThinkGem
*/
public void deleteChatConversation(String conversationId) {
Map<String, Map<String, Object>> cache = getChatCacheMap();
cache.remove(conversationId);
CacheUtils.put(CMS_CHAT_CACHE, getChatCacheKey(), cache);
chatMemory.clear(conversationId);
}
/**
* 聊天对话,流输出
* @author ThinkGem
*/
public Flux<ChatResponse> chatStream(String conversationId, String message, HttpServletRequest request) {
return chatClient.prompt()
.messages(
new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE))
)
.advisors(
new MessageChatMemoryAdvisor(chatMemory, conversationId, 1024),
new QuestionAnswerAdvisor(vectorStore, SearchRequest.builder().similarityThreshold(0.6F).topK(6).build())
)
.stream()
.chatResponse()
.doOnNext(response -> {
if (response.getResult() != null && StringUtils.isNotBlank(response.getResult().getOutput().getText())) {
AssistantMessage assistantMessage = (AssistantMessage)request.getAttribute("assistantMessage");
AssistantMessage currAssistantMessage = response.getResult().getOutput();
if (assistantMessage == null) {
request.setAttribute("assistantMessage", currAssistantMessage);
} else {
request.setAttribute("assistantMessage", new AssistantMessage(
assistantMessage.getText() + currAssistantMessage.getText(),
currAssistantMessage.getMetadata()));
}
}
})
.doFinally((signalType) -> {
if (signalType != SignalType.ON_COMPLETE) {
AssistantMessage assistantMessage = (AssistantMessage)request.getAttribute("assistantMessage");
if (assistantMessage != null) {
chatMemory.add(conversationId, assistantMessage);
} else if (signalType == SignalType.CANCEL) {
chatMemory.add(conversationId, new AssistantMessage(text("暂无消息,你已主动停止响应。")));
}
}
})
.onErrorResume(error -> {
String errorMessage = error.getMessage();
if (error instanceof WebClientResponseException webClientError) {
errorMessage = webClientError.getResponseBodyAsString();
}
AssistantMessage assistantMessage = new AssistantMessage(errorMessage);
chatMemory.add(conversationId, assistantMessage);
logger.error("Error message: {}", errorMessage);
return Flux.just(ChatResponse.builder()
.generations(List.of(new Generation(assistantMessage)))
.build());
});
}
}

View File

@@ -0,0 +1,40 @@
/**
* 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.tools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import java.time.LocalDateTime;
/**
* AI 工具调用、Tool calling需选择支持 Tools 的模型)
* @author ThinkGem
*/
public class CmsAiTools {
private final Logger logger = LoggerFactory.getLogger(CmsAiTools.class);
/**
* 未联网搜索的时候,可获取到服务器时间
*/
@Tool(description = "当前时间,当前日期,几点了")
public String getCurrentDateTime() {
String dateTime = "当前日期时间:" + LocalDateTime.now();
logger.info(dateTime + " ============== ");
return dateTime;
}
/**
* 你可以询问:打开客厅的灯,关闭卧室的灯(需创建新对话)
*/
@Tool(description = "房间里的灯打开或关闭")
public void turnLight(@ToolParam(description = "房间") String roomName, @ToolParam(description = "开关") boolean on) {
String message = roomName + " 房间里的灯被 " + (on ? "打开" : "关闭");
logger.info(message + " ============== ");
}
}

View File

@@ -0,0 +1,85 @@
/**
* 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;
import com.jeesite.common.config.Global;
import com.jeesite.common.web.BaseController;
import com.jeesite.modules.cms.ai.service.CmsAiChatService;
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.RestController;
import reactor.core.publisher.Flux;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* AI 聊天控制器类
* @author ThinkGem
*/
@RestController
@RequestMapping("${adminPath}/cms/chat")
public class CmsAiChatController extends BaseController {
@Autowired
private CmsAiChatService cmsAiChatService;
/**
* 获取聊天对话消息
* @author ThinkGem
*/
@RequestMapping("/message")
public List<Message> message(String id) {
return cmsAiChatService.getChatMessage(id);
}
/**
* 聊天对话列表
* @author ThinkGem
*/
@RequestMapping("/list")
public Collection<Map<String, Object>> list() {
return cmsAiChatService.getChatCacheMap().values().stream()
.sorted(Comparator.comparing(map -> (String) map.get("id"),
Comparator.reverseOrder())).collect(Collectors.toList());
}
/**
* 新建或更新聊天对话
* @author ThinkGem
*/
@RequestMapping("/save")
public String save(String id, String title) {
Map<String, Object> map = cmsAiChatService.saveChatConversation(id, title);
return renderResult(Global.TRUE, "保存成功", map);
}
/**
* 删除聊天对话
* @author ThinkGem
*/
@RequestMapping("/delete")
public String delete(String id) {
cmsAiChatService.deleteChatConversation(id);
return renderResult(Global.TRUE, "删除成功", id);
}
/**
* 聊天对话,流输出
* @author ThinkGem
*/
@RequestMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatResponse> stream(String id, String message, HttpServletRequest request) {
return cmsAiChatService.chatStream(id, message, request);
}
}

View File

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

View File

@@ -0,0 +1,152 @@
# 温馨提示不建议直接修改此文件为了平台升级方便建议将需要修改的参数值复制到application.yml里进行覆盖该参数值。
spring:
ai:
# 在线大模型【请在 pom.xml 中打开 openai 的注释,并注释上其它模型】
openai:
base-url: https://api.siliconflow.cn
api-key: ${SFLOW_APP_KEY}
#base-url: https://ai.gitee.com
#api-key: ${GITEE_APP_KEY}
#base-url: https://dashscope.aliyuncs.com/compatible-mode
#api-key: ${BAILIAN_APP_KEY}
# 聊天对话模型
chat:
options:
model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
#model: DeepSeek-R1-Distill-Qwen-14B
#model: deepseek-r1-distill-llama-8b
max-tokens: 1024
temperature: 0.6
top-p: 0.9
frequency-penalty: 0
#logprobs: true
# 向量库知识库模型(注意:不同的模型维度不同)
embedding:
options:
model: BAAI/bge-m3
#model: bge-large-zh-v1.5
dimensions: 512
#model: text-embedding-v3
#dimensions: 1024
# 本地大模型配置【请在 pom.xml 中打开 ollama 的注释,并注释上其它模型】
ollama:
base-url: http://localhost:11434
# 聊天对话模型
chat:
options:
model: qwen2.5
#model: deepseek-r1:7b
max-tokens: 1024
temperature: 0.6
top-p: 0.7
frequency-penalty: 0
# 向量库知识库模型(注意:不同的模型维度不同)
embedding:
# 维度 dimensions 设置为 384
#model: all-minilm:33m
# 维度 dimensions 设置为 768
#model: nomic-embed-text
# 维度 dimensions 设置为 1024
model: bge-m3
# 向量数据库配置
vectorstore:
# Chroma 向量数据库【请在 pom.xml 中打开 chroma 的注释,并注释上其它向量库】
chroma:
client:
host: http://testserver
port: 8000
initialize-schema: true
collection-name: vector_store
#collection-name: vector_store_1024
# Postgresql 向量数据库PG 连接配置,见下文,需要手动建表)【请在 pom.xml 中打开 pgvector 的注释,并注释上其它向量库】
pgvector:
id-type: TEXT
index-type: HNSW
distance-type: COSINE_DISTANCE
initialize-schema: false
#table-name: vector_store_384
#dimensions: 384
#table-name: vector_store_786
#dimensions: 768
table-name: vector_store_1024
dimensions: 1024
max-document-batch-size: 10000
# ES 向量数据库ES 连接配置,见下文)【请在 pom.xml 中打开 elasticsearch 的注释,并注释上其它向量库】
elasticsearch:
index-name: vector-index
initialize-schema: true
dimensions: 1024
similarity: cosine
# Milvus 向量数据库【请在 pom.xml 中打开 milvus 的注释,并注释上其它向量库】
milvus:
client:
host: "localhost"
port: 19530
username: "root"
password: "milvus"
initialize-schema: true
database-name: "default"
collection-name: "vector_store"
embedding-dimension: 384
index-type: HNSW
metric-type: COSINE
# 是否启用工具调用【例子详见 CmsAiTools.java 】
tool-calls: false
# 默认系统提示词
default-system: |
## 人物设定
你是我的知识库AI助手你把我当作朋友耐心真诚地回复我提出的相关问题。
你需要遵循以下原则,与关注者进行友善而有价值的沟通。
## 表达方式:
1. 使用简体中文回答我的问题。
2. 使用幽默有趣的方式与我沟通。
3. 可以用少量表情,避免过多表情。
4. 增加互动,如 “您的看法如何?”
# ========= Postgresql 向量数据库数据源 =========
#jdbc:
# ds_pgvector:
# type: postgresql
# driver: org.postgresql.Driver
# url: jdbc:postgresql://127.0.0.1:5433/jeesite-ai
# username: postgres
# password: postgres
# testSql: SELECT 1
# pool:
# init: 0
# minIdle: 0
# breakAfterAcquireFailure: true
# ========= ES 向量数据库连接配置 =========
#spring.elasticsearch:
# socket-timeout: 120s
# connection-timeout: 120s
# uris: http://127.0.0.1:9200
# username: elastic
# password: elastic
# 对话消息存缓存,可自定义存数据库
j2cache:
caffeine:
region:
# 对话消息的超期时间,默认 30天根据需要可以设置更久。
cmsChatCache: 100000, 30d
cmsChatMsgCache: 100000, 30d
#logging:
# level:
# org.springframework: debug

View File

@@ -0,0 +1 @@
5.11.0

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-parent</artifactId>
<version>5.9.1.springboot3-SNAPSHOT</version>
<version>5.11.1.springboot3-SNAPSHOT</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
@@ -24,6 +24,12 @@
<artifactId>jeesite-module-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

View File

@@ -61,7 +61,7 @@ import java.util.Date;
@Column(name = "site_code"),
@Column(name = "site_name"),
})
}, orderBy = "a.update_date DESC", extWhereKeys = "dsfCategory"
}, orderBy = "a.weight DESC, a.update_date DESC"
)
public class Article extends DataEntity<Article> {

View File

@@ -28,23 +28,28 @@ import java.util.List;
@Column(name = "category_code", attrName = "categoryCode", label = "栏目编码", isPK = true),
@Column(name = "category_name", attrName = "categoryName", label = "栏目名称", queryType = QueryType.LIKE, isTreeName = true),
@Column(name = "site_code", attrName = "site.siteCode", label = "站点编码", queryType = QueryType.EQ),
@Column(name = "module_type", attrName = "moduleType", label = "模块类型"), @Column(name = "image", attrName = "image", label = "栏目图片"),
@Column(name = "href", attrName = "href", label = "链接"), @Column(name = "target", attrName = "target", label = "目标"),
@Column(name = "keywords", attrName = "keywords", label = "关键字"), @Column(name = "description", attrName = "description", label = "描述"),
@Column(name = "in_menu", attrName = "inMenu", label = "是否在导航中显示"), @Column(name = "in_list", attrName = "inList", label = "是否在分类页中显示列表"),
@Column(name = "module_type", attrName = "moduleType", label = "模块类型"),
@Column(name = "image", attrName = "image", label = "栏目图片"),
@Column(name = "href", attrName = "href", label = "链接"),
@Column(name = "target", attrName = "target", label = "目标"),
@Column(name = "keywords", attrName = "keywords", label = "关键字"),
@Column(name = "description", attrName = "description", label = "描述"),
@Column(name = "in_menu", attrName = "inMenu", label = "是否在导航中显示"),
@Column(name = "in_list", attrName = "inList", label = "是否在分类页中显示列表"),
@Column(name = "show_modes", attrName = "showModes", label = "展现模式"),
@Column(name = "is_need_audit", attrName = "isNeedAudit", label = "是否需要审核"),
@Column(name = "is_can_comment", attrName = "isCanComment", label = "是否允许评论"),
@Column(name = "custom_list_view", attrName = "customListView", label = "自定义列表视图"),
@Column(name = "custom_content_view", attrName = "customContentView", label = "自定义内容视图"),
@Column(name = "view_config", attrName = "viewConfig", label = "视图配置"), @Column(includeEntity = DataEntity.class),
@Column(name = "view_config", attrName = "viewConfig", label = "视图配置"),
@Column(includeEntity = DataEntity.class),
@Column(includeEntity = Extend.class, attrName = "extend"),
}, joinTable = {
@JoinTable(entity = Site.class, alias = "s",
on = "s.site_code = a.site_code", columns = {
@Column(name = "site_name"),
})
}, orderBy = "a.tree_sorts, a.category_code", extWhereKeys = "dsfCategory"
}, orderBy = "a.tree_sorts, a.category_code"
)
public class Category extends TreeEntity<Category> {
@@ -109,6 +114,14 @@ public class Category extends TreeEntity<Category> {
this.categoryCode = categoryCode;
}
public String getCategoryCode_like() {
return sqlMap().getWhere().getValue("category_code", QueryType.LIKE);
}
public void setCategoryCode_like(String categoryCode) {
sqlMap().getWhere().and("category_code", QueryType.LIKE, categoryCode);
}
@NotBlank(message = "栏目名称不能为空")
@Size(min = 0, max = 100, message = "栏目名称长度不能超过 100 个字符")
public String getCategoryName() {

View File

@@ -29,7 +29,7 @@ import java.util.List;
@Column(name = "site_sort", attrName = "siteSort", label = "站点排序号"),
@Column(name = "title", attrName = "title", label = "站点标题", queryType = QueryType.LIKE),
@Column(name = "logo", attrName = "logo", label = "站点Logo"),
@Column(name = "domain_name", attrName = "domain", label = "站点域名"),
@Column(name = "domain_name", attrName = "domain", label = "站点域名", queryType = QueryType.LIKE),
@Column(name = "keywords", attrName = "keywords", label = "关键字"),
@Column(name = "description", attrName = "description", label = "描述"),
@Column(name = "theme", attrName = "theme", label = "主题"),

View File

@@ -0,0 +1,81 @@
/**
* 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.properties.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* CmsProperties
* @author ThinkGem
* @version 2022-4-10
*/
@ConfigurationProperties(prefix = "cms")
public class CmsProperties {
private PageCache pageCache = new PageCache();
public static class PageCache {
/**
* 是否开启页面静态化缓存
*/
private Boolean enabled = false;
/**
* 缓存名称标识
*/
private String cacheName = "cmsPageCache";
/**
* 拦截的网页地址
*/
private String urlPatterns = "${frontPath}/*";
/**
* 只静态化 .html 后缀的网页
*/
private String urlSuffixes = ".html";
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public String getCacheName() {
return cacheName;
}
public void setCacheName(String cacheName) {
this.cacheName = cacheName;
}
public String getUrlPatterns() {
return urlPatterns;
}
public void setUrlPatterns(String urlPatterns) {
this.urlPatterns = urlPatterns;
}
public String getUrlSuffixes() {
return urlSuffixes;
}
public void setUrlSuffixes(String urlSuffixes) {
this.urlSuffixes = urlSuffixes;
}
}
public PageCache getPageCache() {
return pageCache;
}
public void setPageCache(PageCache pageCache) {
this.pageCache = pageCache;
}
}

View File

@@ -44,6 +44,8 @@ public class ArticleService extends CrudService<ArticleDao, Article> {
@Autowired(required = false)
private ArticleIndexService articleIndexService;
@Autowired(required = false)
private ArticleVectorStore articleVectorStore;
@Autowired(required = false)
private PageCacheService pageCacheService;
private static final ExecutorService updateExpiredWeightThreadPool = new ThreadPoolExecutor(5, 20,
@@ -166,6 +168,10 @@ public class ArticleService extends CrudService<ArticleDao, Article> {
if (articleIndexService != null && Article.STATUS_NORMAL.equals(article.getStatus())) {
articleIndexService.save(article);
}
// 保存文章到向量数据库
if (articleVectorStore != null && Article.STATUS_NORMAL.equals(article.getStatus())) {
articleVectorStore.save(article);
}
// 清理首页、栏目和文章页面缓存
if (pageCacheService != null) {
pageCacheService.clearCache(article);
@@ -188,6 +194,14 @@ public class ArticleService extends CrudService<ArticleDao, Article> {
articleIndexService.delete(article);
}
}
// 保存文章到向量数据库
if (articleVectorStore != null) {
if (Article.STATUS_NORMAL.equals(article.getStatus())) {
articleVectorStore.save(article);
} else {
articleVectorStore.delete(article);
}
}
// 清理首页、栏目和文章页面缓存
if (pageCacheService != null) {
pageCacheService.clearCache(article);
@@ -221,6 +235,10 @@ public class ArticleService extends CrudService<ArticleDao, Article> {
if (articleIndexService != null) {
articleIndexService.delete(article);
}
// 保存文章到向量数据库
if (articleVectorStore != null) {
articleVectorStore.delete(article);
}
// 清理首页、栏目和文章页面缓存
if (pageCacheService != null) {
pageCacheService.clearCache(article);

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.modules.cms.service;
import com.jeesite.common.entity.Page;
import com.jeesite.modules.cms.entity.Article;
import java.util.Map;
/**
* 文章向量存储服务类
* @author ThinkGem
*/
public interface ArticleVectorStore {
/**
* 保存索引
* @author ThinkGem
*/
void save(Article article);
/**
* 删除索引
* @author ThinkGem
*/
void delete(Article article);
/**
* 重建向量库
* @author ThinkGem
*/
String rebuild(Article article);
}

View File

@@ -27,6 +27,8 @@ public class CategoryService extends TreeService<CategoryDao, Category> {
@Autowired(required = false)
private ArticleIndexService articleIndexService;
@Autowired(required = false)
private ArticleVectorStore articleVectorStore;
@Autowired(required = false)
private PageCacheService pageCacheService;
/**
@@ -125,4 +127,15 @@ public class CategoryService extends TreeService<CategoryDao, Category> {
return articleIndexService.rebuild(new Article(category));
}
/**
* 重建向量数据库
* @author ThinkGem
*/
public String rebuildVectorStore(Category category) {
if (articleVectorStore == null) {
return text("您好,系统未安装全文检索模块");
}
return articleVectorStore.rebuild(new Article(category));
}
}

View File

@@ -27,6 +27,8 @@ public class SiteService extends CrudService<SiteDao, Site> {
@Autowired(required = false)
private ArticleIndexService articleIndexService;
@Autowired(required = false)
private ArticleVectorStore articleVectorStore;
@Autowired(required = false)
private PageCacheService pageCacheService;
/**
@@ -120,5 +122,16 @@ public class SiteService extends CrudService<SiteDao, Site> {
}
return articleIndexService.rebuild(new Article(new Category(site)));
}
/**
* 重建向量数据库
* @author ThinkGem
*/
public String rebuildVectorStore(Site site) {
if (articleVectorStore == null) {
return text("您好系统未安装内容管理AI模块");
}
return articleVectorStore.rebuild(new Article(new Category(site)));
}
}

View File

@@ -16,8 +16,8 @@ import com.jeesite.modules.cms.entity.Site;
import com.jeesite.modules.cms.service.CategoryService;
import com.jeesite.modules.cms.service.FileTempleteService;
import com.jeesite.modules.cms.utils.CmsUtils;
import com.jeesite.modules.sys.entity.Office;
import com.jeesite.modules.sys.utils.DictUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
@@ -52,10 +52,23 @@ public class CategoryController extends BaseController {
* 获取数据
*/
@ModelAttribute
public Category get(String categoryCode, boolean isNewRecord) {
public Category get(String categoryCode, boolean isNewRecord, HttpServletRequest request) {
if (StringUtils.endsWith(request.getRequestURI(), "listData")) {
return new Category();
}
return categoryService.get(categoryCode, isNewRecord);
}
/**
* 管理主页
*/
@RequiresPermissions("cms:category:view")
@RequestMapping(value = "index")
public String index(Category category, Model model) {
model.addAttribute("category", category);
return "modules/cms/categoryIndex";
}
/**
* 查询列表
*/
@@ -78,17 +91,18 @@ public class CategoryController extends BaseController {
@ResponseBody
public List<Category> listData(Category category) {
if (StringUtils.isBlank(category.getParentCode())) {
category.setParentCode(Office.ROOT_CODE);
category.setParentCode(Category.ROOT_CODE);
}
if (StringUtils.isBlank(category.getSite().getSiteCode())) {
category.setSite(new Site(Site.getCurrentSiteCode()));
}
List<Category> list = null;
if (StringUtils.isNotBlank(category.getCategoryCode())) {
list = ListUtils.newArrayList(get(category.getCategoryCode(), false));
} else {
list = categoryService.findList(category);
if (StringUtils.isNotBlank(category.getCategoryCode())
|| StringUtils.isNotBlank(category.getCategoryCode_like())
|| StringUtils.isNotBlank(category.getCategoryName())
|| StringUtils.isNotBlank(category.getRemarks())) {
category.setParentCode(null);
}
List<Category> list = categoryService.findList(category);
return list;
}
@@ -212,7 +226,7 @@ public class CategoryController extends BaseController {
public String disable(Category category) {
Category where = new Category();
where.setStatus(Category.STATUS_NORMAL);
where.setParentCodes("," + category.getId() + ",");
where.setParentCodes_rightLike(category.getParentCodes() + category.getId() + ",");
long count = categoryService.findCount(where);
if (count > 0) {
return renderResult(Global.FALSE, text("该栏目表包含未停用的子栏目表!"));
@@ -256,6 +270,17 @@ public class CategoryController extends BaseController {
return renderResult(Global.TRUE, categoryService.rebuildIndex(category));
}
/**
* 重建向量数据库
* @author ThinkGem
*/
@RequiresPermissions("cms:category:rebuildVectorStore")
@ResponseBody
@RequestMapping(value = "rebuildVectorStore")
public String rebuildVectorStore(Category category) {
return renderResult(Global.TRUE, categoryService.rebuildVectorStore(category));
}
/**
* 获取树结构数据
* @param excludeCode 排除的Code

View File

@@ -8,12 +8,12 @@ import com.jeesite.common.config.Global;
import com.jeesite.common.entity.Page;
import com.jeesite.common.lang.StringUtils;
import com.jeesite.common.web.BaseController;
import com.jeesite.common.web.CookieUtils;
import com.jeesite.modules.cms.entity.Site;
import com.jeesite.modules.cms.service.FileTempleteService;
import com.jeesite.modules.cms.service.SiteService;
import com.jeesite.modules.sys.utils.CorpUtils;
import com.jeesite.modules.sys.utils.UserUtils;
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;
@@ -24,8 +24,6 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
@@ -144,6 +142,17 @@ public class SiteController extends BaseController {
public String rebuildIndex(Site site) {
return renderResult(Global.TRUE, siteService.rebuildIndex(site));
}
/**
* 重建向量数据库
* @author ThinkGem
*/
@RequiresPermissions("cms:site:rebuildVectorStore")
@ResponseBody
@RequestMapping(value = "rebuildVectorStore")
public String rebuildVectorStore(Site site) {
return renderResult(Global.TRUE, siteService.rebuildVectorStore(site));
}
/**
* 选择站点

View File

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

View File

@@ -3,7 +3,7 @@
cms:
pageCache:
# 是否开启页面静态化缓存
#enabled: true
enabled: false
# 缓存名称标识
cacheName: cmsPageCache
# 拦截的网页地址
@@ -20,5 +20,7 @@ j2cache:
#spring:
# elasticsearch:
# enabled: true
# uris: http://Win11:9200
# uris: http://127.0.0.1:9200
# connection-timeout: 120s
# username: elastic
# password: elastic

View File

@@ -29,4 +29,9 @@
5.8.0
5.8.1
5.9.0
5.9.1
5.9.1
5.9.2
5.10.0
5.10.1
5.11.0
5.11.1

View File

@@ -4,6 +4,10 @@ body>.navbar{-webkit-transition:background-color .3s ease-in;transition:backgrou
body>.navbar-transparent{background-color:transparent}
body>.navbar-transparent .navbar-nav>.open>a{background-color:transparent!important}
}
h1,.h1{font-size:26px}
h2,.h2{font-size:24px}
h3,.h3{font-size:22px}
p{margin:5px 0 10px;line-height:1.75;}
#home{padding-top:0}
#home .navbar-brand{padding:13.5px 15px 12.5px}
#home .navbar-brand>img{display:inline;margin:0 10px;height:100%}
@@ -80,7 +84,7 @@ footer p{clear:left;margin-bottom:0}
.article-title {color:#333;font-size:30px;text-align:center;border-bottom:1px solid #ddd;padding:15px 20px 20px 20px;margin-bottom:30px;}
.article-info {border-top:1px solid #ddd;padding:10px;margin:30px 0 0;}
.article-desc {padding:8px 10px 8px;margin:10px 20px 20px 35px;font-size:14px;}
.article-content {padding-top:20px;}
.article-content {padding-top:10px;}
.pagination {margin:8px 0;display:block;/* text-align:center; */font-size:13px;} /* .pagination .controls a{border:0;} */
.pagination>li>a, .pagination>li>span {min-width:37px;text-align:center;padding:6px;border:1px solid #ddd;background:transparent;/* border-radius:3px; */}

View File

@@ -1210,7 +1210,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
@@ -1255,7 +1255,7 @@
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Source Code</h4>
</div>
<div class="modal-body">

View File

@@ -6,10 +6,10 @@
<i class="fa icon-note"></i> ${text('内容管理')}
</div>
<div class="box-tools pull-right">
<a href="${ctxFront}/index" target="_blank" class="btn btn-default" title="${text('访问站点')}"><i class="fa fa-globe"></i> ${text('访问网站')}</a>
<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
<a href="${ctxFront}/index" target="_blank" class="btn btn-default" title="${text('访问站点')}"><i class="fa fa-globe"></i> ${text('访问网站')}</a>
<% if(hasPermi('cms:article:edit')){ %>
<a href="${ctx}/cms/article/form" onclick="$(this).data('href', this.href+'?category.categoryCode='+$('#categoryCode').val())" class="btn btn-default btnTool" title="${text('新增文章')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<a href="${ctx}/cms/article/form" onclick="$(this).data('href', this.href+'?category.categoryCode='+$('#categoryCode').val())" class="btn btn-primary btnTool" title="${text('新增文章')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<% } %>
</div>
</div>
@@ -71,12 +71,12 @@ $('#dataGrid').dataGrid({
{header:'${text("操作")}', name:'actions', width:150, formatter: function(val, obj, row, act){
var actions = [];
//# if(hasPermi('cms:article:edit')){
actions.push('<a href="${ctx}/cms/article/form?id='+row.id+'" class="btnList" title="${text("编辑文章")}"><i class="fa fa-pencil"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/article/form?id='+row.id+'" class="btnList" title="${text("编辑文章")}"><i class="fa icon-note"></i></a>&nbsp;');
if (row.status == Global.STATUS_NORMAL){
actions.push('<a href="${ctx}/cms/article/disable?id='+row.id+'" class="btnList" title="${text("停用文章")}" data-confirm="${text("确认要停用该文章吗?")}"><i class="glyphicon glyphicon-ban-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/article/disable?id='+row.id+'" class="btnList" title="${text("停用文章")}" data-confirm="${text("确认要停用该文章吗?")}"><i class="fa icon-ban"></i></a>&nbsp;');
}
if (row.status == Global.STATUS_DISABLE){
actions.push('<a href="${ctx}/cms/article/enable?id='+row.id+'" class="btnList" title="${text("启用文章")}" data-confirm="${text("确认要启用该文章吗?")}"><i class="glyphicon glyphicon-ok-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/article/enable?id='+row.id+'" class="btnList" title="${text("启用文章")}" data-confirm="${text("确认要启用该文章吗?")}"><i class="fa icon-check"></i></a>&nbsp;');
}
actions.push('<a href="${ctx}/cms/article/delete?id='+row.id+'" class="btnList" title="${text("删除文章")}" data-confirm="${text("确认要删除该文章吗?")}"><i class="fa fa-trash-o"></i></a>&nbsp;');
if (row.status == Global.STATUS_NORMAL){

View File

@@ -0,0 +1,78 @@
<% layout('/layouts/default.html', {title: '栏目管理', libs: ['layout','zTree']}){ %>
<div class="ui-layout-west">
<div class="main-content">
<div class="box box-main">
<div class="box-header">
<div class="box-title">
<i class="fa icon-grid"></i> ${text('栏目导航')}
</div>
<div class="box-tools pull-right">
<% if(hasPermi('bpm:bpmCategory:edit')){ %>
<button type="button" class="btn btn-box-tool addTabPage" data-href="${ctx}/cms/category/list" title="${text('栏目管理')}"><i class="fa fa-edit"></i></button>
<% } %>
<button type="button" class="btn btn-box-tool" id="btnExpand" title="${text('展开')}" style="display:none;"><i class="fa fa-chevron-up"></i></button>
<button type="button" class="btn btn-box-tool" id="btnCollapse" title="${text('折叠')}"><i class="fa fa-chevron-down"></i></button>
<button type="button" class="btn btn-box-tool" id="btnRefresh" title="${text('刷新')}"><i class="fa fa-refresh"></i></button>
</div>
</div>
<div class="ui-layout-content">
<div id="tree" class="ztree"></div>
</div>
</div>
</div>
</div>
<div class="ui-layout-center">
<iframe id="mainFrame" name="mainFrame" class="ui-layout-content p0"
src="${ctx}/cms/category/list"></iframe>
</div>
<% } %>
<script>
//# // 初始化布局
$('body').layout({
west__initClosed: $(window).width() <= 767, // 是否默认关闭
west__size: 190
});
//# // 主页框架
var win = $("#mainFrame")[0].contentWindow;
//# // 树结构初始化加载
var setting = {view:{selectedMulti:false},data:{key:{title:"title"},simpleData:{enable:true}},
async:{enable:true,autoParam:["id=parentCode"],url:"${ctx}/cms/category/treeData"},
callback:{onClick:function(event, treeId, treeNode){
tree.expandNode(treeNode);
//win.$('button[type=reset]').click();
win.$('#categoryCode').val(treeNode.id);
win.page();
}}
}, tree, loadTree = function(){
js.ajaxSubmit(setting.async.url+"?___t="+new Date().getTime(), {
parentCode:'${parameter.parentCode!}'}, function(data){
tree = $.fn.zTree.init($("#tree"), setting, data);
var level = -1, nodes;
while (++level <= 1) {
nodes = tree.getNodesByParam("level", level);
if (nodes.length > 10) { break; }
for(var i=0; i<nodes.length; i++) {
tree.expandNode(nodes[i], true, false, false);
}
}
}, null, null, js.text('loading.message'));
};loadTree();
//# // 工具栏按钮绑定
$('#btnExpand').click(function(){
tree.expandAll(true);
$(this).hide();
$('#btnCollapse').show();
});
$('#btnCollapse').click(function(){
tree.expandAll(false);
$(this).hide();
$('#btnExpand').show();
});
$('#btnRefresh').click(function(){
loadTree();
});
//调用子页分页函数
function page(){
win.page();
}
</script>

View File

@@ -24,7 +24,7 @@
<a href="#" class="btn btn-default" id="btnExpandTreeNode" title="${text('展开一级')}"><i class="fa fa-angle-double-down"></i>${text('展开')}</a>
<a href="#" class="btn btn-default" id="btnCollapseTreeNode" title="${text('折叠全部')}"><i class="fa fa-angle-double-up"></i> ${text('折叠')}</a> <%
if(hasPermi('cms:category:edit')){ %>
<a href="${ctx}/cms/category/form?site.siteCode=${category.site.siteCode}" class="btn btn-default btnTool" title="${text('栏目添加')}"><i class="fa fa-plus"></i>${text('新增')}</a>
<a href="${ctx}/cms/category/form?site.siteCode=${category.site.siteCode}" class="btn btn-primary btnTool" title="${text('新增栏目')}"><i class="fa fa-plus"></i>${text('新增')}</a>
<% } %>
</div>
</div>
@@ -32,28 +32,35 @@
<#form:form id="searchForm" model="${category}" action="${ctx}/cms/category/listData" method="post" class="form-inline "
data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
<#form:hidden path="site.siteCode" maxlength="64" class="form-control width-120" />
<#form:hidden path="categoryCode" />
<div class="form-group">
<label class="control-label">${text('栏目名称')}</label>
<label class="control-label">${text('编码')}</label>
<div class="control-inline">
<#form:input path="categoryName" maxlength="100" class="form-control width-120" />
<#form:input path="categoryCode_like" maxlength="100" class="form-control width-90" />
</div>
</div>
<div class="form-group">
<label class="control-label">${text('模块类型')}</label>
<label class="control-label">${text('名称')}</label>
<div class="control-inline">
<#form:input path="moduleType" maxlength="50" class="form-control width-120" />
<#form:input path="categoryName" maxlength="100" class="form-control width-90" />
</div>
</div>
<div class="form-group">
<label class="control-label">${text('模型')}</label>
<div class="control-inline width-90">
<#form:select path="moduleType" dictType="cms_module_type" blankOption="true" class="form-control" />
</div>
</div>
<div class="form-group">
<label class="control-label">${text('状态')}</label>
<div class="control-inline width-120">
<div class="control-inline width-90">
<#form:select path="status" dictType="sys_search_status" blankOption="true" class="form-control" />
</div>
</div>
<div class="form-group">
<label class="control-label">${text('备注信息')}</label>
<label class="control-label">${text('备注')}</label>
<div class="control-inline">
<#form:input path="remarks" maxlength="500" class="form-control width-120" />
<#form:input path="remarks" maxlength="500" class="form-control width-90" />
</div>
</div>
<div class="form-group">
@@ -74,31 +81,40 @@ $('#dataGrid').dataGrid({
{header:'${text("名称")}', name:'categoryName', index:'a.category_name', width:250, align:"left", frozen:true, formatter: function(val, obj, row, act){
return '( '+row.categoryCode+' ) '+'<a href="${ctx}/cms/category/form?categoryCode='+row.categoryCode+'" class="btnList" data-title="${text("编辑栏目表")}">'+(val||row.id)+'</a>';
}},
{header:'${text("模型")}', name:'moduleType', index:'a.module_type', width:100, align:"center"},
{header:'${text("排序")}', name:'treeSort', index:'a.tree_sort', width:50, align:"center"},
{header:'${text("模型")}', name:'moduleType', index:'a.module_type', width:100, align:"center", formatter: function(val, obj, row, act){
return js.getDictLabel("#{@DictUtils.getDictListJson('cms_module_type')}", val, '未知', true);
}},
{header:'${text("排序")}', name:'treeSort', index:'a.tree_sort', width:60, align:"center"},
{header:'${text("导航栏目")}', name:'inMenu', index:'a.in_menu', width:80, fixed:true, align:"center", formatter: function(val, obj, row, act){
return js.getDictLabel("#{@DictUtils.getDictListJson('sys_show_hide')}", val, '未知', true);
}},
{header:'${text("栏目列表")}', name:'inList', index:'a.in_list', width:80, fixed:true, align:"center", formatter: function(val, obj, row, act){
return js.getDictLabel("#{@DictUtils.getDictListJson('sys_show_hide')}", val, '未知', true);
}},
{header:'${text("展现方式")}', name:'showModes', index:'a.show_modes', width:150, fixed:true, align:"center", formatter: function(val, obj, row, act){
{header:'${text("展现方式")}', name:'showModes', index:'a.show_modes', width:90, fixed:true, align:"center", formatter: function(val, obj, row, act){
return js.getDictLabel("#{@DictUtils.getDictListJson('cms_show_modes')}", val, '未知', true);
}},
{header:'${text("操作")}', name:'actions', width:150, formatter: function(val, obj, row, act){
{header:'${text("状态")}', name:'status', index:'a.status', width:90, fixed:true, align:"center", formatter: function(val, obj, row, act){
return js.getDictLabel("#{@DictUtils.getDictListJson('sys_status')}", val, '未知', true);
}},
{header:'${text("备注")}', name:'remarks', index:'a.remarks', width:100, align:"left"},
{header:'${text("操作")}', name:'actions', width:210, formatter: function(val, obj, row, act){
var actions = [];
//# if(hasPermi('cms:category:edit')){
actions.push('<a href="${ctx}/cms/category/form?categoryCode='+row.categoryCode+'" class="btnList" title="${text("编辑栏目表")}"><i class="fa fa-pencil"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/category/form?categoryCode='+row.categoryCode+'" class="btnList" title="${text("编辑栏目表")}"><i class="fa icon-note"></i></a>&nbsp;');
if (row.status == Global.STATUS_NORMAL){
actions.push('<a href="${ctx}/cms/category/disable?categoryCode='+row.categoryCode+'" class="btnList" title="${text("停用栏目表")}" data-confirm="${text("确认要停用该栏目表吗?")}"><i class="glyphicon glyphicon-ban-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/category/disable?categoryCode='+row.categoryCode+'" class="btnList" title="${text("停用栏目表")}" data-confirm="${text("确认要停用该栏目表吗?")}"><i class="fa icon-ban"></i></a>&nbsp;');
}
if (row.status == Global.STATUS_DISABLE){
actions.push('<a href="${ctx}/cms/category/enable?categoryCode='+row.categoryCode+'" class="btnList" title="${text("启用栏目表")}" data-confirm="${text("确认要启用该栏目表吗?")}"><i class="glyphicon glyphicon-ok-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/category/enable?categoryCode='+row.categoryCode+'" class="btnList" title="${text("启用栏目表")}" data-confirm="${text("确认要启用该栏目表吗?")}"><i class="fa icon-check"></i></a>&nbsp;');
}
actions.push('<a href="${ctx}/cms/category/delete?categoryCode='+row.categoryCode+'" class="btnList" title="${text("删除栏目表")}" data-confirm="${text("确认要删除该栏目表及所有子栏目表吗?")}" data-deltreenode="'+row.id+'"><i class="fa fa-trash-o"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/category/form?parentCode='+row.id+'&site.siteCode=${category.site.siteCode}" class="btnList" title="${text("新增下级栏目表")}"><i class="fa fa-plus-square"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/category/form?parentCode='+row.id+'&site.siteCode=${category.site.siteCode}" class="btnList" title="${text("新增下级栏目表")}"><i class="fa icon-plus"></i></a>&nbsp;');
//# if(hasPermi('cms:category:rebuildIndex')){
actions.push('<a href="${ctx}/cms/category/rebuildIndex?categoryCode='+row.categoryCode+'" class="btnList" title="${text("重建该栏目索引")}" data-confirm="${text("确认重建该栏目文章索引吗")}"><i class="fa fa-crosshairs"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/category/rebuildIndex?categoryCode='+row.categoryCode+'" class="btnList" title="${text("重建该栏目索引")}" data-confirm="${text("确认重建该栏目文章索引吗")}"><i class="fa fa-crosshairs"></i></a>&nbsp;');
//# }
//# if(hasPermi('cms:category:rebuildVectorStore')){
actions.push('<a href="${ctx}/cms/category/rebuildVectorStore?categoryCode='+row.categoryCode+'" class="btnList" title="${text("重建该栏目向量数据库")}" data-confirm="${text("确认重建该栏目文章向量数据库吗")}"><i class="fa fa-database"></i></a>&nbsp;');
//# }
if (row.status == Global.STATUS_NORMAL){
actions.push('<a href="${ctxFront}/list-'+row.categoryCode+'" target="_blank" title="${text("访问栏目")}"><i class="fa fa-globe"></i></a>&nbsp;');
@@ -109,10 +125,13 @@ $('#dataGrid').dataGrid({
],
treeGrid: true, // 启用树结构表格
defaultExpandLevel: 0, // 默认展开的层次
expandNodeClearPostData: 'categoryName,siteCode,moduleType,image,href,target,keywords,description,inMenu,inList,showModes,isNeedAudit,isCanComment,customListView,customContentView,viewConfig,status,remarks,', // 展开节点清理请求参数数据(一般设置查询条件的字段属性,否则在查询后,不能展开子节点数据)
expandNodeClearPostData: 'categoryCode,categoryName,siteCode,moduleType,image,href,target,keywords,description,inMenu,inList,showModes,isNeedAudit,isCanComment,customListView,customContentView,viewConfig,status,remarks,', // 展开节点清理请求参数数据(一般设置查询条件的字段属性,否则在查询后,不能展开子节点数据)
//# // 加载成功后执行事件
ajaxSuccess: function(data){
if ($('#categoryCode').val() != ''){
$('#categoryCode').val('');
$('#btnExpandTreeNode').click();
}
}
});
</script>

View File

@@ -8,7 +8,7 @@
<div class="box-tools pull-right">
<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
<% if(hasPermi('cms:comment:edit')){ %>
<a href="${ctx}/cms/comment/form" class="btn btn-default btnTool" title="${text('新增文章评论表')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<a href="${ctx}/cms/comment/form" class="btn btn-primary btnTool" title="${text('新增文章评论表')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<% } %>
</div>
</div>
@@ -130,12 +130,12 @@ $('#dataGrid').dataGrid({
{header:'${text('操作')}', name:'actions', width:120, formatter: function(val, obj, row, act){
var actions = [];
//# if(hasPermi('cms:comment:edit')){
actions.push('<a href="${ctx}/cms/comment/form?id='+row.id+'" class="btnList" title="${text('编辑文章评论表')}"><i class="fa fa-pencil"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/comment/form?id='+row.id+'" class="btnList" title="${text('编辑文章评论表')}"><i class="fa icon-note"></i></a>&nbsp;');
if (row.status == Global.STATUS_NORMAL){
actions.push('<a href="${ctx}/cms/comment/disable?id='+row.id+'" class="btnList" title="${text('停用文章评论表')}" data-confirm="${text('确认要停用该文章评论表吗')}"><i class="glyphicon glyphicon-ban-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/comment/disable?id='+row.id+'" class="btnList" title="${text('停用文章评论表')}" data-confirm="${text('确认要停用该文章评论表吗')}"><i class="fa icon-ban"></i></a>&nbsp;');
}
if (row.status == Global.STATUS_DISABLE){
actions.push('<a href="${ctx}/cms/comment/enable?id='+row.id+'" class="btnList" title="${text('启用文章评论表')}" data-confirm="${text('确认要启用该文章评论表吗')}"><i class="glyphicon glyphicon-ok-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/comment/enable?id='+row.id+'" class="btnList" title="${text('启用文章评论表')}" data-confirm="${text('确认要启用该文章评论表吗')}"><i class="fa icon-check"></i></a>&nbsp;');
}
actions.push('<a href="${ctx}/cms/comment/delete?id='+row.id+'" class="btnList" title="${text('删除文章评论表')}" data-confirm="${text('确认要删除该文章评论表吗')}"><i class="fa fa-trash-o"></i></a>&nbsp;');
//# }

View File

@@ -8,7 +8,7 @@
<div class="box-tools pull-right">
<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
<% if(hasPermi('cms:report:edit')){ %>
<a href="${ctx}/cms/report/form" class="btn btn-default btnTool" title="${text('新增内容举报表')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<a href="${ctx}/cms/report/form" class="btn btn-primary btnTool" title="${text('新增内容举报表')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<% } %>
</div>
</div>
@@ -71,12 +71,12 @@ $('#dataGrid').dataGrid({
{header:'${text('操作')}', name:'actions', width:120, formatter: function(val, obj, row, act){
var actions = [];
//# if(hasPermi('cms:report:edit')){
actions.push('<a href="${ctx}/cms/report/form?id='+row.id+'" class="btnList" title="${text('编辑内容举报表')}"><i class="fa fa-pencil"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/report/form?id='+row.id+'" class="btnList" title="${text('编辑内容举报表')}"><i class="fa icon-note"></i></a>&nbsp;');
if (row.status == Global.STATUS_NORMAL){
actions.push('<a href="${ctx}/cms/report/disable?id='+row.id+'" class="btnList" title="${text('停用内容举报表')}" data-confirm="${text('确认要停用该内容举报表吗')}"><i class="glyphicon glyphicon-ban-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/report/disable?id='+row.id+'" class="btnList" title="${text('停用内容举报表')}" data-confirm="${text('确认要停用该内容举报表吗')}"><i class="fa icon-ban"></i></a>&nbsp;');
}
if (row.status == Global.STATUS_DISABLE){
actions.push('<a href="${ctx}/cms/report/enable?id='+row.id+'" class="btnList" title="${text('启用内容举报表')}" data-confirm="${text('确认要启用该内容举报表吗')}"><i class="glyphicon glyphicon-ok-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/report/enable?id='+row.id+'" class="btnList" title="${text('启用内容举报表')}" data-confirm="${text('确认要启用该内容举报表吗')}"><i class="fa icon-check"></i></a>&nbsp;');
}
actions.push('<a href="${ctx}/cms/report/delete?id='+row.id+'" class="btnList" title="${text('删除内容举报表')}" data-confirm="${text('确认要删除该内容举报表吗')}"><i class="fa fa-trash-o"></i></a>&nbsp;');
//# }

View File

@@ -8,7 +8,7 @@
<div class="box-tools pull-right">
<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
<% if(hasPermi('cms:site:edit')){ %>
<a href="${ctx}/cms/site/form" class="btn btn-default btnTool" title="${text('新增站点')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<a href="${ctx}/cms/site/form" class="btn btn-primary btnTool" title="${text('新增站点')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<% } %>
</div>
</div>
@@ -67,20 +67,23 @@ $('#dataGrid').dataGrid({
return js.getDictLabel("#{@DictUtils.getDictListJson('sys_search_status')}", val, '${text("未知")}', true);
}},
{header:'${text("创建时间")}', name:'createDate', index:'a.create_date', width:150, align:"center"},
{header:'${text("操作")}', name:'actions', width:150, formatter: function(val, obj, row, act){
{header:'${text("操作")}', name:'actions', width:183, formatter: function(val, obj, row, act){
var actions = [];
//# if(hasPermi('cms:site:edit')){
actions.push('<a href="${ctx}/cms/site/form?siteCode='+row.siteCode+'" class="btnList" title="${text("编辑站点")}"><i class="fa fa-pencil"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/site/form?siteCode='+row.siteCode+'" class="btnList" title="${text("编辑站点")}"><i class="fa icon-note"></i></a>&nbsp;');
if (row.status == Global.STATUS_NORMAL){
actions.push('<a href="${ctx}/cms/site/disable?siteCode='+row.siteCode+'" class="btnList" title="${text("停用站点")}" data-confirm="${text("确认要停用该站点吗?")}"><i class="glyphicon glyphicon-ban-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/site/disable?siteCode='+row.siteCode+'" class="btnList" title="${text("停用站点")}" data-confirm="${text("确认要停用该站点吗?")}"><i class="fa icon-ban"></i></a>&nbsp;');
}
if (row.status == Global.STATUS_DISABLE){
actions.push('<a href="${ctx}/cms/site/enable?siteCode='+row.siteCode+'" class="btnList" title="${text("启用站点")}" data-confirm="${text("确认要启用该站点吗?")}"><i class="glyphicon glyphicon-ok-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/site/enable?siteCode='+row.siteCode+'" class="btnList" title="${text("启用站点")}" data-confirm="${text("确认要启用该站点吗?")}"><i class="fa icon-check"></i></a>&nbsp;');
}
actions.push('<a href="${ctx}/cms/site/delete?siteCode='+row.siteCode+'" class="btnList" title="${text("删除站点")}" data-confirm="${text("确认要删除该站点吗?")}"><i class="fa fa-trash-o"></i></a>&nbsp;');
//# if(hasPermi('cms:site:rebuildIndex')){
actions.push('<a href="${ctx}/cms/site/rebuildIndex?siteCode='+row.siteCode+'" class="btnList" title="${text("重建该站点索引")}" data-confirm="${text("确认重建该站点文章索引吗")}"><i class="fa fa-crosshairs"></i></a>&nbsp;');
//# }
//# if(hasPermi('cms:site:rebuildVectorStore')){
actions.push('<a href="${ctx}/cms/site/rebuildVectorStore?siteCode='+row.siteCode+'" class="btnList" title="${text("重建该站点向量数据库")}" data-confirm="${text("确认重建该站点文章向量数据库吗")}"><i class="fa fa-database"></i></a>&nbsp;');
//# }
if (row.status == Global.STATUS_NORMAL){
actions.push('<a href="${ctxFront}/index-'+row.siteCode+'" target="_blank" title="${text("访问站点")}"><i class="fa fa-globe"></i></a>&nbsp;');
}

View File

@@ -8,7 +8,7 @@
<div class="box-tools pull-right">
<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
<!-- <% if(hasPermi('cms:visitLog:edit')){ %>
<a href="${ctx}/cms/visitLog/form" class="btn btn-default btnTool" title="${text('新增访问日志表')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<a href="${ctx}/cms/visitLog/form" class="btn btn-primary btnTool" title="${text('新增访问日志表')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
<% } %> -->
</div>
</div>
@@ -92,12 +92,12 @@ $('#dataGrid').dataGrid({
{header:'${text('操作')}', name:'actions', width:120, formatter: function(val, obj, row, act){
var actions = [];
//# if(hasPermi('cms:visitLog:edit')){
actions.push('<a href="${ctx}/cms/visitLog/form?id='+row.id+'" class="btnList" title="${text('编辑访问日志表')}"><i class="fa fa-pencil"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/visitLog/form?id='+row.id+'" class="btnList" title="${text('编辑访问日志表')}"><i class="fa icon-note"></i></a>&nbsp;');
if (row.status == Global.STATUS_NORMAL){
actions.push('<a href="${ctx}/cms/visitLog/disable?id='+row.id+'" class="btnList" title="${text('停用访问日志表')}" data-confirm="${text('确认要停用该访问日志表吗')}"><i class="glyphicon glyphicon-ban-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/visitLog/disable?id='+row.id+'" class="btnList" title="${text('停用访问日志表')}" data-confirm="${text('确认要停用该访问日志表吗')}"><i class="fa icon-ban"></i></a>&nbsp;');
}
if (row.status == Global.STATUS_DISABLE){
actions.push('<a href="${ctx}/cms/visitLog/enable?id='+row.id+'" class="btnList" title="${text('启用访问日志表')}" data-confirm="${text('确认要启用该访问日志表吗')}"><i class="glyphicon glyphicon-ok-circle"></i></a>&nbsp;');
actions.push('<a href="${ctx}/cms/visitLog/enable?id='+row.id+'" class="btnList" title="${text('启用访问日志表')}" data-confirm="${text('确认要启用该访问日志表吗')}"><i class="fa icon-check"></i></a>&nbsp;');
}
actions.push('<a href="${ctx}/cms/visitLog/delete?id='+row.id+'" class="btnList" title="${text('删除访问日志表')}" data-confirm="${text('确认要删除该访问日志表吗')}"><i class="fa fa-trash-o"></i></a>&nbsp;');
//# }

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,31 @@
/* Create Tables */
-- 业务分类
CREATE TABLE js_biz_category
(
category_code varchar(64) NOT NULL,
view_code varchar(500),
category_name varchar(64) NOT NULL,
parent_code varchar(64) NOT NULL,
parent_codes varchar(767) NOT NULL,
tree_sort decimal(10) NOT NULL,
tree_sorts varchar(767) NOT NULL,
tree_leaf char(1) NOT NULL,
tree_level decimal(4) NOT NULL,
tree_names varchar(767) NOT NULL,
status char(1) DEFAULT '0' NOT NULL,
create_by varchar(64) NOT NULL,
create_date timestamp NOT NULL,
update_by varchar(64) NOT NULL,
update_date timestamp NOT NULL,
remarks vargraphic(500),
corp_code varchar(64) DEFAULT '0' NOT NULL,
corp_name vargraphic(100) DEFAULT 'JeeSite' NOT NULL,
PRIMARY KEY (category_code)
);
-- 代码生成表
CREATE TABLE js_gen_table
(
@@ -79,31 +104,6 @@ CREATE TABLE js_sys_area
);
-- 业务分类
CREATE TABLE js_biz_category
(
category_code varchar(64) NOT NULL,
view_code varchar(500),
category_name varchar(64) NOT NULL,
parent_code varchar(64) NOT NULL,
parent_codes varchar(767) NOT NULL,
tree_sort decimal(10) NOT NULL,
tree_sorts varchar(767) NOT NULL,
tree_leaf char(1) NOT NULL,
tree_level decimal(4) NOT NULL,
tree_names varchar(767) NOT NULL,
status char(1) DEFAULT '0' NOT NULL,
create_by varchar(64) NOT NULL,
create_date timestamp NOT NULL,
update_by varchar(64) NOT NULL,
update_date timestamp NOT NULL,
remarks vargraphic(500),
corp_code varchar(64) DEFAULT '0' NOT NULL,
corp_name vargraphic(100) DEFAULT 'JeeSite' NOT NULL,
PRIMARY KEY (category_code)
);
-- 公司表
CREATE TABLE js_sys_company
(
@@ -492,6 +492,21 @@ CREATE TABLE js_sys_menu
);
-- 菜单数据权限
CREATE TABLE js_sys_menu_data_scope
(
id varchar(64) NOT NULL,
role_code varchar(64) NOT NULL,
menu_code varchar(64) NOT NULL,
rule_name varchar(100),
rule_type char(1),
rule_config clob,
status char(1),
remarks vargraphic(500),
PRIMARY KEY (id)
);
-- 模块表
CREATE TABLE js_sys_module
(
@@ -767,7 +782,8 @@ CREATE TABLE js_sys_role_data_scope
ctrl_type varchar(20) NOT NULL,
ctrl_data varchar(64) NOT NULL,
ctrl_permi varchar(64) NOT NULL,
PRIMARY KEY (role_code, ctrl_type, ctrl_data, ctrl_permi)
menu_code varchar(64) DEFAULT '0' NOT NULL,
PRIMARY KEY (role_code, ctrl_type, ctrl_data, ctrl_permi, menu_code)
);
@@ -934,6 +950,8 @@ CREATE INDEX idx_sys_menu_sc ON js_sys_menu (sys_code);
CREATE INDEX idx_sys_menu_is ON js_sys_menu (is_show);
CREATE INDEX idx_sys_menu_mcs ON js_sys_menu (module_codes);
CREATE INDEX idx_sys_menu_wt ON js_sys_menu (weight);
CREATE INDEX idx_sys_menu_ds_mc ON js_sys_menu_data_scope (menu_code);
CREATE INDEX idx_sys_menu_ds_rc ON js_sys_menu_data_scope (role_code);
CREATE INDEX idx_sys_module_status ON js_sys_module (status);
CREATE INDEX idx_sys_msg_inner_cb ON js_sys_msg_inner (create_by);
CREATE INDEX idx_sys_msg_inner_status ON js_sys_msg_inner (status);

View File

@@ -2,6 +2,31 @@
/* Create Tables */
-- 业务分类
CREATE TABLE js_biz_category
(
category_code varchar(64) NOT NULL,
view_code varchar(500),
category_name varchar(64) NOT NULL,
parent_code varchar(64) NOT NULL,
parent_codes varchar(767) NOT NULL,
tree_sort decimal(10) NOT NULL,
tree_sorts varchar(767) NOT NULL,
tree_leaf char(1) NOT NULL,
tree_level decimal(4) NOT NULL,
tree_names varchar(767) NOT NULL,
status char(1) DEFAULT '0' NOT NULL,
create_by varchar(64) NOT NULL,
create_date datetime NOT NULL,
update_by varchar(64) NOT NULL,
update_date datetime NOT NULL,
remarks varchar(500),
corp_code varchar(64) DEFAULT '0' NOT NULL,
corp_name varchar(100) DEFAULT 'JeeSite' NOT NULL,
PRIMARY KEY (category_code)
);
-- 代码生成表
CREATE TABLE js_gen_table
(
@@ -79,31 +104,6 @@ CREATE TABLE js_sys_area
);
-- 业务分类
CREATE TABLE js_biz_category
(
category_code varchar(64) NOT NULL,
view_code varchar(500),
category_name varchar(64) NOT NULL,
parent_code varchar(64) NOT NULL,
parent_codes varchar(767) NOT NULL,
tree_sort decimal(10) NOT NULL,
tree_sorts varchar(767) NOT NULL,
tree_leaf char(1) NOT NULL,
tree_level decimal(4) NOT NULL,
tree_names varchar(767) NOT NULL,
status char(1) DEFAULT '0' NOT NULL,
create_by varchar(64) NOT NULL,
create_date datetime NOT NULL,
update_by varchar(64) NOT NULL,
update_date datetime NOT NULL,
remarks varchar(500),
corp_code varchar(64) DEFAULT '0' NOT NULL,
corp_name varchar(100) DEFAULT 'JeeSite' NOT NULL,
PRIMARY KEY (category_code)
);
-- 公司表
CREATE TABLE js_sys_company
(
@@ -492,6 +492,21 @@ CREATE TABLE js_sys_menu
);
-- 菜单数据权限
CREATE TABLE js_sys_menu_data_scope
(
id varchar(64) NOT NULL,
role_code varchar(64) NOT NULL,
menu_code varchar(64) NOT NULL,
rule_name varchar(100),
rule_type char(1),
rule_config clob,
status char(1),
remarks varchar(500),
PRIMARY KEY (id)
);
-- 模块表
CREATE TABLE js_sys_module
(
@@ -767,7 +782,8 @@ CREATE TABLE js_sys_role_data_scope
ctrl_type varchar(20) NOT NULL,
ctrl_data varchar(64) NOT NULL,
ctrl_permi varchar(64) NOT NULL,
PRIMARY KEY (role_code, ctrl_type, ctrl_data, ctrl_permi)
menu_code varchar(64) DEFAULT '0' NOT NULL,
PRIMARY KEY (role_code, ctrl_type, ctrl_data, ctrl_permi, menu_code)
);
@@ -934,6 +950,8 @@ CREATE INDEX idx_sys_menu_sc ON js_sys_menu (sys_code);
CREATE INDEX idx_sys_menu_is ON js_sys_menu (is_show);
CREATE INDEX idx_sys_menu_mcs ON js_sys_menu (module_codes);
CREATE INDEX idx_sys_menu_wt ON js_sys_menu (weight);
CREATE INDEX idx_sys_menu_ds_mc ON js_sys_menu_data_scope (menu_code);
CREATE INDEX idx_sys_menu_ds_rc ON js_sys_menu_data_scope (role_code);
CREATE INDEX idx_sys_module_status ON js_sys_module (status);
CREATE INDEX idx_sys_msg_inner_cb ON js_sys_msg_inner (create_by);
CREATE INDEX idx_sys_msg_inner_status ON js_sys_msg_inner (status);
@@ -999,6 +1017,25 @@ CREATE INDEX idx_sys_user_cc ON js_sys_user (corp_code);
/* Comments */
COMMENT ON TABLE js_biz_category IS '业务分类';
COMMENT ON COLUMN js_biz_category.category_code IS '流程分类';
COMMENT ON COLUMN js_biz_category.view_code IS '分类代码';
COMMENT ON COLUMN js_biz_category.category_name IS '分类名称';
COMMENT ON COLUMN js_biz_category.parent_code IS '父级编号';
COMMENT ON COLUMN js_biz_category.parent_codes IS '所有父级编号';
COMMENT ON COLUMN js_biz_category.tree_sort IS '排序号(升序)';
COMMENT ON COLUMN js_biz_category.tree_sorts IS '所有排序号';
COMMENT ON COLUMN js_biz_category.tree_leaf IS '是否最末级';
COMMENT ON COLUMN js_biz_category.tree_level IS '层次级别';
COMMENT ON COLUMN js_biz_category.tree_names IS '全节点名';
COMMENT ON COLUMN js_biz_category.status IS '状态0正常 1删除 2停用';
COMMENT ON COLUMN js_biz_category.create_by IS '创建者';
COMMENT ON COLUMN js_biz_category.create_date IS '创建时间';
COMMENT ON COLUMN js_biz_category.update_by IS '更新者';
COMMENT ON COLUMN js_biz_category.update_date IS '更新时间';
COMMENT ON COLUMN js_biz_category.remarks IS '备注信息';
COMMENT ON COLUMN js_biz_category.corp_code IS '租户代码';
COMMENT ON COLUMN js_biz_category.corp_name IS '租户名称';
COMMENT ON TABLE js_gen_table IS '代码生成表';
COMMENT ON COLUMN js_gen_table.table_name IS '表名';
COMMENT ON COLUMN js_gen_table.class_name IS '实体类名称';
@@ -1058,25 +1095,6 @@ COMMENT ON COLUMN js_sys_area.create_date IS '创建时间';
COMMENT ON COLUMN js_sys_area.update_by IS '更新者';
COMMENT ON COLUMN js_sys_area.update_date IS '更新时间';
COMMENT ON COLUMN js_sys_area.remarks IS '备注信息';
COMMENT ON TABLE js_biz_category IS '业务分类';
COMMENT ON COLUMN js_biz_category.category_code IS '流程分类';
COMMENT ON COLUMN js_biz_category.view_code IS '分类代码';
COMMENT ON COLUMN js_biz_category.category_name IS '分类名称';
COMMENT ON COLUMN js_biz_category.parent_code IS '父级编号';
COMMENT ON COLUMN js_biz_category.parent_codes IS '所有父级编号';
COMMENT ON COLUMN js_biz_category.tree_sort IS '排序号(升序)';
COMMENT ON COLUMN js_biz_category.tree_sorts IS '所有排序号';
COMMENT ON COLUMN js_biz_category.tree_leaf IS '是否最末级';
COMMENT ON COLUMN js_biz_category.tree_level IS '层次级别';
COMMENT ON COLUMN js_biz_category.tree_names IS '全节点名';
COMMENT ON COLUMN js_biz_category.status IS '状态0正常 1删除 2停用';
COMMENT ON COLUMN js_biz_category.create_by IS '创建者';
COMMENT ON COLUMN js_biz_category.create_date IS '创建时间';
COMMENT ON COLUMN js_biz_category.update_by IS '更新者';
COMMENT ON COLUMN js_biz_category.update_date IS '更新时间';
COMMENT ON COLUMN js_biz_category.remarks IS '备注信息';
COMMENT ON COLUMN js_biz_category.corp_code IS '租户代码';
COMMENT ON COLUMN js_biz_category.corp_name IS '租户名称';
COMMENT ON TABLE js_sys_company IS '公司表';
COMMENT ON COLUMN js_sys_company.company_code IS '公司编码';
COMMENT ON COLUMN js_sys_company.view_code IS '公司代码';
@@ -1375,6 +1393,15 @@ COMMENT ON COLUMN js_sys_menu.extend_d2 IS '扩展 Date 2';
COMMENT ON COLUMN js_sys_menu.extend_d3 IS '扩展 Date 3';
COMMENT ON COLUMN js_sys_menu.extend_d4 IS '扩展 Date 4';
COMMENT ON COLUMN js_sys_menu.extend_json IS '扩展 JSON';
COMMENT ON TABLE js_sys_menu_data_scope IS '菜单数据权限';
COMMENT ON COLUMN js_sys_menu_data_scope.id IS '编号';
COMMENT ON COLUMN js_sys_menu_data_scope.role_code IS '角色编码';
COMMENT ON COLUMN js_sys_menu_data_scope.menu_code IS '菜单编码';
COMMENT ON COLUMN js_sys_menu_data_scope.rule_name IS '规则名称';
COMMENT ON COLUMN js_sys_menu_data_scope.rule_type IS '规则类型1 角色数据范围 2自定义条件规则 3自定义SQL';
COMMENT ON COLUMN js_sys_menu_data_scope.rule_config IS '规则配置JSON';
COMMENT ON COLUMN js_sys_menu_data_scope.status IS '状态0正常 1删除 2停用';
COMMENT ON COLUMN js_sys_menu_data_scope.remarks IS '备注信息';
COMMENT ON TABLE js_sys_module IS '模块表';
COMMENT ON COLUMN js_sys_module.module_code IS '模块编码';
COMMENT ON COLUMN js_sys_module.module_name IS '模块名称';
@@ -1551,7 +1578,7 @@ COMMENT ON COLUMN js_sys_role.is_sys IS '系统内置1是 0否';
COMMENT ON COLUMN js_sys_role.is_show IS '是否显示';
COMMENT ON COLUMN js_sys_role.user_type IS '用户类型employee员工 member会员';
COMMENT ON COLUMN js_sys_role.desktop_url IS '桌面地址(仪表盘地址)';
COMMENT ON COLUMN js_sys_role.data_scope IS '数据范围设置0未设置 1全部数据 2自定义数据';
COMMENT ON COLUMN js_sys_role.data_scope IS '数据范围0未设置 1全部数据 2自定义数据';
COMMENT ON COLUMN js_sys_role.biz_scope IS '适应业务范围(不同的功能,不同的数据权限支持)';
COMMENT ON COLUMN js_sys_role.sys_codes IS '包含系统(多个用逗号隔开)';
COMMENT ON COLUMN js_sys_role.status IS '状态0正常 1删除 2停用';
@@ -1588,6 +1615,7 @@ COMMENT ON COLUMN js_sys_role_data_scope.role_code IS '控制角色编码';
COMMENT ON COLUMN js_sys_role_data_scope.ctrl_type IS '控制类型';
COMMENT ON COLUMN js_sys_role_data_scope.ctrl_data IS '控制数据';
COMMENT ON COLUMN js_sys_role_data_scope.ctrl_permi IS '控制权限';
COMMENT ON COLUMN js_sys_role_data_scope.menu_code IS '菜单编码';
COMMENT ON TABLE js_sys_role_menu IS '角色与菜单关联表';
COMMENT ON COLUMN js_sys_role_menu.role_code IS '角色编码';
COMMENT ON COLUMN js_sys_role_menu.menu_code IS '菜单编码';

View File

@@ -2,6 +2,31 @@
/* Create Tables */
-- 业务分类
CREATE TABLE [js_biz_category]
(
[category_code] varchar(64) NOT NULL,
[view_code] varchar(500),
[category_name] varchar(64) NOT NULL,
[parent_code] varchar(64) NOT NULL,
[parent_codes] varchar(767) NOT NULL,
[tree_sort] decimal(10) NOT NULL,
[tree_sorts] varchar(767) NOT NULL,
[tree_leaf] char(1) NOT NULL,
[tree_level] decimal(4) NOT NULL,
[tree_names] varchar(767) NOT NULL,
[status] char(1) DEFAULT '0' NOT NULL,
[create_by] varchar(64) NOT NULL,
[create_date] datetime NOT NULL,
[update_by] varchar(64) NOT NULL,
[update_date] datetime NOT NULL,
[remarks] nvarchar(500),
[corp_code] varchar(64) DEFAULT '0' NOT NULL,
[corp_name] nvarchar(100) DEFAULT 'JeeSite' NOT NULL,
PRIMARY KEY ([category_code])
);
-- 代码生成表
CREATE TABLE [js_gen_table]
(
@@ -79,31 +104,6 @@ CREATE TABLE [js_sys_area]
);
-- 业务分类
CREATE TABLE [js_biz_category]
(
[category_code] varchar(64) NOT NULL,
[view_code] varchar(500),
[category_name] varchar(64) NOT NULL,
[parent_code] varchar(64) NOT NULL,
[parent_codes] varchar(767) NOT NULL,
[tree_sort] decimal(10) NOT NULL,
[tree_sorts] varchar(767) NOT NULL,
[tree_leaf] char(1) NOT NULL,
[tree_level] decimal(4) NOT NULL,
[tree_names] varchar(767) NOT NULL,
[status] char(1) DEFAULT '0' NOT NULL,
[create_by] varchar(64) NOT NULL,
[create_date] datetime NOT NULL,
[update_by] varchar(64) NOT NULL,
[update_date] datetime NOT NULL,
[remarks] nvarchar(500),
[corp_code] varchar(64) DEFAULT '0' NOT NULL,
[corp_name] nvarchar(100) DEFAULT 'JeeSite' NOT NULL,
PRIMARY KEY ([category_code])
);
-- 公司表
CREATE TABLE [js_sys_company]
(
@@ -492,6 +492,21 @@ CREATE TABLE [js_sys_menu]
);
-- 菜单数据权限
CREATE TABLE [js_sys_menu_data_scope]
(
[id] varchar(64) NOT NULL,
[role_code] varchar(64) NOT NULL,
[menu_code] varchar(64) NOT NULL,
[rule_name] varchar(100),
[rule_type] char(1),
[rule_config] text,
[status] char(1),
[remarks] nvarchar(500),
PRIMARY KEY ([id])
);
-- 模块表
CREATE TABLE [js_sys_module]
(
@@ -767,7 +782,8 @@ CREATE TABLE [js_sys_role_data_scope]
[ctrl_type] varchar(20) NOT NULL,
[ctrl_data] varchar(64) NOT NULL,
[ctrl_permi] varchar(64) NOT NULL,
PRIMARY KEY ([role_code], [ctrl_type], [ctrl_data], [ctrl_permi])
[menu_code] varchar(64) DEFAULT '0' NOT NULL,
PRIMARY KEY ([role_code], [ctrl_type], [ctrl_data], [ctrl_permi], [menu_code])
);
@@ -934,6 +950,8 @@ CREATE INDEX [idx_sys_menu_sc] ON [js_sys_menu] ([sys_code]);
CREATE INDEX [idx_sys_menu_is] ON [js_sys_menu] ([is_show]);
CREATE INDEX [idx_sys_menu_mcs] ON [js_sys_menu] ([module_codes]);
CREATE INDEX [idx_sys_menu_wt] ON [js_sys_menu] ([weight]);
CREATE INDEX [idx_sys_menu_ds_mc] ON [js_sys_menu_data_scope] ([menu_code]);
CREATE INDEX [idx_sys_menu_ds_rc] ON [js_sys_menu_data_scope] ([role_code]);
CREATE INDEX [idx_sys_module_status] ON [js_sys_module] ([status]);
CREATE INDEX [idx_sys_msg_inner_cb] ON [js_sys_msg_inner] ([create_by]);
CREATE INDEX [idx_sys_msg_inner_status] ON [js_sys_msg_inner] ([status]);

View File

@@ -3,6 +3,31 @@ SET SESSION FOREIGN_KEY_CHECKS=0;
/* Create Tables */
-- 业务分类
CREATE TABLE js_biz_category
(
category_code varchar(64) NOT NULL COMMENT '流程分类',
view_code varchar(500) COMMENT '分类代码',
category_name varchar(64) NOT NULL COMMENT '分类名称',
parent_code varchar(64) NOT NULL COMMENT '父级编号',
parent_codes varchar(767) NOT NULL COMMENT '所有父级编号',
tree_sort decimal(10) NOT NULL COMMENT '排序号(升序)',
tree_sorts varchar(767) NOT NULL COMMENT '所有排序号',
tree_leaf char(1) NOT NULL COMMENT '是否最末级',
tree_level decimal(4) NOT NULL COMMENT '层次级别',
tree_names varchar(767) NOT NULL COMMENT '全节点名',
status char(1) DEFAULT '0' NOT NULL COMMENT '状态0正常 1删除 2停用',
create_by varchar(64) NOT NULL COMMENT '创建者',
create_date datetime NOT NULL COMMENT '创建时间',
update_by varchar(64) NOT NULL COMMENT '更新者',
update_date datetime NOT NULL COMMENT '更新时间',
remarks varchar(500) COMMENT '备注信息',
corp_code varchar(64) DEFAULT '0' NOT NULL COMMENT '租户代码',
corp_name varchar(100) DEFAULT 'JeeSite' NOT NULL COMMENT '租户名称',
PRIMARY KEY (category_code)
) COMMENT = '业务分类';
-- 代码生成表
CREATE TABLE js_gen_table
(
@@ -80,31 +105,6 @@ CREATE TABLE js_sys_area
) COMMENT = '行政区划';
-- 业务分类
CREATE TABLE js_biz_category
(
category_code varchar(64) NOT NULL COMMENT '流程分类',
view_code varchar(500) COMMENT '分类代码',
category_name varchar(64) NOT NULL COMMENT '分类名称',
parent_code varchar(64) NOT NULL COMMENT '父级编号',
parent_codes varchar(767) NOT NULL COMMENT '所有父级编号',
tree_sort decimal(10) NOT NULL COMMENT '排序号(升序)',
tree_sorts varchar(767) NOT NULL COMMENT '所有排序号',
tree_leaf char(1) NOT NULL COMMENT '是否最末级',
tree_level decimal(4) NOT NULL COMMENT '层次级别',
tree_names varchar(767) NOT NULL COMMENT '全节点名',
status char(1) DEFAULT '0' NOT NULL COMMENT '状态0正常 1删除 2停用',
create_by varchar(64) NOT NULL COMMENT '创建者',
create_date datetime NOT NULL COMMENT '创建时间',
update_by varchar(64) NOT NULL COMMENT '更新者',
update_date datetime NOT NULL COMMENT '更新时间',
remarks varchar(500) COMMENT '备注信息',
corp_code varchar(64) DEFAULT '0' NOT NULL COMMENT '租户代码',
corp_name varchar(100) DEFAULT 'JeeSite' NOT NULL COMMENT '租户名称',
PRIMARY KEY (category_code)
) COMMENT = '业务分类';
-- 公司表
CREATE TABLE js_sys_company
(
@@ -493,6 +493,21 @@ CREATE TABLE js_sys_menu
) COMMENT = '菜单表';
-- 菜单数据权限
CREATE TABLE js_sys_menu_data_scope
(
id varchar(64) NOT NULL COMMENT '编号',
role_code varchar(64) NOT NULL COMMENT '角色编码',
menu_code varchar(64) NOT NULL COMMENT '菜单编码',
rule_name varchar(100) COMMENT '规则名称',
rule_type char(1) COMMENT '规则类型1 角色数据范围 2自定义条件规则 3自定义SQL',
rule_config text COMMENT '规则配置JSON',
status char(1) COMMENT '状态0正常 1删除 2停用',
remarks varchar(500) COMMENT '备注信息',
PRIMARY KEY (id)
) COMMENT = '菜单数据权限';
-- 模块表
CREATE TABLE js_sys_module
(
@@ -725,7 +740,7 @@ CREATE TABLE js_sys_role
is_show char(1) DEFAULT '1' COMMENT '是否显示',
user_type varchar(16) COMMENT '用户类型employee员工 member会员',
desktop_url varchar(255) COMMENT '桌面地址(仪表盘地址)',
data_scope char(1) COMMENT '数据范围设置0未设置 1全部数据 2自定义数据',
data_scope char(1) COMMENT '数据范围0未设置 1全部数据 2自定义数据',
biz_scope varchar(255) COMMENT '适应业务范围(不同的功能,不同的数据权限支持)',
sys_codes varchar(500) COMMENT '包含系统(多个用逗号隔开)',
status char(1) DEFAULT '0' NOT NULL COMMENT '状态0正常 1删除 2停用',
@@ -768,7 +783,8 @@ CREATE TABLE js_sys_role_data_scope
ctrl_type varchar(20) NOT NULL COMMENT '控制类型',
ctrl_data varchar(64) NOT NULL COMMENT '控制数据',
ctrl_permi varchar(64) NOT NULL COMMENT '控制权限',
PRIMARY KEY (role_code, ctrl_type, ctrl_data, ctrl_permi)
menu_code varchar(64) DEFAULT '0' NOT NULL COMMENT '菜单编码',
PRIMARY KEY (role_code, ctrl_type, ctrl_data, ctrl_permi, menu_code)
) COMMENT = '角色数据权限表';
@@ -935,6 +951,8 @@ CREATE INDEX idx_sys_menu_sc ON js_sys_menu (sys_code ASC);
CREATE INDEX idx_sys_menu_is ON js_sys_menu (is_show ASC);
CREATE INDEX idx_sys_menu_mcs ON js_sys_menu (module_codes ASC);
CREATE INDEX idx_sys_menu_wt ON js_sys_menu (weight ASC);
CREATE INDEX idx_sys_menu_ds_mc ON js_sys_menu_data_scope (menu_code ASC);
CREATE INDEX idx_sys_menu_ds_rc ON js_sys_menu_data_scope (role_code ASC);
CREATE INDEX idx_sys_module_status ON js_sys_module (status ASC);
CREATE INDEX idx_sys_msg_inner_cb ON js_sys_msg_inner (create_by ASC);
CREATE INDEX idx_sys_msg_inner_status ON js_sys_msg_inner (status ASC);

View File

@@ -2,6 +2,31 @@
/* Create Tables */
-- 业务分类
CREATE TABLE js_biz_category
(
category_code varchar2(64) NOT NULL,
view_code varchar2(500),
category_name varchar2(64) NOT NULL,
parent_code varchar2(64) NOT NULL,
parent_codes varchar2(767) NOT NULL,
tree_sort number(10) NOT NULL,
tree_sorts varchar2(767) NOT NULL,
tree_leaf char(1) NOT NULL,
tree_level number(4) NOT NULL,
tree_names varchar2(767) NOT NULL,
status char(1) DEFAULT '0' NOT NULL,
create_by varchar2(64) NOT NULL,
create_date timestamp NOT NULL,
update_by varchar2(64) NOT NULL,
update_date timestamp NOT NULL,
remarks nvarchar2(500),
corp_code varchar2(64) DEFAULT '0' NOT NULL,
corp_name nvarchar2(100) DEFAULT 'JeeSite' NOT NULL,
PRIMARY KEY (category_code)
);
-- 代码生成表
CREATE TABLE js_gen_table
(
@@ -79,31 +104,6 @@ CREATE TABLE js_sys_area
);
-- 业务分类
CREATE TABLE js_biz_category
(
category_code varchar2(64) NOT NULL,
view_code varchar2(500),
category_name varchar2(64) NOT NULL,
parent_code varchar2(64) NOT NULL,
parent_codes varchar2(767) NOT NULL,
tree_sort number(10) NOT NULL,
tree_sorts varchar2(767) NOT NULL,
tree_leaf char(1) NOT NULL,
tree_level number(4) NOT NULL,
tree_names varchar2(767) NOT NULL,
status char(1) DEFAULT '0' NOT NULL,
create_by varchar2(64) NOT NULL,
create_date timestamp NOT NULL,
update_by varchar2(64) NOT NULL,
update_date timestamp NOT NULL,
remarks nvarchar2(500),
corp_code varchar2(64) DEFAULT '0' NOT NULL,
corp_name nvarchar2(100) DEFAULT 'JeeSite' NOT NULL,
PRIMARY KEY (category_code)
);
-- 公司表
CREATE TABLE js_sys_company
(
@@ -492,6 +492,21 @@ CREATE TABLE js_sys_menu
);
-- 菜单数据权限
CREATE TABLE js_sys_menu_data_scope
(
id varchar2(64) NOT NULL,
role_code varchar2(64) NOT NULL,
menu_code varchar2(64) NOT NULL,
rule_name varchar2(100),
rule_type char(1),
rule_config clob,
status char(1),
remarks nvarchar2(500),
PRIMARY KEY (id)
);
-- 模块表
CREATE TABLE js_sys_module
(
@@ -767,7 +782,8 @@ CREATE TABLE js_sys_role_data_scope
ctrl_type varchar2(20) NOT NULL,
ctrl_data varchar2(64) NOT NULL,
ctrl_permi varchar2(64) NOT NULL,
PRIMARY KEY (role_code, ctrl_type, ctrl_data, ctrl_permi)
menu_code varchar2(64) DEFAULT '0' NOT NULL,
PRIMARY KEY (role_code, ctrl_type, ctrl_data, ctrl_permi, menu_code)
);
@@ -934,6 +950,8 @@ CREATE INDEX idx_sys_menu_sc ON js_sys_menu (sys_code);
CREATE INDEX idx_sys_menu_is ON js_sys_menu (is_show);
CREATE INDEX idx_sys_menu_mcs ON js_sys_menu (module_codes);
CREATE INDEX idx_sys_menu_wt ON js_sys_menu (weight);
CREATE INDEX idx_sys_menu_ds_mc ON js_sys_menu_data_scope (menu_code);
CREATE INDEX idx_sys_menu_ds_rc ON js_sys_menu_data_scope (role_code);
CREATE INDEX idx_sys_module_status ON js_sys_module (status);
CREATE INDEX idx_sys_msg_inner_cb ON js_sys_msg_inner (create_by);
CREATE INDEX idx_sys_msg_inner_status ON js_sys_msg_inner (status);
@@ -999,6 +1017,25 @@ CREATE INDEX idx_sys_user_cc ON js_sys_user (corp_code);
/* Comments */
COMMENT ON TABLE js_biz_category IS '业务分类';
COMMENT ON COLUMN js_biz_category.category_code IS '流程分类';
COMMENT ON COLUMN js_biz_category.view_code IS '分类代码';
COMMENT ON COLUMN js_biz_category.category_name IS '分类名称';
COMMENT ON COLUMN js_biz_category.parent_code IS '父级编号';
COMMENT ON COLUMN js_biz_category.parent_codes IS '所有父级编号';
COMMENT ON COLUMN js_biz_category.tree_sort IS '排序号(升序)';
COMMENT ON COLUMN js_biz_category.tree_sorts IS '所有排序号';
COMMENT ON COLUMN js_biz_category.tree_leaf IS '是否最末级';
COMMENT ON COLUMN js_biz_category.tree_level IS '层次级别';
COMMENT ON COLUMN js_biz_category.tree_names IS '全节点名';
COMMENT ON COLUMN js_biz_category.status IS '状态0正常 1删除 2停用';
COMMENT ON COLUMN js_biz_category.create_by IS '创建者';
COMMENT ON COLUMN js_biz_category.create_date IS '创建时间';
COMMENT ON COLUMN js_biz_category.update_by IS '更新者';
COMMENT ON COLUMN js_biz_category.update_date IS '更新时间';
COMMENT ON COLUMN js_biz_category.remarks IS '备注信息';
COMMENT ON COLUMN js_biz_category.corp_code IS '租户代码';
COMMENT ON COLUMN js_biz_category.corp_name IS '租户名称';
COMMENT ON TABLE js_gen_table IS '代码生成表';
COMMENT ON COLUMN js_gen_table.table_name IS '表名';
COMMENT ON COLUMN js_gen_table.class_name IS '实体类名称';
@@ -1058,25 +1095,6 @@ COMMENT ON COLUMN js_sys_area.create_date IS '创建时间';
COMMENT ON COLUMN js_sys_area.update_by IS '更新者';
COMMENT ON COLUMN js_sys_area.update_date IS '更新时间';
COMMENT ON COLUMN js_sys_area.remarks IS '备注信息';
COMMENT ON TABLE js_biz_category IS '业务分类';
COMMENT ON COLUMN js_biz_category.category_code IS '流程分类';
COMMENT ON COLUMN js_biz_category.view_code IS '分类代码';
COMMENT ON COLUMN js_biz_category.category_name IS '分类名称';
COMMENT ON COLUMN js_biz_category.parent_code IS '父级编号';
COMMENT ON COLUMN js_biz_category.parent_codes IS '所有父级编号';
COMMENT ON COLUMN js_biz_category.tree_sort IS '排序号(升序)';
COMMENT ON COLUMN js_biz_category.tree_sorts IS '所有排序号';
COMMENT ON COLUMN js_biz_category.tree_leaf IS '是否最末级';
COMMENT ON COLUMN js_biz_category.tree_level IS '层次级别';
COMMENT ON COLUMN js_biz_category.tree_names IS '全节点名';
COMMENT ON COLUMN js_biz_category.status IS '状态0正常 1删除 2停用';
COMMENT ON COLUMN js_biz_category.create_by IS '创建者';
COMMENT ON COLUMN js_biz_category.create_date IS '创建时间';
COMMENT ON COLUMN js_biz_category.update_by IS '更新者';
COMMENT ON COLUMN js_biz_category.update_date IS '更新时间';
COMMENT ON COLUMN js_biz_category.remarks IS '备注信息';
COMMENT ON COLUMN js_biz_category.corp_code IS '租户代码';
COMMENT ON COLUMN js_biz_category.corp_name IS '租户名称';
COMMENT ON TABLE js_sys_company IS '公司表';
COMMENT ON COLUMN js_sys_company.company_code IS '公司编码';
COMMENT ON COLUMN js_sys_company.view_code IS '公司代码';
@@ -1375,6 +1393,15 @@ COMMENT ON COLUMN js_sys_menu.extend_d2 IS '扩展 Date 2';
COMMENT ON COLUMN js_sys_menu.extend_d3 IS '扩展 Date 3';
COMMENT ON COLUMN js_sys_menu.extend_d4 IS '扩展 Date 4';
COMMENT ON COLUMN js_sys_menu.extend_json IS '扩展 JSON';
COMMENT ON TABLE js_sys_menu_data_scope IS '菜单数据权限';
COMMENT ON COLUMN js_sys_menu_data_scope.id IS '编号';
COMMENT ON COLUMN js_sys_menu_data_scope.role_code IS '角色编码';
COMMENT ON COLUMN js_sys_menu_data_scope.menu_code IS '菜单编码';
COMMENT ON COLUMN js_sys_menu_data_scope.rule_name IS '规则名称';
COMMENT ON COLUMN js_sys_menu_data_scope.rule_type IS '规则类型1 角色数据范围 2自定义条件规则 3自定义SQL';
COMMENT ON COLUMN js_sys_menu_data_scope.rule_config IS '规则配置JSON';
COMMENT ON COLUMN js_sys_menu_data_scope.status IS '状态0正常 1删除 2停用';
COMMENT ON COLUMN js_sys_menu_data_scope.remarks IS '备注信息';
COMMENT ON TABLE js_sys_module IS '模块表';
COMMENT ON COLUMN js_sys_module.module_code IS '模块编码';
COMMENT ON COLUMN js_sys_module.module_name IS '模块名称';
@@ -1551,7 +1578,7 @@ COMMENT ON COLUMN js_sys_role.is_sys IS '系统内置1是 0否';
COMMENT ON COLUMN js_sys_role.is_show IS '是否显示';
COMMENT ON COLUMN js_sys_role.user_type IS '用户类型employee员工 member会员';
COMMENT ON COLUMN js_sys_role.desktop_url IS '桌面地址(仪表盘地址)';
COMMENT ON COLUMN js_sys_role.data_scope IS '数据范围设置0未设置 1全部数据 2自定义数据';
COMMENT ON COLUMN js_sys_role.data_scope IS '数据范围0未设置 1全部数据 2自定义数据';
COMMENT ON COLUMN js_sys_role.biz_scope IS '适应业务范围(不同的功能,不同的数据权限支持)';
COMMENT ON COLUMN js_sys_role.sys_codes IS '包含系统(多个用逗号隔开)';
COMMENT ON COLUMN js_sys_role.status IS '状态0正常 1删除 2停用';
@@ -1588,6 +1615,7 @@ COMMENT ON COLUMN js_sys_role_data_scope.role_code IS '控制角色编码';
COMMENT ON COLUMN js_sys_role_data_scope.ctrl_type IS '控制类型';
COMMENT ON COLUMN js_sys_role_data_scope.ctrl_data IS '控制数据';
COMMENT ON COLUMN js_sys_role_data_scope.ctrl_permi IS '控制权限';
COMMENT ON COLUMN js_sys_role_data_scope.menu_code IS '菜单编码';
COMMENT ON TABLE js_sys_role_menu IS '角色与菜单关联表';
COMMENT ON COLUMN js_sys_role_menu.role_code IS '角色编码';
COMMENT ON COLUMN js_sys_role_menu.menu_code IS '菜单编码';

View File

@@ -2,6 +2,31 @@
/* Create Tables */
-- 业务分类
CREATE TABLE js_biz_category
(
category_code varchar(64) NOT NULL,
view_code varchar(500),
category_name varchar(64) NOT NULL,
parent_code varchar(64) NOT NULL,
parent_codes varchar(767) NOT NULL,
tree_sort decimal(10) NOT NULL,
tree_sorts varchar(767) NOT NULL,
tree_leaf char(1) NOT NULL,
tree_level decimal(4) NOT NULL,
tree_names varchar(767) NOT NULL,
status char(1) DEFAULT '0' NOT NULL,
create_by varchar(64) NOT NULL,
create_date timestamp NOT NULL,
update_by varchar(64) NOT NULL,
update_date timestamp NOT NULL,
remarks varchar(500),
corp_code varchar(64) DEFAULT '0' NOT NULL,
corp_name varchar(100) DEFAULT 'JeeSite' NOT NULL,
PRIMARY KEY (category_code)
) WITHOUT OIDS;
-- 代码生成表
CREATE TABLE js_gen_table
(
@@ -79,31 +104,6 @@ CREATE TABLE js_sys_area
) WITHOUT OIDS;
-- 业务分类
CREATE TABLE js_biz_category
(
category_code varchar(64) NOT NULL,
view_code varchar(500),
category_name varchar(64) NOT NULL,
parent_code varchar(64) NOT NULL,
parent_codes varchar(767) NOT NULL,
tree_sort decimal(10) NOT NULL,
tree_sorts varchar(767) NOT NULL,
tree_leaf char(1) NOT NULL,
tree_level decimal(4) NOT NULL,
tree_names varchar(767) NOT NULL,
status char(1) DEFAULT '0' NOT NULL,
create_by varchar(64) NOT NULL,
create_date timestamp NOT NULL,
update_by varchar(64) NOT NULL,
update_date timestamp NOT NULL,
remarks varchar(500),
corp_code varchar(64) DEFAULT '0' NOT NULL,
corp_name varchar(100) DEFAULT 'JeeSite' NOT NULL,
PRIMARY KEY (category_code)
) WITHOUT OIDS;
-- 公司表
CREATE TABLE js_sys_company
(
@@ -492,6 +492,21 @@ CREATE TABLE js_sys_menu
) WITHOUT OIDS;
-- 菜单数据权限
CREATE TABLE js_sys_menu_data_scope
(
id varchar(64) NOT NULL,
role_code varchar(64) NOT NULL,
menu_code varchar(64) NOT NULL,
rule_name varchar(100),
rule_type char(1),
rule_config text,
status char(1),
remarks varchar(500),
PRIMARY KEY (id)
) WITHOUT OIDS;
-- 模块表
CREATE TABLE js_sys_module
(
@@ -767,7 +782,8 @@ CREATE TABLE js_sys_role_data_scope
ctrl_type varchar(20) NOT NULL,
ctrl_data varchar(64) NOT NULL,
ctrl_permi varchar(64) NOT NULL,
PRIMARY KEY (role_code, ctrl_type, ctrl_data, ctrl_permi)
menu_code varchar(64) DEFAULT '0' NOT NULL,
PRIMARY KEY (role_code, ctrl_type, ctrl_data, ctrl_permi, menu_code)
) WITHOUT OIDS;
@@ -934,6 +950,8 @@ CREATE INDEX idx_sys_menu_sc ON js_sys_menu (sys_code);
CREATE INDEX idx_sys_menu_is ON js_sys_menu (is_show);
CREATE INDEX idx_sys_menu_mcs ON js_sys_menu (module_codes);
CREATE INDEX idx_sys_menu_wt ON js_sys_menu (weight);
CREATE INDEX idx_sys_menu_ds_mc ON js_sys_menu_data_scope (menu_code);
CREATE INDEX idx_sys_menu_ds_rc ON js_sys_menu_data_scope (role_code);
CREATE INDEX idx_sys_module_status ON js_sys_module (status);
CREATE INDEX idx_sys_msg_inner_cb ON js_sys_msg_inner (create_by);
CREATE INDEX idx_sys_msg_inner_status ON js_sys_msg_inner (status);
@@ -999,6 +1017,25 @@ CREATE INDEX idx_sys_user_cc ON js_sys_user (corp_code);
/* Comments */
COMMENT ON TABLE js_biz_category IS '业务分类';
COMMENT ON COLUMN js_biz_category.category_code IS '流程分类';
COMMENT ON COLUMN js_biz_category.view_code IS '分类代码';
COMMENT ON COLUMN js_biz_category.category_name IS '分类名称';
COMMENT ON COLUMN js_biz_category.parent_code IS '父级编号';
COMMENT ON COLUMN js_biz_category.parent_codes IS '所有父级编号';
COMMENT ON COLUMN js_biz_category.tree_sort IS '排序号(升序)';
COMMENT ON COLUMN js_biz_category.tree_sorts IS '所有排序号';
COMMENT ON COLUMN js_biz_category.tree_leaf IS '是否最末级';
COMMENT ON COLUMN js_biz_category.tree_level IS '层次级别';
COMMENT ON COLUMN js_biz_category.tree_names IS '全节点名';
COMMENT ON COLUMN js_biz_category.status IS '状态0正常 1删除 2停用';
COMMENT ON COLUMN js_biz_category.create_by IS '创建者';
COMMENT ON COLUMN js_biz_category.create_date IS '创建时间';
COMMENT ON COLUMN js_biz_category.update_by IS '更新者';
COMMENT ON COLUMN js_biz_category.update_date IS '更新时间';
COMMENT ON COLUMN js_biz_category.remarks IS '备注信息';
COMMENT ON COLUMN js_biz_category.corp_code IS '租户代码';
COMMENT ON COLUMN js_biz_category.corp_name IS '租户名称';
COMMENT ON TABLE js_gen_table IS '代码生成表';
COMMENT ON COLUMN js_gen_table.table_name IS '表名';
COMMENT ON COLUMN js_gen_table.class_name IS '实体类名称';
@@ -1058,25 +1095,6 @@ COMMENT ON COLUMN js_sys_area.create_date IS '创建时间';
COMMENT ON COLUMN js_sys_area.update_by IS '更新者';
COMMENT ON COLUMN js_sys_area.update_date IS '更新时间';
COMMENT ON COLUMN js_sys_area.remarks IS '备注信息';
COMMENT ON TABLE js_biz_category IS '业务分类';
COMMENT ON COLUMN js_biz_category.category_code IS '流程分类';
COMMENT ON COLUMN js_biz_category.view_code IS '分类代码';
COMMENT ON COLUMN js_biz_category.category_name IS '分类名称';
COMMENT ON COLUMN js_biz_category.parent_code IS '父级编号';
COMMENT ON COLUMN js_biz_category.parent_codes IS '所有父级编号';
COMMENT ON COLUMN js_biz_category.tree_sort IS '排序号(升序)';
COMMENT ON COLUMN js_biz_category.tree_sorts IS '所有排序号';
COMMENT ON COLUMN js_biz_category.tree_leaf IS '是否最末级';
COMMENT ON COLUMN js_biz_category.tree_level IS '层次级别';
COMMENT ON COLUMN js_biz_category.tree_names IS '全节点名';
COMMENT ON COLUMN js_biz_category.status IS '状态0正常 1删除 2停用';
COMMENT ON COLUMN js_biz_category.create_by IS '创建者';
COMMENT ON COLUMN js_biz_category.create_date IS '创建时间';
COMMENT ON COLUMN js_biz_category.update_by IS '更新者';
COMMENT ON COLUMN js_biz_category.update_date IS '更新时间';
COMMENT ON COLUMN js_biz_category.remarks IS '备注信息';
COMMENT ON COLUMN js_biz_category.corp_code IS '租户代码';
COMMENT ON COLUMN js_biz_category.corp_name IS '租户名称';
COMMENT ON TABLE js_sys_company IS '公司表';
COMMENT ON COLUMN js_sys_company.company_code IS '公司编码';
COMMENT ON COLUMN js_sys_company.view_code IS '公司代码';
@@ -1375,6 +1393,15 @@ COMMENT ON COLUMN js_sys_menu.extend_d2 IS '扩展 Date 2';
COMMENT ON COLUMN js_sys_menu.extend_d3 IS '扩展 Date 3';
COMMENT ON COLUMN js_sys_menu.extend_d4 IS '扩展 Date 4';
COMMENT ON COLUMN js_sys_menu.extend_json IS '扩展 JSON';
COMMENT ON TABLE js_sys_menu_data_scope IS '菜单数据权限';
COMMENT ON COLUMN js_sys_menu_data_scope.id IS '编号';
COMMENT ON COLUMN js_sys_menu_data_scope.role_code IS '角色编码';
COMMENT ON COLUMN js_sys_menu_data_scope.menu_code IS '菜单编码';
COMMENT ON COLUMN js_sys_menu_data_scope.rule_name IS '规则名称';
COMMENT ON COLUMN js_sys_menu_data_scope.rule_type IS '规则类型1 角色数据范围 2自定义条件规则 3自定义SQL';
COMMENT ON COLUMN js_sys_menu_data_scope.rule_config IS '规则配置JSON';
COMMENT ON COLUMN js_sys_menu_data_scope.status IS '状态0正常 1删除 2停用';
COMMENT ON COLUMN js_sys_menu_data_scope.remarks IS '备注信息';
COMMENT ON TABLE js_sys_module IS '模块表';
COMMENT ON COLUMN js_sys_module.module_code IS '模块编码';
COMMENT ON COLUMN js_sys_module.module_name IS '模块名称';
@@ -1551,7 +1578,7 @@ COMMENT ON COLUMN js_sys_role.is_sys IS '系统内置1是 0否';
COMMENT ON COLUMN js_sys_role.is_show IS '是否显示';
COMMENT ON COLUMN js_sys_role.user_type IS '用户类型employee员工 member会员';
COMMENT ON COLUMN js_sys_role.desktop_url IS '桌面地址(仪表盘地址)';
COMMENT ON COLUMN js_sys_role.data_scope IS '数据范围设置0未设置 1全部数据 2自定义数据';
COMMENT ON COLUMN js_sys_role.data_scope IS '数据范围0未设置 1全部数据 2自定义数据';
COMMENT ON COLUMN js_sys_role.biz_scope IS '适应业务范围(不同的功能,不同的数据权限支持)';
COMMENT ON COLUMN js_sys_role.sys_codes IS '包含系统(多个用逗号隔开)';
COMMENT ON COLUMN js_sys_role.status IS '状态0正常 1删除 2停用';
@@ -1588,6 +1615,7 @@ COMMENT ON COLUMN js_sys_role_data_scope.role_code IS '控制角色编码';
COMMENT ON COLUMN js_sys_role_data_scope.ctrl_type IS '控制类型';
COMMENT ON COLUMN js_sys_role_data_scope.ctrl_data IS '控制数据';
COMMENT ON COLUMN js_sys_role_data_scope.ctrl_permi IS '控制权限';
COMMENT ON COLUMN js_sys_role_data_scope.menu_code IS '菜单编码';
COMMENT ON TABLE js_sys_role_menu IS '角色与菜单关联表';
COMMENT ON COLUMN js_sys_role_menu.role_code IS '角色编码';
COMMENT ON COLUMN js_sys_role_menu.menu_code IS '菜单编码';

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-parent</artifactId>
<version>5.9.1.springboot3-SNAPSHOT</version>
<version>5.11.1.springboot3-SNAPSHOT</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>

View File

@@ -8,6 +8,7 @@ import com.jeesite.common.codec.DesUtils;
import com.jeesite.common.codec.EncodeUtils;
import com.jeesite.common.collect.ListUtils;
import com.jeesite.common.collect.MapUtils;
import com.jeesite.common.collect.SetUtils;
import com.jeesite.common.config.Global;
import com.jeesite.common.lang.ObjectUtils;
import com.jeesite.common.lang.StringUtils;
@@ -17,13 +18,12 @@ import com.jeesite.common.shiro.realm.BaseAuthorizingRealm;
import com.jeesite.common.utils.SpringUtils;
import com.jeesite.common.web.CookieUtils;
import com.jeesite.common.web.http.ServletUtils;
import com.jeesite.modules.sys.entity.Log;
import com.jeesite.modules.sys.entity.Role;
import com.jeesite.modules.sys.entity.User;
import com.jeesite.modules.sys.utils.CorpUtils;
import com.jeesite.modules.sys.utils.LogUtils;
import com.jeesite.modules.sys.utils.UserUtils;
import com.jeesite.modules.sys.utils.ValidCodeUtils;
import com.jeesite.modules.sys.entity.*;
import com.jeesite.modules.sys.utils.*;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
@@ -37,14 +37,11 @@ import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* 表单验证(包含验证码)过滤类
@@ -57,8 +54,12 @@ public class FormFilter extends org.apache.shiro.web.filter.authc.FormAuthentica
public static final String MESSAGE_PARAM = "message"; // 登录返回消息
public static final String REMEMBER_USERCODE_PARAM = "rememberUserCode"; // 记住用户名
public static final String EXCEPTION_ATTRIBUTE_NAME = "exception"; // 异常类属性名
public static final String LOGIN_PARAM = "__login"; // 支持GET方式登录的参数
public static final String LOGIN_PARAM = "__login"; // 支持GET方式登录的参数
public static final Boolean POST_ROLE_PERMI = Global.getConfigToBoolean("user.postRolePermi", "false");
public static final Boolean SWITCH_OFFICE = Global.getConfigToBoolean("user.switchOffice", "false");
public static final Boolean LOGIN_AFTER_ACTIVE_MAIN_OFFICE = Global.getConfigToBoolean("user.loginAfterActiveMainOffice", "false");
private static final Logger logger = LoggerFactory.getLogger(FormFilter.class);
private static FormFilter instance;
@@ -424,8 +425,8 @@ public class FormFilter extends org.apache.shiro.web.filter.authc.FormAuthentica
Global.setLang((String)paramMap.get("lang"), request, response);
}
data.put("demoMode", Global.isDemoMode());
data.put("useCorpModel", Global.isUseCorpModel()
&& Global.getConfigToBoolean("user.loginCodeCorpUnique", "false"));
data.put("useCorpModel", Global.isUseCorpModel());
data.put("loginCodeCorpUnique", Global.getConfigToBoolean("user.loginCodeCorpUnique", "false"));
data.put("title", Global.getProperty("productName"));
data.put("company", Global.getProperty("companyName"));
data.put("version", Global.getProperty("productVersion"));
@@ -452,13 +453,15 @@ public class FormFilter extends org.apache.shiro.web.filter.authc.FormAuthentica
data.put("msgEnabled", Global.getPropertyToBoolean("msg.enabled", "false"));
data.put("sysCode", session.getAttribute("sysCode"));
data.put("roleCode", session.getAttribute("roleCode"));
data.put("postCode", session.getAttribute("postCode"));
data.put("title", Global.getProperty("productName"));
data.put("company", Global.getProperty("companyName"));
data.put("version", Global.getProperty("productVersion"));
data.put("year", Global.getProperty("copyrightYear"));
data.put("lang", Global.getLang());
List<Map<String, Object>> roleList = ListUtils.newArrayList();
String desktopUrl = null;
String desktopUrl = null; String roleCode = (String)session.getAttribute("roleCode");
Set<String> roleCodes = roleCode != null ? SetUtils.newHashSet(StringUtils.splitComma(roleCode)) : null;
for (Role role : user.getRoleList()){
Map<String, Object> roleMap = MapUtils.newHashMap();
roleMap.put("roleCode", role.getRoleCode());
@@ -466,11 +469,40 @@ public class FormFilter extends org.apache.shiro.web.filter.authc.FormAuthentica
roleMap.put("isShow", role.getIsShow());
roleMap.put("sysCodes", role.getSysCodes());
roleList.add(roleMap);
if (desktopUrl == null && StringUtils.isNotBlank(role.getDesktopUrl())) {
desktopUrl = role.getDesktopUrl();
// 根据当前身份,选择桌面地址(先得到先受用)
if (desktopUrl == null) {
if (roleCodes != null){
if (roleCodes.contains(role.getRoleCode()) && StringUtils.isNotBlank(role.getDesktopUrl())) {
desktopUrl = role.getDesktopUrl();
}
}else if (StringUtils.isNotBlank(role.getDesktopUrl())) {
desktopUrl = role.getDesktopUrl();
}
}
}
data.put("roleList", roleList);
if (POST_ROLE_PERMI && User.USER_TYPE_EMPLOYEE.equals(user.getUserType())) {
List<Map<String, Object>> postList = ListUtils.newArrayList();
data.put("postRolePermi", "true");
Employee employee = user.getRefObj();
for (EmployeePost ep : EmpUtils.getEmployeePostList(employee.getEmpCode())){
Post post = ep.getPost();
if (post != null) {
Map<String, Object> postMap = MapUtils.newHashMap();
postMap.put("postCode", post.getPostCode());
postMap.put("postName", post.getPostName());
postList.add(postMap);
}
}
data.put("postList", postList);
}
if (SWITCH_OFFICE && User.USER_TYPE_EMPLOYEE.equals(user.getUserType())) {
data.put("switchOffice", "true");
// 登录后指定当前部门,即当前部门权限,否则为混合权限
data.put("officeCode", LOGIN_AFTER_ACTIVE_MAIN_OFFICE ? EmpUtils.getCurrentOfficeCode()
: session.getAttribute("officeCode"));
data.put("officeName", EmpUtils.getCurrentOfficeName());
}
data.put("desktopUrl", desktopUrl != null ? desktopUrl : Global.getConfig("sys.index.desktopUrl"));
return data;
}

View File

@@ -71,6 +71,14 @@ public class BizCategory extends TreeEntity<BizCategory> {
this.viewCode = viewCode;
}
public String getViewCode_like() {
return sqlMap().getWhere().getValue("view_code", QueryType.LIKE);
}
public void setViewCode_like(String viewCode) {
sqlMap().getWhere().and("view_code", QueryType.LIKE, viewCode);
}
@NotBlank(message="分类名称不能为空")
@Size(min=0, max=64, message="分类名称长度不能超过 64 个字符")
public String getCategoryName() {

View File

@@ -12,7 +12,9 @@ import com.jeesite.common.lang.StringUtils;
import com.jeesite.common.web.BaseController;
import com.jeesite.modules.biz.entity.BizCategory;
import com.jeesite.modules.biz.service.BizCategoryService;
import com.jeesite.modules.sys.entity.Office;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@@ -43,7 +45,10 @@ public class BizCategoryController extends BaseController {
* 获取数据
*/
@ModelAttribute
public BizCategory get(String categoryCode, boolean isNewRecord) {
public BizCategory get(String categoryCode, boolean isNewRecord, HttpServletRequest request) {
if (StringUtils.endsWith(request.getRequestURI(), "listData")) {
return new BizCategory();
}
return bizCategoryService.get(categoryCode, isNewRecord);
}
@@ -77,13 +82,11 @@ public class BizCategoryController extends BaseController {
if (StringUtils.isBlank(bizCategory.getParentCode())) {
bizCategory.setParentCode(BizCategory.ROOT_CODE);
}
if (StringUtils.isNotBlank(bizCategory.getViewCode())) {
bizCategory.setParentCode(null);
}
if (StringUtils.isNotBlank(bizCategory.getCategoryName())){
bizCategory.setParentCode(null);
}
if (StringUtils.isNotBlank(bizCategory.getRemarks())){
if (StringUtils.isNotBlank(bizCategory.getCategoryCode())
|| StringUtils.isNotBlank(bizCategory.getViewCode())
|| StringUtils.isNotBlank(bizCategory.getViewCode_like())
|| StringUtils.isNotBlank(bizCategory.getCategoryName())
|| StringUtils.isNotBlank(bizCategory.getRemarks())){
bizCategory.setParentCode(null);
}
List<BizCategory> list = bizCategoryService.findList(bizCategory);
@@ -151,7 +154,7 @@ public class BizCategoryController extends BaseController {
public String disable(BizCategory bizCategory) {
BizCategory where = new BizCategory();
where.setStatus(BizCategory.STATUS_NORMAL);
where.setParentCodes("," + bizCategory.getId() + ",");
where.setParentCodes_rightLike(bizCategory.getParentCodes() + bizCategory.getId() + ",");
long count = bizCategoryService.findCount(where);
if (count > 0) {
return renderResult(Global.FALSE, text("该业务分类包含未停用的子业务分类!"));

View File

@@ -4,16 +4,14 @@
*/
package com.jeesite.modules.config.interceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.jeesite.common.config.Global;
import com.jeesite.common.lang.StringUtils;
import com.jeesite.modules.sys.interceptor.LogInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 后台管理日志记录拦截器
@@ -22,7 +20,6 @@ import com.jeesite.modules.sys.interceptor.LogInterceptor;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name="web.interceptor.log.enabled", havingValue="true", matchIfMissing=true)
@EnableWebMvc
public class LogInterceptorConfig implements WebMvcConfigurer {
@Override

View File

@@ -4,16 +4,14 @@
*/
package com.jeesite.modules.config.interceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.jeesite.common.config.Global;
import com.jeesite.common.lang.StringUtils;
import com.jeesite.modules.sys.interceptor.MobileInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 前台自动切换到手机视图拦截器
@@ -22,7 +20,6 @@ import com.jeesite.modules.sys.interceptor.MobileInterceptor;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name="web.interceptor.mobile.enabled", havingValue="true", matchIfMissing=false)
@EnableWebMvc
public class MobileViewInterceptorConfig implements WebMvcConfigurer {
@Override

View File

@@ -1,52 +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.modules.config.server;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
/**
* 将请求协议转换为 https
* @author ThinkGem
* @version 2020年1月21日
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name="server.schemeHttps", havingValue="true", matchIfMissing=false)
public class SchemeHttpsConfig {
@Bean
public FilterRegistrationBean<Filter> schemeHttpsFilter() {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setName("schemeHttpsFilter");
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
bean.setFilter((request, response, chain) -> {
chain.doFilter(new HttpServletRequestWrapper((HttpServletRequest) request) {
@Override
public String getScheme() {
return "https";
}
@Override
public StringBuffer getRequestURL() {
StringBuffer sb = super.getRequestURL();
if ("http:".equals(sb.substring(0, 5))){
return sb.replace(0, 5, "https:");
}else{
return sb;
}
}
}, response);
});
bean.addUrlPatterns("/*");
return bean;
}
}

View File

@@ -69,7 +69,7 @@ public class FileUploadController extends BaseController {
@RequestMapping(value = "/download/{fileUploadId}")
public String downloadFile(@PathVariable("fileUploadId") String fileUploadId, String preview, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
FileUpload fileUpload = fileUploadService.getFile(new FileUpload(fileUploadId));
return fileUploadService.downloadFile(fileUpload, preview, request, response);
return fileUploadService.downloadFile(fileUpload, preview, "file", request, response);
}
/**

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