commit 0ee56404c2ba2ff22d4476558da7a3bb2c57e40e Author: zc Date: Fri Feb 27 10:16:46 2026 +0800 first commit diff --git a/.flattened-pom.xml b/.flattened-pom.xml new file mode 100644 index 0000000..190d617 --- /dev/null +++ b/.flattened-pom.xml @@ -0,0 +1,217 @@ + + + 4.0.0 + + top.continew + continew-starter + 2.9.0 + + top.ysoft + ysoft-admin + 3.6.0-SNAPSHOT + pom + 园区管理系统 + https://github.com/ysoft-org/ysoft-admin + + + GNU LESSER GENERAL PUBLIC LICENSE + http://www.gnu.org/licenses/lgpl.html + + + + ysoft-webapi + ysoft-module-system + ysoft-plugin + ysoft-common + ysoft-extension + + + 3.6.0-SNAPSHOT + + + + + top.ysoft + ysoft-webapi + 3.6.0-SNAPSHOT + + + top.ysoft + ysoft-module-system + 3.6.0-SNAPSHOT + + + top.ysoft + ysoft-common + 3.6.0-SNAPSHOT + + + top.ysoft + ysoft-plugin-schedule + 3.6.0-SNAPSHOT + + + top.ysoft + ysoft-plugin-open + 3.6.0-SNAPSHOT + + + top.ysoft + ysoft-plugin-generator + 3.6.0-SNAPSHOT + + + + + + cn.hutool + hutool-all + + + org.projectlombok + lombok + true + + + org.mapstruct + mapstruct + 1.5.5.Final + + + org.mapstruct + mapstruct-processor + 1.5.5.Final + + + commons-fileupload + commons-fileupload + 1.4 + + + commons-io + commons-io + 2.8.0 + + + com.google.guava + guava + 29.0-jre + + + + + huawei-mirror + HuaweiCloud Mirror + https://mirrors.huaweicloud.com/repository/maven/ + + + ali-mirror + AliYun Mirror + https://maven.aliyun.com/repository/public/ + + + + + huawei-mirror + HuaweiCloud Mirror + https://mirrors.huaweicloud.com/repository/maven/ + + + ali-mirror + AliYun Mirror + https://maven.aliyun.com/repository/public/ + + + + + + maven-compiler-plugin + + -parameters + + + + maven-surefire-plugin + + true + + + + com.diffplug.spotless + spotless-maven-plugin + + + compile + + apply + + + + + + + + .style/p3c-codestyle.xml + + + .style/license-header + + + + + + org.codehaus.mojo + flatten-maven-plugin + + + flatten + process-resources + + flatten + + + + flatten-clean + clean + + clean + + + + + true + resolveCiFriendliesOnly + + + + + + + sonar + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + + + verify + + sonar + + + + + + + + Charles7c_ysoft-admin + ${project.groupId}:${project.artifactId} + https://sonarcloud.io + charles7c + + + + diff --git a/.image/qrcode.jpg b/.image/qrcode.jpg new file mode 100644 index 0000000..d95a7aa Binary files /dev/null and b/.image/qrcode.jpg differ diff --git a/.image/screenshot/000登录页面-H5.png b/.image/screenshot/000登录页面-H5.png new file mode 100644 index 0000000..ee50bed Binary files /dev/null and b/.image/screenshot/000登录页面-H5.png differ diff --git a/.image/screenshot/000登录页面.png b/.image/screenshot/000登录页面.png new file mode 100644 index 0000000..b8d8751 Binary files /dev/null and b/.image/screenshot/000登录页面.png differ diff --git a/.image/screenshot/001仪表盘.png b/.image/screenshot/001仪表盘.png new file mode 100644 index 0000000..294ca40 Binary files /dev/null and b/.image/screenshot/001仪表盘.png differ diff --git a/.image/screenshot/002分析页.png b/.image/screenshot/002分析页.png new file mode 100644 index 0000000..1fd7f9b Binary files /dev/null and b/.image/screenshot/002分析页.png differ diff --git a/.image/screenshot/010个人中心.png b/.image/screenshot/010个人中心.png new file mode 100644 index 0000000..8eae084 Binary files /dev/null and b/.image/screenshot/010个人中心.png differ diff --git a/.image/screenshot/011安全设置-修改邮箱.png b/.image/screenshot/011安全设置-修改邮箱.png new file mode 100644 index 0000000..c3bfdde Binary files /dev/null and b/.image/screenshot/011安全设置-修改邮箱.png differ diff --git a/.image/screenshot/012安全设置-修改邮箱-邮箱验证码.png b/.image/screenshot/012安全设置-修改邮箱-邮箱验证码.png new file mode 100644 index 0000000..c28c0f0 Binary files /dev/null and b/.image/screenshot/012安全设置-修改邮箱-邮箱验证码.png differ diff --git a/.image/screenshot/013消息中心.png b/.image/screenshot/013消息中心.png new file mode 100644 index 0000000..d0f1ce4 Binary files /dev/null and b/.image/screenshot/013消息中心.png differ diff --git a/.image/screenshot/020系统管理-用户管理-列表.png b/.image/screenshot/020系统管理-用户管理-列表.png new file mode 100644 index 0000000..efade46 Binary files /dev/null and b/.image/screenshot/020系统管理-用户管理-列表.png differ diff --git a/.image/screenshot/021系统管理-用户管理-新增.png b/.image/screenshot/021系统管理-用户管理-新增.png new file mode 100644 index 0000000..47b84f3 Binary files /dev/null and b/.image/screenshot/021系统管理-用户管理-新增.png differ diff --git a/.image/screenshot/025系统管理-角色管理-列表.png b/.image/screenshot/025系统管理-角色管理-列表.png new file mode 100644 index 0000000..9e248d7 Binary files /dev/null and b/.image/screenshot/025系统管理-角色管理-列表.png differ diff --git a/.image/screenshot/026系统管理-角色管理-新增.png b/.image/screenshot/026系统管理-角色管理-新增.png new file mode 100644 index 0000000..9910fbc Binary files /dev/null and b/.image/screenshot/026系统管理-角色管理-新增.png differ diff --git a/.image/screenshot/027系统管理-角色管理-分配.png b/.image/screenshot/027系统管理-角色管理-分配.png new file mode 100644 index 0000000..78d4e57 Binary files /dev/null and b/.image/screenshot/027系统管理-角色管理-分配.png differ diff --git a/.image/screenshot/030系统管理-菜单管理-列表.png b/.image/screenshot/030系统管理-菜单管理-列表.png new file mode 100644 index 0000000..f1190b7 Binary files /dev/null and b/.image/screenshot/030系统管理-菜单管理-列表.png differ diff --git a/.image/screenshot/031系统管理-菜单管理-新增.png b/.image/screenshot/031系统管理-菜单管理-新增.png new file mode 100644 index 0000000..b8f6a7a Binary files /dev/null and b/.image/screenshot/031系统管理-菜单管理-新增.png differ diff --git a/.image/screenshot/035系统管理-部门管理-列表.png b/.image/screenshot/035系统管理-部门管理-列表.png new file mode 100644 index 0000000..88fb5e8 Binary files /dev/null and b/.image/screenshot/035系统管理-部门管理-列表.png differ diff --git a/.image/screenshot/036系统管理-部门管理-新增.png b/.image/screenshot/036系统管理-部门管理-新增.png new file mode 100644 index 0000000..d6dab35 Binary files /dev/null and b/.image/screenshot/036系统管理-部门管理-新增.png differ diff --git a/.image/screenshot/040系统管理-字典管理-列表.png b/.image/screenshot/040系统管理-字典管理-列表.png new file mode 100644 index 0000000..4d76b60 Binary files /dev/null and b/.image/screenshot/040系统管理-字典管理-列表.png differ diff --git a/.image/screenshot/041系统管理-字典项管理.png b/.image/screenshot/041系统管理-字典项管理.png new file mode 100644 index 0000000..881e13f Binary files /dev/null and b/.image/screenshot/041系统管理-字典项管理.png differ diff --git a/.image/screenshot/045系统管理-公告管理-列表.png b/.image/screenshot/045系统管理-公告管理-列表.png new file mode 100644 index 0000000..73e0dfa Binary files /dev/null and b/.image/screenshot/045系统管理-公告管理-列表.png differ diff --git a/.image/screenshot/046系统管理-公告管理-修改.png b/.image/screenshot/046系统管理-公告管理-修改.png new file mode 100644 index 0000000..660c067 Binary files /dev/null and b/.image/screenshot/046系统管理-公告管理-修改.png differ diff --git a/.image/screenshot/050系统管理-文件管理-列表-1.png b/.image/screenshot/050系统管理-文件管理-列表-1.png new file mode 100644 index 0000000..3d8f51c Binary files /dev/null and b/.image/screenshot/050系统管理-文件管理-列表-1.png differ diff --git a/.image/screenshot/051系统管理-文件管理-列表-2.png b/.image/screenshot/051系统管理-文件管理-列表-2.png new file mode 100644 index 0000000..8d185bf Binary files /dev/null and b/.image/screenshot/051系统管理-文件管理-列表-2.png differ diff --git a/.image/screenshot/052系统管理-文件管理-查看文档.png b/.image/screenshot/052系统管理-文件管理-查看文档.png new file mode 100644 index 0000000..0359647 Binary files /dev/null and b/.image/screenshot/052系统管理-文件管理-查看文档.png differ diff --git a/.image/screenshot/055系统管理-存储管理-列表.png b/.image/screenshot/055系统管理-存储管理-列表.png new file mode 100644 index 0000000..89d5f60 Binary files /dev/null and b/.image/screenshot/055系统管理-存储管理-列表.png differ diff --git a/.image/screenshot/056系统管理-存储管理-新增.png b/.image/screenshot/056系统管理-存储管理-新增.png new file mode 100644 index 0000000..c0647cf Binary files /dev/null and b/.image/screenshot/056系统管理-存储管理-新增.png differ diff --git a/.image/screenshot/060系统管理-系统配置.png b/.image/screenshot/060系统管理-系统配置.png new file mode 100644 index 0000000..b264473 Binary files /dev/null and b/.image/screenshot/060系统管理-系统配置.png differ diff --git a/.image/screenshot/061系统管理-安全配置.png b/.image/screenshot/061系统管理-安全配置.png new file mode 100644 index 0000000..10598f6 Binary files /dev/null and b/.image/screenshot/061系统管理-安全配置.png differ diff --git a/.image/screenshot/100系统监控-在线用户.png b/.image/screenshot/100系统监控-在线用户.png new file mode 100644 index 0000000..b8c05f3 Binary files /dev/null and b/.image/screenshot/100系统监控-在线用户.png differ diff --git a/.image/screenshot/101系统监控-系统日志-登录日志.png b/.image/screenshot/101系统监控-系统日志-登录日志.png new file mode 100644 index 0000000..4428c31 Binary files /dev/null and b/.image/screenshot/101系统监控-系统日志-登录日志.png differ diff --git a/.image/screenshot/102系统监控-系统日志-操作日志.png b/.image/screenshot/102系统监控-系统日志-操作日志.png new file mode 100644 index 0000000..71017e2 Binary files /dev/null and b/.image/screenshot/102系统监控-系统日志-操作日志.png differ diff --git a/.image/screenshot/103系统监控-系统日志-操作日志-详情.png b/.image/screenshot/103系统监控-系统日志-操作日志-详情.png new file mode 100644 index 0000000..80c7964 Binary files /dev/null and b/.image/screenshot/103系统监控-系统日志-操作日志-详情.png differ diff --git a/.image/screenshot/150任务调度-任务管理-列表.png b/.image/screenshot/150任务调度-任务管理-列表.png new file mode 100644 index 0000000..b734ebc Binary files /dev/null and b/.image/screenshot/150任务调度-任务管理-列表.png differ diff --git a/.image/screenshot/151任务调度-任务管理-新增.png b/.image/screenshot/151任务调度-任务管理-新增.png new file mode 100644 index 0000000..bfee241 Binary files /dev/null and b/.image/screenshot/151任务调度-任务管理-新增.png differ diff --git a/.image/screenshot/155任务调度-任务日志-列表.png b/.image/screenshot/155任务调度-任务日志-列表.png new file mode 100644 index 0000000..6fddcfe Binary files /dev/null and b/.image/screenshot/155任务调度-任务日志-列表.png differ diff --git a/.image/screenshot/156任务调度-任务日志-详情.png b/.image/screenshot/156任务调度-任务日志-详情.png new file mode 100644 index 0000000..d692c74 Binary files /dev/null and b/.image/screenshot/156任务调度-任务日志-详情.png differ diff --git a/.image/screenshot/200能力开放-应用管理-列表.png b/.image/screenshot/200能力开放-应用管理-列表.png new file mode 100644 index 0000000..b16adf5 Binary files /dev/null and b/.image/screenshot/200能力开放-应用管理-列表.png differ diff --git a/.image/screenshot/201能力开放-应用管理-新增.png b/.image/screenshot/201能力开放-应用管理-新增.png new file mode 100644 index 0000000..1aa49c8 Binary files /dev/null and b/.image/screenshot/201能力开放-应用管理-新增.png differ diff --git a/.image/screenshot/300系统工具-代码生成-列表.png b/.image/screenshot/300系统工具-代码生成-列表.png new file mode 100644 index 0000000..7845387 Binary files /dev/null and b/.image/screenshot/300系统工具-代码生成-列表.png differ diff --git a/.image/screenshot/301系统工具-代码生成-配置.png b/.image/screenshot/301系统工具-代码生成-配置.png new file mode 100644 index 0000000..1e62a54 Binary files /dev/null and b/.image/screenshot/301系统工具-代码生成-配置.png differ diff --git a/.image/screenshot/302系统工具-代码生成-预览.png b/.image/screenshot/302系统工具-代码生成-预览.png new file mode 100644 index 0000000..e898208 Binary files /dev/null and b/.image/screenshot/302系统工具-代码生成-预览.png differ diff --git a/.style/Java开发手册(黄山版).pdf b/.style/Java开发手册(黄山版).pdf new file mode 100644 index 0000000..6ff47d6 Binary files /dev/null and b/.style/Java开发手册(黄山版).pdf differ diff --git a/.style/license-header b/.style/license-header new file mode 100644 index 0000000..e69de29 diff --git a/.style/p3c-codestyle.xml b/.style/p3c-codestyle.xml new file mode 100644 index 0000000..fb5f47f --- /dev/null +++ b/.style/p3c-codestyle.xml @@ -0,0 +1,380 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..131bd0a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,790 @@ +## [v3.5.0](https://github.com/ysoft-org/ysoft-admin/compare/v3.4.1...v3.5.0) (2025-03-05) + +### ✨ 新特性 + +* 【generator】生成预览支持批量 ([a7296a3](https://github.com/ysoft-org/ysoft-admin/commit/a7296a36278f68108a0513fe26b3cccd9af5244c)) +* 【generator】代码生成新增 Mapper.xml 模板 ([b519364](https://github.com/ysoft-org/ysoft-admin/commit/b51936445d36a76650d94225b3ecfa81b86e066c)) ([98569ae](https://github.com/ysoft-org/ysoft-admin/commit/98569ae20530a3c94cd1a071a29fae3c904761e3)) +* 🔥新增终端管理,重构认证体系,多端认证鉴权控制 ([Gitee#40](https://gitee.com/ysoft/ysoft-admin/pulls/40)) ([95f2617](https://github.com/ysoft-org/ysoft-admin/commit/95f2617a4c086e3e5113799fe213c24c42bfbd17)) ([c90e80e](https://github.com/ysoft-org/ysoft-admin/commit/c90e80e9d72eba173df586e7f12a0c9378c82ec9)) ([438615f](https://github.com/ysoft-org/ysoft-admin/commit/438615f87c5583890703c78c4e61aae36e3dab80)) ([229bd9b](https://github.com/ysoft-org/ysoft-admin/commit/229bd9becfda15cbcb049fd1e0842dbe04d7b135)) ([a305eac](https://github.com/ysoft-org/ysoft-admin/commit/a305eac96f766ad3a72051fe19f533f4fd64d304)) ([82cf439](https://github.com/ysoft-org/ysoft-admin/commit/82cf4390e8fdc73da1818dcc9a5dd0c7ff87db02)) ([5f68e84](https://github.com/ysoft-org/ysoft-admin/commit/5f68e84e7dbb5c2d0f793c72df76d69d326e574e)) +* 【generator】支持源项目内生成代码文件 ([GitHub#125](https://github.com/ysoft-org/ysoft-admin/pull/125)) ([653802e](https://github.com/ysoft-org/ysoft-admin/commit/653802efbe2debdbfd1fe5097dae7280e80f1e43)) +* 新增接口文档配置,支持显示 SaToken 权限码 ([Gitee#44](https://gitee.com/ysoft/ysoft-admin/pulls/44)) ([19c5dbd](https://github.com/ysoft-org/ysoft-admin/commit/19c5dbd2659264cfba59e2b1316420c39f82f731)) +* 新增NoHandlerFoundException、HttpRequestMethodNotSupportedException异常处理 ([Gitee#44](https://gitee.com/ysoft/ysoft-admin/pulls/44)) ([4efe025](https://github.com/ysoft-org/ysoft-admin/commit/4efe025b2e36c56162dc8fcbd05482b7ecc21e5f)) + +### 💎 功能优化 + +- 菜单路由为空时默认返回空列表而非 null ([43cc429](https://github.com/ysoft-org/ysoft-admin/commit/43cc429234150e185a697b6dbc340bd966bf6133)) +- 移除初始数据脚本 update_user、update_time 字段赋值(此优化无需跟进升级) ([9d0e1fc](https://github.com/ysoft-org/ysoft-admin/commit/9d0e1fc8e642c5be46d20173fd573582ad54d5e6)) +- 【generator】消除前端红色报警、更新表格创建者和更新者字段索引,自定义单选框数据 ([GitHub#108](https://github.com/ysoft-org/ysoft-admin/pull/108)) ([4c8ebf2](https://github.com/ysoft-org/ysoft-admin/commit/4c8ebf2d0f3e65737f6a55c06eb1ff2d31273505)) +- 更新 nginx.conf 部署配置文件 ([4920d7b](https://github.com/ysoft-org/ysoft-admin/commit/4920d7b730c2e975c4a50a5b8b8172f5365509c5)) +- 调整 starter 内的 BaseResp、BaseDetailResp 到 admin 项目 ([144251b](https://github.com/ysoft-org/ysoft-admin/commit/144251b21ec0d79927164a705f3c846aace53ca1)) +- 调整 starter 内的 CommonUserService、ContainerPool 到 admin 项目 ([f1d0b49](https://github.com/ysoft-org/ysoft-admin/commit/f1d0b491b14d806fbe1d0011cdadea64336fe3b0)) +- 优化登录日志描述 ([a24136d](https://github.com/ysoft-org/ysoft-admin/commit/a24136d6fe92bcb055c965600caef399df563361)) +- 丰富部门、角色、用户初始测试数据,方便开发场景 ([b5bbdb2](https://github.com/ysoft-org/ysoft-admin/commit/b5bbdb27e6e002d52b5a1894e2bf8e24e6f963e8)) +- 调整 starter 内的 BaseDO、BaseCreateDO、BaseUpdateDO 到 admin 项目 ([498e680](https://github.com/ysoft-org/ysoft-admin/commit/498e680672df00be9e8f9546f2010fcc54faab23)) +- 🔥重构角色管理,更新权限扁平化 ([f6535ef](https://github.com/ysoft-org/ysoft-admin/commit/f6535ef7a35794147d8094eed71a4789c06f3db8)) ([0a62f81](https://github.com/ysoft-org/ysoft-admin/commit/0a62f81ad7c1f7b086379059fe780a609e5d575b)) ([144cfa2](https://github.com/ysoft-org/ysoft-admin/commit/144cfa27ce944dc5b78d2d26466cd149b8ca7959)) +- 优化任务调度服务配置,允许用户名密码使用环境变量 ([GitHub#129](https://github.com/ysoft-org/ysoft-admin/pull/129)) ([0e65190](https://github.com/ysoft-org/ysoft-admin/commit/0e651902f267feb8b4997d066285d979429bd56f)) +- 优化系统配置 SQL 数据脚本 ([d336911](https://github.com/ysoft-org/ysoft-admin/commit/d3369119e090b62468fda38e1dfb52ddb5dc7df3)) +- 调整日志 module 字段长度 50 => 100 ([65941c1](https://github.com/ysoft-org/ysoft-admin/commit/65941c1ee4020e2f9b4fc07f15c249b9d0d7e851)) +- 🔥重构存储管理,新增设置默认存储、修改状态接口 ([37d6efb](https://github.com/ysoft-org/ysoft-admin/commit/37d6efb70e5bcfddc8a4ca6becaf440ab05785fb)) + +### 🐛 问题修复 + +- 【generator】修复 PostgreSQL 菜单 SQL 脚本模板错误 ([GitHub#107](https://github.com/ysoft-org/ysoft-admin/pull/107)) ([af403d0](https://github.com/ysoft-org/ysoft-admin/commit/af403d055af1c186d7b5976cff894e9dd2afcc01)) +- 【generator】生成菜单脚本添加ID ([GitHub#109](https://github.com/ysoft-org/ysoft-admin/pull/109)) ([9ebecdc](https://github.com/ysoft-org/ysoft-admin/commit/9ebecdc1935a99f15ece4e2c933175d9919e9825)) +- 【generator】前端页面生成表单类型 ([GitHub#110](https://github.com/ysoft-org/ysoft-admin/pull/110)) ([75d2662](https://github.com/ysoft-org/ysoft-admin/commit/75d26623652d4813643cea9a7c3f821e44edc885)) +- 完善部分 in 查询前的空集合处理 ([899354a](https://github.com/ysoft-org/ysoft-admin/commit/899354a6e7239ed00f81155e8ed5f2760b191480)) +- 修复公告通知范围字段类型错误 ([fdd0617](https://github.com/ysoft-org/ysoft-admin/commit/fdd0617a2832221bb017d2e17dc5fb82af862707)) +- 修复通知公告分页 通知范围字段类型回显错误 ([160ab8d](https://github.com/ysoft-org/ysoft-admin/commit/160ab8d38bb68801ed6efe0ceeb4ccb4c3f4fba3)) +- 🔥修复 PageResp 手动分页计算错误 ([6bcff72](https://github.com/ysoft-org/ysoft-admin/commit/6bcff7244f0a37474c39509dfed44d3cf630d898)) +- 修复导入用户部门名称校验注解使用错误 ([Gitee#41](https://gitee.com/ysoft/ysoft-admin/pulls/41)) ([c870014](https://github.com/ysoft-org/ysoft-admin/commit/c870014730a47d9a9b567416b2f6736049a25125)) +- 修复 PostgreSQL Liquibase 数据脚本缺失 ([8c53700](https://github.com/ysoft-org/ysoft-admin/commit/8c53700cfd8ea20ccece1867161d2315d5346d9b)) +- 修复新增用户时日志记录获取 description 为空的问题 ([91924ac](https://github.com/ysoft-org/ysoft-admin/commit/91924acaa15658db88c387f81ff723ca043cd1f1)) +- 调整 PostgreSQL 连接配置以消除部分类型使用报错 ([7e3257b](https://github.com/ysoft-org/ysoft-admin/commit/7e3257bd6d3965622ba53a906fd8b27e5209e67f)) +- 修复部分过期配置信息 ([3fb9922](https://github.com/ysoft-org/ysoft-admin/commit/3fb9922b524a5c1a40de5bd011c99c9863032f7a)) +- 修复邮箱登录,手机号登录对应日志没有记录操作人问题 ([Gitee#42](https://gitee.com/ysoft/ysoft-admin/pulls/42)) ([aab3931](https://github.com/ysoft-org/ysoft-admin/commit/aab3931f3078c1b3468c5f06e1f133862f947d7e)) +- mysql 8.x failing to connect to the database correctly issue ([GitHub#128](https://github.com/ysoft-org/ysoft-admin/pull/128)) ([4caada8](https://github.com/ysoft-org/ysoft-admin/commit/4caada8c64c1f6646f312a0038338396ae860305)) + +### 📦 依赖升级 + +- 🔥ContiNew Starter 2.7.5 => 2.9.0 (更多特性及依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/ysoft-org/ysoft-starter/blob/dev/CHANGELOG.md)) + +## [v3.4.1](https://github.com/ysoft-org/ysoft-admin/compare/v3.4.0...v3.4.1) (2024-12-08) + +### ✨ 新特性 + +* 新增验证码配置开关 ([e314346](https://github.com/ysoft-org/ysoft-admin/commit/e31434617e751f08d12ace7773bb3ba7bf132370)) ([61fe39d](https://github.com/ysoft-org/ysoft-admin/commit/61fe39d439b73c90cfeb989f8f4727ade6b5b3b2)) (Gitee#37@aiming317) + +### 💎 功能优化 + +- 【open】优化 API 参数签名处理 ([22b3564](https://github.com/ysoft-org/ysoft-admin/commit/22b3564a2217dee739fc2172453b23600d82d6de)) +- 移除关于项目菜单初始数据(该菜单从动态路由调整为前端静态,且不再需要鉴权) ([88313c8](https://github.com/ysoft-org/ysoft-admin/commit/88313c8b2017e7ec620e5372f34bf5e431ce3e7f)) +- 优化代码生成菜单图标 ([9296985](https://github.com/ysoft-org/ysoft-admin/commit/9296985be0ab63ba54c63c71c011681f91aef7fb)) +- BaseServiceImpl 所在包调整 ([d7ae7b4](https://github.com/ysoft-org/ysoft-admin/commit/d7ae7b4e42c424a3db51c72a0ed79572c9fd7601)) +- BaseController 改为在 Admin common 模块编写(重构权限校验 checkPermission 处理) ([d7ae7b4](https://github.com/ysoft-org/ysoft-admin/commit/d7ae7b4e42c424a3db51c72a0ed79572c9fd7601)) +- CRUD ValidateGroup => CrudValidationGroup ([d7ae7b4](https://github.com/ysoft-org/ysoft-admin/commit/d7ae7b4e42c424a3db51c72a0ed79572c9fd7601)) +- ValidateGroup => ValidationGroup ([d7ae7b4](https://github.com/ysoft-org/ysoft-admin/commit/d7ae7b4e42c424a3db51c72a0ed79572c9fd7601)) + +### 🐛 问题修复 + +- 【generator】修复 columnSize 类型错误,兼容无注释字段配置 ([6b64ae3](https://github.com/ysoft-org/ysoft-admin/commit/6b64ae3e07a76d844eec4bd05302126cbcaca31b)) +- 补充能力开放模块接口文档配置 ([270fbf1](https://github.com/ysoft-org/ysoft-admin/commit/270fbf15af338a6ac3e6a686409eea8e9a32b6bf)) +- 修复文件管理删除文件异常或不成功的情况 ([361a412](https://github.com/ysoft-org/ysoft-admin/commit/361a41258e9fdece5ba681298f2839b013d6cfab)) (Gitee#35@kiki1373639299) +- 修复本地文件管理删除文件异常或不成功的情况 ([c7b58a0](https://github.com/ysoft-org/ysoft-admin/commit/c7b58a0fd167c566f6680c87cc455b71c42b8eda)) (Gitee#36@kiki1373639299) +- 修复 Query 查询数组范围报错 ([d7ae7b4](https://github.com/ysoft-org/ysoft-admin/commit/d7ae7b4e42c424a3db51c72a0ed79572c9fd7601)) + +### 📦 依赖升级 + +- ContiNew Starter 2.7.4 => 2.7.5 (更多特性及依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/ysoft-org/ysoft-starter/blob/dev/CHANGELOG.md)) + +## [v3.4.0](https://github.com/ysoft-org/ysoft-admin/compare/v3.3.0...v3.4.0) (2024-11-18) + +### ✨ 新特性 + +* 新增仪表盘分析接口,查询访问时段分析、查询模块分析、查询终端分析、查询浏览器分析 ([dea8dbe](https://github.com/ysoft-org/ysoft-admin/commit/dea8dbe131867a564f7e151a6484db5be6effaa3)) +* 新增查询仪表盘数据总览相关接口, 重构仪表盘相关代码 ([e01df09](https://github.com/ysoft-org/ysoft-admin/commit/e01df09127e6efc33971d64e2fe49a2a42282425)) +* 公告支持设置通知范围 ([29202ae](https://github.com/ysoft-org/ysoft-admin/commit/29202aea307a7257c9d1e9649dee00140164c59c)) (GitCode#1) +* 角色管理增加分配角色功能 ([73f880e](https://github.com/ysoft-org/ysoft-admin/commit/73f880ec57cfdccfc297aac228410f5bb7fed448)) ([ad3f832](https://github.com/ysoft-org/ysoft-admin/commit/ad3f8329dd07858b0982020db3605200112f09b5)) (GitHub#93) +* 新增能力开放模块应用管理功能 ([f774183](https://github.com/ysoft-org/ysoft-admin/commit/f7741832bdd315039cc5f1aa062d8ffac32ddf0f)) ([d1b3824](https://github.com/ysoft-org/ysoft-admin/commit/d1b38242b9f291c897a8eb82bd330bd775e656cf)) ([4454daa](https://github.com/ysoft-org/ysoft-admin/commit/4454daa9e07af7c7202c386e533ea055bb79f0df)) (Gitee#31) +* 新增查询用户字典接口 ([d4b02ba](https://github.com/ysoft-org/ysoft-admin/commit/d4b02ba9180f82084e5ca844eaa9b4a0966a0164)) +* 代码生成新增菜单SQL模板 ([fb947c9](https://github.com/ysoft-org/ysoft-admin/commit/fb947c98fdd075a19c55e1e9e5c137a8482db618)) (GitHub#95) + +### 💎 功能优化 + +- 优化部分 Mapper 方法使用 (替换为 MP 新增方法) ([ad69d44](https://github.com/ysoft-org/ysoft-admin/commit/ad69d44ebda72dbdb639ba4ff48cde9aa7f6e400)) +- 优化查询代码生成表性能 ([781d291](https://github.com/ysoft-org/ysoft-admin/commit/781d29142fbcc1f981d3760565f2f96b49570438)) +- 移除上传文件时的多余校验 ([8466105](https://github.com/ysoft-org/ysoft-admin/commit/8466105a9b8b2fd807ac9d3029e4da7bc609d551)) +- 重构获取登录用户信息方式(线程级存储) ([79ea39d](https://github.com/ysoft-org/ysoft-admin/commit/79ea39dd078639b4e137d576f3d7820bb6c24d0a)) +- 完善及优化代码生成模板 ([ffdc971](https://github.com/ysoft-org/ysoft-admin/commit/ffdc9712d4cd1fd3093cec0780f630d672339cdf)) ([2b47ed7](https://github.com/ysoft-org/ysoft-admin/commit/2b47ed711074ad64d98fa0d9d68ccd2777b70bf2)) ([90e3bc0](https://github.com/ysoft-org/ysoft-admin/commit/90e3bc0595fdb692644ed7fd9b1a3735962cb68b)) ([985bc25](https://github.com/ysoft-org/ysoft-admin/commit/985bc25716daae6fb018a856af889919436f26e6)) +- 字典项管理日志模块调整为字典管理 ([60cb2e3](https://github.com/ysoft-org/ysoft-admin/commit/60cb2e3b5cae56d9a3117aeea6c57ec02cb9abe4)) +- 解决查询日志数据时索引失效的问题 ([4525cb3](https://github.com/ysoft-org/ysoft-admin/commit/4525cb3531c06354e3dc57147c93dd5f7f8a4e8a)) +- 重构拆分 liquibase 脚本结构 ([aadaa5b](https://github.com/ysoft-org/ysoft-admin/commit/aadaa5b4a70caaa775a38fd542890aa8d7f951c6)) +- 调整系统配置菜单图标 ([872bc1c](https://github.com/ysoft-org/ysoft-admin/commit/872bc1ca8143632ea0ca00eb320e57f61729937c)) +- 优化系统管理、代码生成相关代码及初始数据脚本 ([9ecdeb5](https://github.com/ysoft-org/ysoft-admin/commit/9ecdeb52f601b93116f6e89d8db32d8db95cb0c5)) ([5717d03](https://github.com/ysoft-org/ysoft-admin/commit/5717d03d01f8f052688bef873def83a3b8defc21)) ([7870de2](https://github.com/ysoft-org/ysoft-admin/commit/7870de28926cde86a8732ad5d6950616e1078d57)) +- 优化项目模块命名(简化、分类、统一) ([c276e53](https://github.com/ysoft-org/ysoft-admin/commit/c276e53a8e02e64f9e5d8270171e20424804382d)) +- 优化任务调度配置及 docker 部署脚本 ([b927470](https://github.com/ysoft-org/ysoft-admin/commit/b927470e33bb75594f1546a4f06d48bc2281f5ab)) ([c5cd4e2](https://github.com/ysoft-org/ysoft-admin/commit/c5cd4e2c284fa195411f704e41ca50a70271c6b7)) +- 重构仪表盘查询地域分析接口 ([e0e157f](https://github.com/ysoft-org/ysoft-admin/commit/e0e157f0e5b23ff6e27ec7ae0ee028af6f2facd1)) +- 完善 PostgreSQL 代码生成类型映射配置 ([4c36f23](https://github.com/ysoft-org/ysoft-admin/commit/4c36f2339830272aa047e46d02f34485b9051ec3)) +- 优化通知公告部分代码 ([e1941ec](https://github.com/ysoft-org/ysoft-admin/commit/e1941eca455a066dae631b1533d63f8d9a193161)) +- 优化初始数据脚本 ([6abb444](https://github.com/ysoft-org/ysoft-admin/commit/6abb444f9dce7ed1ea4aa90502c21d2ab6c8e247)) +- 忽略获取在线用户信息异常 ([4856366](https://github.com/ysoft-org/ysoft-admin/commit/48563663e1fed93154f63f6c9a6c07ea79d741da)) +- 优化部分注释 ([3116836](https://github.com/ysoft-org/ysoft-admin/commit/3116836b0139232769797da271a400fa4d9d52fa)) + +### 🐛 问题修复 + +- 参数配置支持设值为空 ([d7e8fc9](https://github.com/ysoft-org/ysoft-admin/commit/d7e8fc9bc31409b3652b5ad03a79248726547088)) +- 修复修改存储时同时设置默认存储及启用判断顺序错误 ([d9602e8](https://github.com/ysoft-org/ysoft-admin/commit/d9602e8639bcd125b15faea5ca7f618429bcc50e)) +- 修复任务日志缺失异常堆栈的问题 ([5cbeddb](https://github.com/ysoft-org/ysoft-admin/commit/5cbeddb97bd38274641ac5d63226a937975d69ba)) (Gitee#29) +- 修复更新在线用户权限信息报错的问题 ([8278032](https://github.com/ysoft-org/ysoft-admin/commit/82780324b7c2faeba22f2dbde440d1cf0e42c3c9)) +- 修复查询日志排序错误 ([8b403f4](https://github.com/ysoft-org/ysoft-admin/commit/8b403f4357caeac0c0a4cc9ac67c73043a1f4465)) +- 修复部分错误规范代码 ([a83b45f](https://github.com/ysoft-org/ysoft-admin/commit/a83b45f776234274a844337a2f2b541705ba5aff)) +- 调整部分实体包 ([3f4331e](https://github.com/ysoft-org/ysoft-admin/commit/3f4331e92b86e73303c4d675f0f1d4bc91a2a71b)) +- 修复获取邮箱验证码未进行行为验证码校验错误 ([731bfa0](https://github.com/ysoft-org/ysoft-admin/commit/731bfa065ab3a10ab933aaffd2e9ceebf0a4d16d)) +- 完善用户角色变更校验及在线用户权限处理 ([c28d3cf](https://github.com/ysoft-org/ysoft-admin/commit/c28d3cf1c45212e670b90fc0077b5e176a894bd2)) +- 修复查询系统配置参数漏洞 :boom: ([8c3fe35](https://github.com/ysoft-org/ysoft-admin/commit/8c3fe353be5d68f1ed252eef12f5fcdc0a1e3c83)) + +### 📦 依赖升级 + +- ContiNew Starter 2.6.0 => 2.7.4 (更多特性及依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/ysoft-org/ysoft-starter/blob/dev/CHANGELOG.md)) + +## [v3.3.0](https://github.com/ysoft-org/ysoft-admin/compare/v3.2.0...v3.3.0) (2024-09-09) + +### ✨ 新特性 + +* 重构全局响应处理及异常拦截,自定义异常拦截从 Starter 调整到 Admin 项目 ([d7621c6](https://github.com/ysoft-org/ysoft-admin/commit/d7621c6b26bfd253d9295444ea144f5dabf67f44)) +* 重构 Controller 接口方法返回值写法,接口文档也已适配处理 ([d7621c6](https://github.com/ysoft-org/ysoft-admin/commit/d7621c6b26bfd253d9295444ea144f5dabf67f44)) ([0f1479f](https://github.com/ysoft-org/ysoft-admin/commit/0f1479f40deef83a5f5d64cbc24a7691b274b112)) +* 代码生成字段配置时支持指定排序 ([d56b9aa](https://github.com/ysoft-org/ysoft-admin/commit/d56b9aa35ee2502804f487487d7bac02f4edc9b0)) +* 代码生成字段配置时支持选择关联字典 ([fdd21a0](https://github.com/ysoft-org/ysoft-admin/commit/fdd21a01c106e12321d0d1886ff643c72a09943b)) ([ecc98b1](https://github.com/ysoft-org/ysoft-admin/commit/ecc98b1999d90c1a7a29af94dc8705283f34dada)) +* 修改角色功能权限、数据权限支持衔接新增角色时的父子联动选项 ([387fb19](https://github.com/ysoft-org/ysoft-admin/commit/387fb194640d4a288f053c3eba1bf5b314d64da7)) + +### 💎 功能优化 + +- 移除 WebMvcConfiguration 配置(已迁移到 Starter 项目)([d7621c6](https://github.com/ysoft-org/ysoft-admin/commit/d7621c6b26bfd253d9295444ea144f5dabf67f44)) +- 重构日志持久层接口本地实现类 ([2c1eb56](https://github.com/ysoft-org/ysoft-admin/commit/2c1eb5660f69a9ab702d503944a11e47edac1142)) +- 优化打包配置,模板等配置文件提取到 jar 包外部 ([75cef77](https://github.com/ysoft-org/ysoft-admin/commit/75cef773187e5b5060a10a12e7c9912002376d7a)) +- 优化健康监测接口响应信息 ([bb5a92e](https://github.com/ysoft-org/ysoft-admin/commit/bb5a92e5ca238ed677d9ac3589fdf8009d2ac232)) +- 优化代码生成列配置代码,取消后端部分默认值 ([f5ee2b5](https://github.com/ysoft-org/ysoft-admin/commit/f5ee2b5beb9572d3fcd5b7c2f6db0627dedb31aa)) ([ca9f34d](https://github.com/ysoft-org/ysoft-admin/commit/ca9f34d3d5a3f96c6df537036a5fd876cae2e89a)) +- 重构权限变更逻辑,修改角色、变更用户角色不再下线用户 ([ad9a600](https://github.com/ysoft-org/ysoft-admin/commit/ad9a6000fcb5d64b04cf230caa3cbacc8c3ac8d7)) + +### 🐛 问题修复 + +- 修复打包部署后,下载用户导入模板异常问题 (Gitee#25) ([c7ffc67](https://github.com/ysoft-org/ysoft-admin/commit/c7ffc67cdc9139a4398c7dc819ca453880bd100a)) +- 修复日志记录仅支持获取 JSON 结构响应体的问题 ([d7621c6](https://github.com/ysoft-org/ysoft-admin/commit/d7621c6b26bfd253d9295444ea144f5dabf67f44)) +- 修复并增强 SQL 注入防御 ([0f1479f](https://github.com/ysoft-org/ysoft-admin/commit/0f1479f40deef83a5f5d64cbc24a7691b274b112)) +- 修复目录、菜单的组件名称重复的错误问题 ([9e91f56](https://github.com/ysoft-org/ysoft-admin/commit/9e91f563e2a263ce302dc3bf17c89e37c2b56285)) +- 修复 DataPermission 注解表别名配置无效的问题 ([6c4e252](https://github.com/ysoft-org/ysoft-admin/commit/6c4e2522df3f44ba0f5a21228e805b8ac98f8e6b)) +- 临时移除 MyBatis Plus saveBatch 不兼容的 rewriteBatchedStatements 配置 ([25240fa](https://github.com/ysoft-org/ysoft-admin/commit/25240fa81957a1677deda294ce8f2b0af5413315)) +- 修复更新会导致原加密失效的问题 ([8903195](https://github.com/ysoft-org/ysoft-admin/commit/89031954c0b7daee1c08e1a10fd50139301cd6ab)) ([c87317d](https://github.com/ysoft-org/ysoft-admin/commit/c87317d19946989e86dfbc5f24b155b2ea5abdc9)) +- 修复角色查询参数与前端不一致的问题 ([098571f](https://github.com/ysoft-org/ysoft-admin/commit/098571ffb2febc6163d2b9e5b18c4796ea80cbfa)) +- 修复特殊校验异常不打印堆栈 ([c87317d](https://github.com/ysoft-org/ysoft-admin/commit/c87317d19946989e86dfbc5f24b155b2ea5abdc9)) +- 修复日志全局 includes 配置会被局部修改的问题 ([c87317d](https://github.com/ysoft-org/ysoft-admin/commit/c87317d19946989e86dfbc5f24b155b2ea5abdc9)) +- 修复初始数据错误 ([403c72a](https://github.com/ysoft-org/ysoft-admin/commit/403c72aa52a0f0852208f15fa1c7117ee26414f0)) + +### 📦 依赖升级 + +- ContiNew Starter 2.4.0 => 2.6.0 (更多特性及依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/ysoft-org/ysoft-starter/blob/dev/CHANGELOG.md)) + +## [v3.2.0](https://github.com/ysoft-org/ysoft-admin/compare/v3.1.0...v3.2.0) (2024-08-05) + +### ✨ 新特性 + +* 新增用户批量导入功能 (GitHub#78) ([c2ad055](https://github.com/ysoft-org/ysoft-admin/commit/c2ad055cf82187e132e4cde9e15251e554deadff)) +* 新增任务调度模块 SnailJob(灵活,可靠和快速的分布式任务重试和分布式任务调度平台) (Gitee#22) ([ce1acea](https://github.com/ysoft-org/ysoft-admin/commit/ce1acea1535083345c33bcc427054831faf5d2e3)) ([ed5594b](https://github.com/ysoft-org/ysoft-admin/commit/ed5594b31e1d31b2a9210b65838d545067ca812f)) ([797221b](https://github.com/ysoft-org/ysoft-admin/commit/797221b4dc66c582b95b872b3cb60429247b14e9)) ([7b381b3](https://github.com/ysoft-org/ysoft-admin/commit/7b381b36de4ed4ef657249028188abc2f274b036)) ([ffe75e1](https://github.com/ysoft-org/ysoft-admin/commit/ffe75e111eb333236e923a1ed14dae5257b09357)) ([cef5cb4](https://github.com/ysoft-org/ysoft-admin/commit/cef5cb4fa5e6ee2325fd3738e1c8601a75277dd8)) ([513d8d9](https://github.com/ysoft-org/ysoft-admin/commit/513d8d9324b952438ec5513e9e3c7dfb092d5b83)) +* 修改 sys_option sql 脚本以适配 base64 图片 (Gitee#25) ([6848559](https://github.com/ysoft-org/ysoft-admin/commit/68485596c47a884b453e4632b59b525527293e17)) + +### 💎 功能优化 + +- 优化更新手机号、邮箱语句 ([9995bf0](https://github.com/ysoft-org/ysoft-admin/commit/9995bf0200e6256ccc4a26d8847e37fb85b4a226)) +- 重构适配 ContiNew Starter 最新线程池配置 ([5604fe9](https://github.com/ysoft-org/ysoft-admin/commit/5604fe95784b2627f1c8144144546de05434577e)) +- 获取短信、邮箱验证码接口适配 ContiNew Starter 限流器 ([44811fc](https://github.com/ysoft-org/ysoft-admin/commit/44811fc93283f508953f5fc9193c0b03305da5b2)) +- 移动 SaToken 配置到 webapi 模块 ([d733b7f](https://github.com/ysoft-org/ysoft-admin/commit/d733b7f1661a5f0df2b6f12b33ba69da8810e0aa)) +- 新增 ysoft-admin-plugins 插件模块,代码生成迁移到插件模块,为后续插件化改造铺垫 ([52f3be8](https://github.com/ysoft-org/ysoft-admin/commit/52f3be8ee3a07334c76cdfc06d15698b7ccd65ea)) +- 使用分组校验优化存储管理 ([3a23db1](https://github.com/ysoft-org/ysoft-admin/commit/3a23db1a4bdfefc26de67ed9e0317097699d5db6)) +- 移动日志配置和依赖至 webapi 模块 ([48aae87](https://github.com/ysoft-org/ysoft-admin/commit/48aae877646e93f20a43fe4002d7f19fa98897c3)) +- 调整部分 Query 查询参数类型为对应枚举(目前已支持非 JSON 格式枚举参数转换) ([f80316e](https://github.com/ysoft-org/ysoft-admin/commit/f80316e34d9757225a1a7b6002061e8626018e47)) +- 调整部分枚举类的包位置 ([6b69dd4](https://github.com/ysoft-org/ysoft-admin/commit/6b69dd43e1544bd955901d6110fa7d7f65aaa80c)) +- 更新通知公告新增、查看菜单数据 ([4554526](https://github.com/ysoft-org/ysoft-admin/commit/45545260a36b57b594eb8329e95b7552cf6893f5)) + +### 🐛 问题修复 + +- 修复代码生成前端模板部分错误 (Gitee#20) ([b512ea9](https://github.com/ysoft-org/ysoft-admin/commit/b512ea99f39aaac04bd4db4a9d73e29ddb340d9e)) +- 修复文件管理删除图片时未删除缩略图的问题 ([bc523eb](https://github.com/ysoft-org/ysoft-admin/commit/bc523eba30a500a4af62adbd590446c48a5cb0be)) +- 修复存储管理私有密钥校验错误 ([eb65cff](https://github.com/ysoft-org/ysoft-admin/commit/eb65cff4c776a8d3e259f8c96d2918acfe038b6a)) +- 删除用户未删除用户历史密码 ([f53d6b6](https://github.com/ysoft-org/ysoft-admin/commit/f53d6b6504d5d504581e2697589dcb9b8fbe82ef)) +- 修复菜单缓存更新错误 ([10ff4ce](https://github.com/ysoft-org/ysoft-admin/commit/10ff4ce838b950df42994de4dbd2af20ff254949)) +- 修复偶发性报错 zip file closed ([b587cb8](https://github.com/ysoft-org/ysoft-admin/commit/b587cb82aa5d48548b5ce75dd4863af037ae8274)) +- 修复代码生成器前端新增数据模板错误 ([81de8d0](https://github.com/ysoft-org/ysoft-admin/commit/81de8d060ba081d14873d14d6e4083302a718bef)) + +### 📦 依赖升级 + +- ContiNew Starter 2.1.0 => 2.4.0 (更多依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/ysoft-org/ysoft-starter/blob/dev/CHANGELOG.md)) + +## [v3.1.0](https://github.com/ysoft-org/ysoft-admin/compare/v3.0.1...v3.1.0) (2024-06-16) + +### ✨ 新特性 + +* 系统配置新增安全设置功能,支持多种密码策略配置,例如:有效期、密码重复使用次数、密码错误锁定等 (GitHub#61) ([1de2a8f](https://github.com/ysoft-org/ysoft-admin/commit/1de2a8f2dcf01ef8eeb7e2d662c09af00b38ffb1)) ([90ecaab](https://github.com/ysoft-org/ysoft-admin/commit/90ecaab63241b8d351ebaffa4faece8c609882b1)) ([3994142](https://github.com/ysoft-org/ysoft-admin/commit/3994142ace6cd6ce9d094b8a4ceed080a2b2ec33)) ([1427c13](https://github.com/ysoft-org/ysoft-admin/commit/1427c13b7a0b4e0f569ccc20a3172ad748c98e14)) ([5f5fee6](https://github.com/ysoft-org/ysoft-admin/commit/5f5fee63f8e584ffd9542bf132ee724762187d83)) ([c1e9d31](https://github.com/ysoft-org/ysoft-admin/commit/c1e9d318e0643c0854a848425955948d05b158d3)) ([48d0f47](https://github.com/ysoft-org/ysoft-admin/commit/48d0f476149ff7bc30fe05fe3dec84e5a947cf19)) +* 图片文件支持缩略图 (GitHub#63) ([d320c95](https://github.com/ysoft-org/ysoft-admin/commit/d320c9596a321dce1028ac36d23bcd04535e87a6)) ([d44fb3a](https://github.com/ysoft-org/ysoft-admin/commit/d44fb3a681370b5763f028cc761fbb5fc94a104c)) +* 在线用户增加最后活跃时间显示 ([926497a](https://github.com/ysoft-org/ysoft-admin/commit/926497a18acbb6ef170de9f68bd97e025e212f3e)) +* 新增 WebSocket 消息通知,站内信重新上线 (GitHub#67) ([9970c46](https://github.com/ysoft-org/ysoft-admin/commit/9970c461cce5c710f1f8479d4bdd258e65558256)) ([94168e2](https://github.com/ysoft-org/ysoft-admin/commit/94168e246f75364605b6fbdb82c9438e2b959c61)) ([5abdb8d](https://github.com/ysoft-org/ysoft-admin/commit/5abdb8d86161e7bd03ace14b3c899f6ad13020e2)) +* 文件上传按日期拆分目录 (GitHub#68) ([08aa085](https://github.com/ysoft-org/ysoft-admin/commit/08aa08550589876a821a37d56a5fae8867292978)) +* 代码生成增加了 TREE_SELECT/CHECK_GROUP/INPUT_NUMBER/INPUT_PASSWORD控件 (Gitee#17) ([8632b22](https://github.com/ysoft-org/ysoft-admin/commit/8632b22bd5b6d43e31b6aeac930836464849441b)) ([cf18c10](https://github.com/ysoft-org/ysoft-admin/commit/cf18c1046b77c9f38d28b0e6608d345df3bbd5a9)) +* 系统参数新增根据类别查询方法 ([694cbb2](https://github.com/ysoft-org/ysoft-admin/commit/694cbb2850c05f7eb35fb982530a6c204f1f64b0)) +* 支持动态邮件 ([1dbb339](https://github.com/ysoft-org/ysoft-admin/commit/1dbb33935a05e9fde749191e34682e632c8e1e63)) + +### 💎 功能优化 + +- 优化部分命名 ([a3cf39f](https://github.com/ysoft-org/ysoft-admin/commit/a3cf39f9f8611f7f34dd9f55166d0fe75b72145b)) +- 优化代码生成预览 (Gitee#14) ([ad7412f](https://github.com/ysoft-org/ysoft-admin/commit/ad7412f9cbee5d851d060d8e9d717b2553d3d4cb)) +- 优化个人中心部分参数命名 ([61dd3a4](https://github.com/ysoft-org/ysoft-admin/commit/61dd3a4c3aef2d4a873a1d4de13bc1962179eeb1)) +- 根据前端最新 ESLint 配置优化代码生成模板 ([044b4b6](https://github.com/ysoft-org/ysoft-admin/commit/044b4b669aa2b77b3f5ac27095202e57045fd10d)) +- 优化代码生成模板 ([3ddcdf0](https://github.com/ysoft-org/ysoft-admin/commit/3ddcdf0f67d12250da4b7f6f6ff3d63a880e6d4c)) ([6396e9a](https://github.com/ysoft-org/ysoft-admin/commit/6396e9a7364bf899023c2399083b43c67efb30cf)) ([2fb4001](https://github.com/ysoft-org/ysoft-admin/commit/2fb40015c1ca2bc58b66891e2cfbfac70b029016)) +- 使用 Crane4j 优化在线用户数据填充 ([cb81135](https://github.com/ysoft-org/ysoft-admin/commit/cb811350f36364fcc85355c081b8b60c7d5bfb2a)) +- 用户角色名称调整为角色名称列表返回,并全局优化 Crane4j 组件的使用方式 ([857a1c9](https://github.com/ysoft-org/ysoft-admin/commit/857a1c90838c8305f916af9dccfd4ca847ca8c66)) ([0b76d5c](https://github.com/ysoft-org/ysoft-admin/commit/0b76d5ca33f5900862d48321e8384ee8ded6ae4e)) +- 优化部分方法排序 ([651cc8a](https://github.com/ysoft-org/ysoft-admin/commit/651cc8ae71bd3f544bd41adac0ef7044011300a7)) +- 字典管理分页查询接口 => 查询列表接口 ([b13d0e9](https://github.com/ysoft-org/ysoft-admin/commit/b13d0e9ee530fc1a8b382e08ead065989f2b0e7f)) +- 移除部门响应信息中的 getDisabled 方法 ([659144a](https://github.com/ysoft-org/ysoft-admin/commit/659144afdaf5d1b4667437053a770cf39681cdd7)) +- 文件管理存储路径改为相对路径 (GitHub#69) ([8854f20](https://github.com/ysoft-org/ysoft-admin/commit/8854f20ce90f6a1c379ac81a27bb40784a838095)) +- 查询文件列表增加存储名称信息返回 ([69bc1e5](https://github.com/ysoft-org/ysoft-admin/commit/69bc1e52e122c14a695d2e37589d36cbb64de8a4)) +- 系统参数表结构新增ID、类别字段 ([45396f2](https://github.com/ysoft-org/ysoft-admin/commit/45396f2dc23b4de47912c1d77b05711c839672ce)) +- 优化公告状态判断 ([a07aedb](https://github.com/ysoft-org/ysoft-admin/commit/a07aedbf35e32f3d09d2e9ed8857b9a9d2e2e13a)) +- 重构系统参数相关接口 ([6d0060b](https://github.com/ysoft-org/ysoft-admin/commit/6d0060b21c374afd9880e57490345a140187a7cd)) +- 优化用户及部门查询 ([448f9a0](https://github.com/ysoft-org/ysoft-admin/commit/448f9a0a819d6816a5ae3ada2e07690a5f70f7df)) +- 用户头像改为Base64存储 ([969216d](https://github.com/ysoft-org/ysoft-admin/commit/969216d7c67332eae826c11233c8745d9d3ad81c)) ([513ea83](https://github.com/ysoft-org/ysoft-admin/commit/513ea83152708194348afeb93d31aed1a9914e57)) ([7a6cafc](https://github.com/ysoft-org/ysoft-admin/commit/7a6cafc6e4ff6914fa62a870538b51759af866a8)) +- 优化配置文件 ([5b3d4f5](https://github.com/ysoft-org/ysoft-admin/commit/5b3d4f57788e30b62a1e1af9c2620a4cc8659bfe)) +- 优化登录 Helper ([afbd619](https://github.com/ysoft-org/ysoft-admin/commit/afbd619d098ff3d70fc786db9594cebf03c07e10)) +- 重构查询参数及字典接口 ([1d60213](https://github.com/ysoft-org/ysoft-admin/commit/1d602134377fc13b062981476b8c17947b36006d)) +- 重构查询角色字典接口 ([1e73d06](https://github.com/ysoft-org/ysoft-admin/commit/1e73d06a972d380bbed253eaffd806d8e0698525)) +- 使用 CompletableFuture 实现异步加载用户权限、角色代码和角色信息,以提高登录时的性能和响应速度 ([d5f3c74](https://github.com/charles7c/ysoft-admin/commit/d5f3c7417ad7b1178b5a53da3fd6f3cb7cd3b19a)) + + +### 🐛 问题修复 + +- 补充查询文件资源统计权限校验注解 ([60cbf04](https://github.com/ysoft-org/ysoft-admin/commit/60cbf0402a350d05a09abec57ba94c7758b46499)) +- Postgresql startup script fixes (GitHub#60) ([8caad16](https://github.com/ysoft-org/ysoft-admin/commit/8caad16ef226e473a81f79b82485c3d03ded7a42)) +- 修复初始菜单数据错误 ([f062797](https://github.com/ysoft-org/ysoft-admin/commit/f062797629bd7fc220ceae1e44859146ef4a14ff)) +- 字典编码、存储编码及类型、菜单类型不允许修改 ([79d0101](https://github.com/ysoft-org/ysoft-admin/commit/79d0101e5eb1baa86979b6a6d3c584a2a483320f)) +- 修复行为验证码接口请求次数限制 ([573e634](https://github.com/ysoft-org/ysoft-admin/commit/573e634b433c473551244418e695a34bbd3fa675)) +- 修复导出用户报错 ([655a695](https://github.com/ysoft-org/ysoft-admin/commit/655a695753d12db624fbf2afaf10d38f67241e31)) +- 移除部门名称错误正则 ([0285874](https://github.com/ysoft-org/ysoft-admin/commit/0285874540c0cadc8aae74077f6368b6e7977c35)) +- 修复插入第三方登录用户时报错 ([0cfc7a5](https://github.com/ysoft-org/ysoft-admin/commit/0cfc7a5c80c3558028c7225fc459ecb07149ab0d)) +- 修复更新手机号、邮箱未加密的问题 ([485d708](https://github.com/ysoft-org/ysoft-admin/commit/485d708cd45df922ec8e601b7bd7344e9ebd9299)) ([e6d7205](https://github.com/ysoft-org/ysoft-admin/commit/e6d720571d3273f27351e81a19fdd97b9b4336f6)) + +### 📦 依赖升级 + +- ContiNew Starter 2.0.0 => 2.1.0 (更多依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/ysoft-org/ysoft-starter/blob/dev/CHANGELOG.md)) + +## [v3.0.1](https://github.com/ysoft-org/ysoft-admin/compare/v3.0.0...v3.0.1) (2024-05-03) + +### ✨ 新特性 + +* 新增验证码超时显示效果,超时后显示已过期请刷新 (GitHub#56) ([4c6a7fb](https://github.com/ysoft-org/ysoft-admin/commit/4c6a7fb91ad195b86d776f8aef6aef81d07b2eb1)) +* 文件管理增加资源统计,统计总存储量、各类型文件存储占用 (GitHub#58) ([15c966f](https://github.com/ysoft-org/ysoft-admin/commit/15c966f7bb255db3edea249f8d3354324cbdbf5b)) + +### 💎 功能优化 + +- 获取图片验证码 URL /img => /image ([9a1a472](https://github.com/ysoft-org/ysoft-admin/commit/9a1a472ec996362cb918e79b9ce37bfa2639a10b)) +- 移除对部分 API 重复的权限校验 ([53eaef9](https://github.com/ysoft-org/ysoft-admin/commit/53eaef9fbdfd6d0866a3d5e424d783e2e7bc0e17)) +- 优化代码生成模板 ([dc92731](https://github.com/ysoft-org/ysoft-admin/commit/dc9273132dc8e266f2d44c834b9c2733256afdfe)) ([def831f](https://github.com/ysoft-org/ysoft-admin/commit/def831f2dca0703f5ef8b84b0e695a32b171461d)) + + +### 🐛 问题修复 + +- 修复查询用户邮箱、手机号时未自动加密导致的错误 ([faa56d1](https://github.com/ysoft-org/ysoft-admin/commit/faa56d16b92cbdb8f7e16c8b43c2916ae692d881)) +- 修复根据部门查询用户列表数据错误 ([42ac82e](https://github.com/ysoft-org/ysoft-admin/commit/42ac82e7ceef9336741c2514470c0db36ab7075e)) +- 修复文件类型处理错误 ([9b60e24](https://github.com/ysoft-org/ysoft-admin/commit/9b60e24364bfb4cc7cd9996a43579a062197cdf3)) + +## [v3.0.0](https://github.com/ysoft-org/ysoft-admin/compare/v2.5.0...v3.0.0) (2024-04-27) + +### ✨ 新特性 + +* 系统日志新增导出 API ([bd0f40c](https://github.com/ysoft-org/ysoft-admin/commit/bd0f40c6ad397174baf80b04923ef1e94ff28e3c)) +* 适配 3.0 前端菜单,并梳理菜单数据 +* 适配 3.0 前端代码生成模板,代码预览及生成 ([3dbe72f](https://github.com/ysoft-org/ysoft-admin/commit/3dbe72fd570c44b32599d869abd30331137a6c7d)) + +### 💎 功能优化 + +- 重构日志管理相关接口 ([7793f82](https://github.com/ysoft-org/ysoft-admin/commit/7793f82009bcdb5fcdfe5e91daab211ab1705bf7)) +- 优化部门管理相关 API,合并 DeptResp 及 DeptDetailResp ([a2cf072](https://github.com/ysoft-org/ysoft-admin/commit/a2cf072609ac33543605ecbb5f8e498237bc3d91)) +- 优化存储管理相关 API,合并 StorageResp 及 StorageDetailResp ([f7b5a4f](https://github.com/ysoft-org/ysoft-admin/commit/f7b5a4ff8dd93f444c00d103b0609ae81e0dd70c)) +- 优化字典管理相关 API ([9ec5945](https://github.com/ysoft-org/ysoft-admin/commit/9ec594509f2d4b31f46e3aca66d65d139dc8b94f)) +- 移除部门、角色、菜单、用户、存储的状态默认值 ([bd5ede2](https://github.com/ysoft-org/ysoft-admin/commit/bd5ede2e2956057376b930ecfed88ca44437cbc1)) +- 代码生成新增 MySQL json 数据类型映射 ([fe57350](https://github.com/ysoft-org/ysoft-admin/commit/fe5735090d94f5d900d79142e5e42ba2db9c0249)) +- 优化角色管理相关 API,角色编码不允许修改 ([df59cee](https://github.com/ysoft-org/ysoft-admin/commit/df59cee98565f9f45b04d16533888be39c3d7a6f)) +- 优化用户管理相关 API ([5269608](https://github.com/ysoft-org/ysoft-admin/commit/5269608c61b1f5a6a9f61cd45b349f28db714232)) +- 文件管理查询 API 调整为分页查询 ([f8bea90](https://github.com/ysoft-org/ysoft-admin/commit/f8bea901938aec0f0ac21c63179c5bde2a0965a7)) +- 移除 Qodana 扫描 ([d88581f](https://github.com/ysoft-org/ysoft-admin/commit/d88581f939afb0caa3589c8ee76c9b296bb9997e)) +- 移除菜单导出接口 ([4363c91](https://github.com/ysoft-org/ysoft-admin/commit/4363c91872e6d83e139b22c95a0d7e83183d8f69)) +- 优化系统日志、在线用户、存储管理、部门管理相关代码 ([a2e4f9a](https://github.com/ysoft-org/ysoft-admin/commit/a2e4f9a28b744e269c46dc66c60311bb939021a7)) +- 优化查询参数字典 API 地址 ([79a3de8](https://github.com/ysoft-org/ysoft-admin/commit/79a3de8971c613277bdcea79463b6f06959e7b85)) +- 移除角色状态字段 ([e89ba7d](https://github.com/ysoft-org/ysoft-admin/commit/e89ba7d5cd793e20c3562c7bd1e4655ed1e5a2a3)) + + +### 🐛 问题修复 + +- 使用字典时,仅查询启用状态字典 ([17c795f](https://github.com/ysoft-org/ysoft-admin/commit/17c795fedef5b6801f2053d97b9d78d067775ca1)) +- 获取 Authorization 请求头内容兼容小写请求头场景 ([e68c445](https://github.com/ysoft-org/ysoft-admin/commit/e68c4455a8af1b4d7a25cd63f9fc9e5aabb441ab)) +- 修复查询用户权限存在空值的问题 ([fce4a56](https://github.com/ysoft-org/ysoft-admin/commit/fce4a566d7204791650153f0a5507a5d05d2d6c3)) +- 存储管理 S3 存储功能修复 (GitHub#51) ([f71c4c2](https://github.com/ysoft-org/ysoft-admin/commit/f71c4c226ffd7c27f6726873be6af125affaf148)) +- 修复 sys_role_menu 表初始数据错误 ([70ed667](https://github.com/ysoft-org/ysoft-admin/commit/70ed667c16388093204eecd97e4914076c62d1ff)) +- 修复用户管理/角色管理编辑及状态变更问题 (GitHub#53) ([abf1e65](https://github.com/ysoft-org/ysoft-admin/commit/abf1e651e9782a6f7bf2a896018de17130038c57)) +- 修复Failed to submit a listener notification task. Event loop shut down? 问题,开发时表现为需要点击两次才能关闭程序 ([f5ab22e](https://github.com/ysoft-org/ysoft-admin/commit/f5ab22eedf594cee43592a2f29409ee9c33a88d3)) + +### 💥 破坏性变更 + +- 适配 ysoft-starter 2.0.0,top.charles7c.ysoft.starter => top.ysoft.starter ([f5ab22e](https://github.com/ysoft-org/ysoft-admin/commit/f5ab22eedf594cee43592a2f29409ee9c33a88d3)) +- 移除 monitor 模块 ([b6206a3](https://github.com/ysoft-org/ysoft-admin/commit/b6206a334671894306043f86ec07d7c045cd757d)) +- top.charles7c.ysoft.admin => top.ysoft.admin ([08eeabc](https://github.com/ysoft-org/ysoft-admin/commit/08eeabc47d58db3cfc861a3a527e52bf89f6183b)) +- 公告管理 Announcement => Notice ([dbe93df](https://github.com/ysoft-org/ysoft-admin/commit/dbe93df8bcec0b7dfb24fbd92f35928a3156f4e5)) + +### 📦 依赖升级 + +- ContiNew Starter 1.5.1 => 2.0.0 (更多依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/ysoft-org/ysoft-starter/blob/dev/CHANGELOG.md)) + +## [v2.5.0](https://github.com/ysoft-org/ysoft-admin/compare/v2.4.0...v2.5.0) (2024-03-23) + +### ✨ 新特性 + +* 新增 PostgreSQL 数据源配置示例 ([ee48c80](https://github.com/ysoft-org/ysoft-admin/commit/ee48c80cd10a4c4546d1cb24f1f4716bb2ac08ea)) +* 新增 PostgreSQL 部署脚本 ([3129e0a](https://github.com/ysoft-org/ysoft-admin/commit/3129e0a6dcbd809f0013fbf6c53ad029ae9f7a0e)) +* 新增 PostgreSQL 初始 SQL 脚本 ([33b8102](https://github.com/ysoft-org/ysoft-admin/commit/33b81029df0b51058b3525b4317b51a2351319dc)) +* 新增代码生成器插件模块(后续会改造为独立插件) ([87829d3](https://github.com/ysoft-org/ysoft-admin/commit/87829d3ce8ab5a35091800900f7d7708f15ed9c2)) +* 代码生成同步最新数据表结构支持同步排序 ([89546de](https://github.com/ysoft-org/ysoft-admin/commit/89546deced78f83daca7ac0ba2e7d3d8cd101d0c)) +* 新增代码批量生成功能 ([Gitee PR#12](https://gitee.com/ysoft/ysoft-admin/pulls/12)) ([040f137](https://github.com/ysoft-org/ysoft-admin/commit/040f137934130451700bc28aeabbced30970c5f6)) + +### 💎 功能优化 + +- 移除 ` 符号的使用,保持数据库无关性 ([d6b07bd](https://github.com/ysoft-org/ysoft-admin/commit/d6b07bd6d1b1f9077a7571702b58c5e9c782b446)) +- 优化字符串模板方法 API 使用 ([0f39384](https://github.com/ysoft-org/ysoft-admin/commit/0f393845a19432e7c965e811c96774694f4d2372)) +- 调整部分 SQL 语句,以兼容 PostgreSQL 数据库 ([9f5049b](https://github.com/ysoft-org/ysoft-admin/commit/9f5049bf26c557738867dfe833261d60d071d4a8)) ([bf60d48](https://github.com/ysoft-org/ysoft-admin/commit/bf60d48d3a53dd5d73a78e73b6b230e3271ec3de)) +- 新增插件仓库配置 ([0439252](https://github.com/ysoft-org/ysoft-admin/commit/04392524ac13c2096b549f99d0391fa1d375ca31)) +- 优化部分接口响应格式为 kv 格式 ([b40d872](https://github.com/ysoft-org/ysoft-admin/commit/b40d872bc4b8dd30ad952d639158619b43cef999)) +- 适配 Crane4j 条件注解 ([bf00747](https://github.com/ysoft-org/ysoft-admin/commit/bf007470b2362159309ff8231a2f0ad180cfc947)) +- 重构代码生成配置 ([7031a51](https://github.com/ysoft-org/ysoft-admin/commit/7031a51cd4d7072d4da841736678bb81b2123e9d)) +- 重构代码生成功能,由指定路径生成模式调整为下载模式,更方便复杂场景 ([df0c0dd](https://github.com/ysoft-org/ysoft-admin/commit/df0c0dd7dcf39620abaf21bd450620ec3fffcf37)) + + +### 🐛 问题修复 + +- 修复 MySQL 初始 SQL 脚本数据错误 ([49d6bd6](https://github.com/ysoft-org/ysoft-admin/commit/49d6bd6874b3df66fd2e2051ea273cb43cb7b4f6)) +- 修复参数缓存未及时过期的问题 ([976e9c4](https://github.com/ysoft-org/ysoft-admin/commit/976e9c43df5926c533723a75222c59fde05e122e)) +- 修复代码生成 text 类型数据的长度校验时,数值显示为 65,535 的问题 ([8026f66](https://github.com/ysoft-org/ysoft-admin/commit/8026f660c7af7bba6d4caaf31535a890e5b40a96)) + +### 💥 破坏性变更 + +- 调整 liquibase 目录结构,更适合开源类项目适配多种数据库脚本场景 ([1ca48a6](https://github.com/ysoft-org/ysoft-admin/commit/1ca48a6620cff62f3648cc28042843163589e150)) +- 适配 ContiNew Starter 日志及数据库工具的包结构优化 ([3405868](https://github.com/ysoft-org/ysoft-admin/commit/3405868c7f042beafb77a7407a388a40b9a75466)) +- 适配 ContiNew Starter Query 组件的包结构优化 ([6be1b6c](https://github.com/ysoft-org/ysoft-admin/commit/6be1b6cfb1e7fef4422b8c38e6073a435ebae5c2)) + +### 📦 依赖升级 + +- ContiNew Starter 1.4.0 => 1.5.1 (更多依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/ysoft-org/ysoft-starter/blob/dev/CHANGELOG.md)) + +## [v2.4.0](https://github.com/ysoft-org/ysoft-admin/compare/v2.3.0...v2.4.0) (2024-02-16) + +### ✨ 新特性 + +* 集成 TLog(轻量级的分布式日志标记追踪神器) ([Gitee PR#10](https://gitee.com/ysoft/ysoft-admin/pulls/10)) +* 系统日志新增 traceId 链路号记录,方便查看完整日志链路 ([860ca40](https://github.com/ysoft-org/ysoft-admin/commit/860ca403c2c32cc6395c1608217bc9b6e7c18bd8)) +* 取消用户默认密码,改为表单填写密码 ([3d77aa9](https://github.com/ysoft-org/ysoft-admin/commit/3d77aa91ee32065b53d9c47a57c33d6d7e4efb0e)) +* 适配 ContiNew Starter 加密模块(安全模块) ([6435175](https://github.com/ysoft-org/ysoft-admin/commit/6435175dc3d853cb170270e39e8f1505adffeae5)) ([43da462](https://github.com/ysoft-org/ysoft-admin/commit/43da462560e224ed92f239cb5af4db64dea51d18)) +* 适配 ContiNew Starter 脱敏模块(安全模块) ([2109789](https://github.com/ysoft-org/ysoft-admin/commit/2109789116d9ff18773d8afeb854d1dfc70b935a)) + +### 💎 功能优化 + +- 优化 API 文档分组配置 ([2df4cce](https://github.com/ysoft-org/ysoft-admin/commit/2df4cceedd35b1c2c07bcbf38b5a157604a752c2)) +- 优化 QueryTypeEnum 枚举值命名 ([9648cf6](https://github.com/ysoft-org/ysoft-admin/commit/9648cf64a4679657f0e609f980805d274563aa53)) +- 优化 Query 相关注解使用方式 ([15b1520](https://github.com/ysoft-org/ysoft-admin/commit/15b152008c6ae8ab89704d83a969dcfbbb8b5b88)) +- 新增 Qodana 扫描 ([f6a9581](https://github.com/ysoft-org/ysoft-admin/commit/f6a9581adef87a8915639e6cb2d7c4d02315ebd0)) +- 新增 SonarCloud 扫描 ([a154abd](https://github.com/ysoft-org/ysoft-admin/commit/a154abde8a39cfecc421c79e01998274b944d2c1)) ([c03c082](https://github.com/ysoft-org/ysoft-admin/commit/c03c082d2e2884962547633f5e98663088bd2c3b)) +- 移除 Lombok 私有构造注解使用 ([a2420d3](https://github.com/ysoft-org/ysoft-admin/commit/a2420d3f4b4652a1d9711f513b8fb22a56105141)) +- 获取不到当前登录用户信息则抛出未登录异常 ([d972a44](https://github.com/ysoft-org/ysoft-admin/commit/d972a4466a9e8a1a6e6375e4171a4790c2ba156e)) +- 优化代码,解决 [Sonar](https://sonarcloud.io/organizations/charles7c/projects)、[Codacy](https://app.codacy.com/gh/ysoft-org/ysoft-admin/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)、[Qodana](https://qodana.cloud/organizations/pQDPD/teams/p5jqd/) 扫描问题,点击各链接查看对应实时质量分析报告(Codacy 已达到 A) +- 优化部署配置 ([b5d668e](https://github.com/ysoft-org/ysoft-admin/commit/b5d668e014690d3f1a8a2bab0d0ad0039083e7bb)) +- 使用密码编码器重构密码加密、密码判断等相关处理 ([594f7fd](https://github.com/ysoft-org/ysoft-admin/commit/594f7fd042f1ff96a298f2e59ffdda112113cb51)) +- 优化 SaToken 及图形验证码配置 ([70973db](https://github.com/ysoft-org/ysoft-admin/commit/70973db71f2eed49c5878d69d8b93ff04b13a8b9)) +- 优化图形验证码使用及部分配置 ([a50d857](https://github.com/ysoft-org/ysoft-admin/commit/a50d857c41d164355d36ae5dfd14c6badbe06202)) + + +### 🐛 问题修复 + +- 修复 API 响应内容类型错误 ([439f7c7](https://github.com/ysoft-org/ysoft-admin/commit/439f7c7c58ee27ff56b5093df71bc902c46f48fa)) + +### 💥 破坏性变更 + +- 调整自增 ID 为分布式 ID ([4779887](https://github.com/ysoft-org/ysoft-admin/commit/4779887751bd3a696e4d31294057e8c03d66eaf3)) + +### 📦 依赖升级 + +- ContiNew Starter 1.2.0 => 1.4.0 (更多依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/ysoft-org/ysoft-starter/blob/dev/CHANGELOG.md)) + +## [v2.3.0](https://github.com/ysoft-org/ysoft-admin/compare/v2.2.0...v2.3.0) (2024-01-21) + +### ✨ 新特性 + +* 代码生成 Request 实体时,针对字符串类型增加数据长度校验注解 ([ee82558](https://github.com/ysoft-org/ysoft-admin/commit/ee8255876f618137f811e14ee305509e4e6466d0)) +* 适配 Crane4j 数据填充组件,优化部分数据填充处理 ([d598408](https://github.com/ysoft-org/ysoft-admin/commit/d5984087a306de31690e9a81d951bd831434a0c9)) ([a2411f7](https://github.com/ysoft-org/ysoft-admin/commit/a2411f728a811910a0918668c0811e7df0345640)) ([7a3ccc2](https://github.com/ysoft-org/ysoft-admin/commit/7a3ccc2dee8f7b938a91df52fd4903ce09e662e5)) +* 移除 Spring Cache,适配 JetCache ([d4bb39d](https://github.com/ysoft-org/ysoft-admin/commit/d4bb39d9b4483b7d7fed76b5f2b997538d86d719)) ([1b7aa9d](https://github.com/ysoft-org/ysoft-admin/commit/1b7aa9db0c56af733f63f602988dba9e225fe445)) ([8596e47](https://github.com/ysoft-org/ysoft-admin/commit/8596e47ed62e19083d6007448c5369d72fa4f2b6)) + +### 💎 功能优化 + +- 优化本地存储库注册 ([918e897](https://github.com/ysoft-org/ysoft-admin/commit/918e897838628b24a160c3a8f3b3dea1eefd1883)) +- 增加华为云镜像源仓库配置 ([16ee2b4](https://github.com/ysoft-org/ysoft-admin/commit/16ee2b4b6fe2a663830972ed99d4e80ddf5a3593)) +- 优化部分字段名称 ([e3e958b](https://github.com/ysoft-org/ysoft-admin/commit/e3e958b419e1ea23fe146b255fce749050302f63)) +- 调整代码生成前端 Vue 页面模板 ([7c34574](https://github.com/ysoft-org/ysoft-admin/commit/7c345745aadc6272e5f1db674c757ff0f9cea604)) +- 更新格式配置,优化全局代码格式 ([35e3123](https://github.com/ysoft-org/ysoft-admin/commit/35e31233c531b68b761c73fc0daf7444843b4059)) +- 优化配置文件格式 ([a8a4cad](https://github.com/ysoft-org/ysoft-admin/commit/a8a4cad840b6d32fbb8d3df24b193f9e7c826d22)) +- 使用钩子方法优化部分增、删、改处理 ([61c5724](https://github.com/ysoft-org/ysoft-admin/commit/61c57242fa481d2668b1d1ac4ff4802c47fd07bc)) +- 完善 flatten Maven 插件配置,以覆盖更多使用情况 ([657accd](https://github.com/ysoft-org/ysoft-admin/commit/657accd8a595ab0c2e9ff4d00e49c569eae03123)) +- 移除部分无用 Maven 配置 ([5db1f66](https://github.com/ysoft-org/ysoft-admin/commit/5db1f669e0bc5022bcd2164757a0f82dfe8d6c30)) +- 优化日志配置,滚动策略调整为基于日志文件大小和时间滚动 ([2fa8c25](https://github.com/ysoft-org/ysoft-admin/commit/2fa8c254fc53cda3d33c56931569822e645dd902)) + +### 🐛 问题修复 + +- 完善代码生成前端路径配置校验 ([bee04d5](https://github.com/ysoft-org/ysoft-admin/commit/bee04d5f363b6de88df5249b0fba85607978b303)) + +### 💥 破坏性变更 + +- 根据发展需要,拆分前端项目 ysoft-admin-ui 到独立仓库 ([4067eb9](https://github.com/ysoft-org/ysoft-admin/commit/4067eb97bf344dec6ae718433b57bdb7d0b8d6cd)) +- PageDataResp => PageResp ([d8c946e](https://github.com/ysoft-org/ysoft-admin/commit/d8c946e8014d205c4fd3f38d1f04b3225faede7a)) +- 适配 ContiNew Starter IService 接口,CRUD 查询详情方法不再检查是否存在 ([47a133a](https://github.com/ysoft-org/ysoft-admin/commit/47a133a065b5c858b588bf77ad51bb9fc38d1222)) +- 适配 ContiNew Starter CRUD 模块注解 ([7fa70e7](https://github.com/ysoft-org/ysoft-admin/commit/7fa70e74070c7c0f487baa5098f85d7dfb808106)) +- 调整部分类的所在包 ([8dc42c7](https://github.com/ysoft-org/ysoft-admin/commit/8dc42c7a21e7422399b49690b28899df299e20c7)) ([6efe1ad](https://github.com/ysoft-org/ysoft-admin/commit/6efe1ad6f416c52130b2380a699129e7dae29499)) + +### 📦 依赖升级 + +- ContiNew Starter 1.1.0 => 1.2.0 (更多依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/ysoft-org/ysoft-starter/blob/dev/CHANGELOG.md)) + +## [v2.2.0](https://github.com/ysoft-org/ysoft-admin/compare/v2.1.0...v2.2.0) (2023-12-31) + +### ✨ 新特性 + +* 发送短信验证码新增限流处理 ([e719d20](https://github.com/ysoft-org/ysoft-admin/commit/e719d207fb76c82b584f2e1ac7210061dc71a89a)) +* 代码生成新增生成预览功能 ([4017029](https://github.com/ysoft-org/ysoft-admin/commit/401702972f30c4e556a2cf8d048f78fa9ee1c5ba)) ([505ba49](https://github.com/ysoft-org/ysoft-admin/commit/505ba49a5304fb3e2ba655dea901cd5e3ea74673)) +* 适配 ContiNew Starter 行为验证码,系统内所有短信发送新增前置行为验证码验证 ([Gitee PR#9](https://gitee.com/ysoft/ysoft-admin/pulls/9)) +* 文件管理:提供文件上传、下载、预览(目前支持图片、音视频)、重命名、切换视图(列表、网格)等功能 +* 存储库管理:提供文件存储库新增、编辑、删除、导出等功能 + +### 💎 功能优化 + +- 优化 API 文档配置 ([108f1c4](https://github.com/ysoft-org/ysoft-admin/commit/108f1c4ae7b855ac0bab2d3fe028270472a8be71)) +- 调整枚举配置值为大写 ([3ece42b](https://github.com/ysoft-org/ysoft-admin/commit/3ece42b94e071ece87e6b4616f7817bf851ba28f)) +- 优化由于 Mock 引起的导出报错提示 ([349899b](https://github.com/ysoft-org/ysoft-admin/commit/349899b4fc9572450ca31d9a5e19268ce0b868a8)) +- 优化查询访客地域分布信息接口 SQL ([4df887d](https://github.com/ysoft-org/ysoft-admin/commit/4df887d82678ced0d30aa0c7a6f92edcac902052)) +- 调整后端部分方法名 save => add ([45bd3e1](https://github.com/ysoft-org/ysoft-admin/commit/45bd3e10b6ac6aecde41ff9484668e557a485b27)) +- 优化系统日志详情 ([55effa3](https://github.com/ysoft-org/ysoft-admin/commit/55effa36580a57ddedb688e2ce30bec45c761224)) ([99997c1](https://github.com/ysoft-org/ysoft-admin/commit/99997c160eefc152a6f4e74bcd9c5ef6fc77a9c5)) +- 移除部分方法中仅有单个非读操作的事务处理 ([b85d692](https://github.com/ysoft-org/ysoft-admin/commit/b85d69298de1a6c48d15300bb9ff1b3ea569fdbd)) +- 优化编译配置 ([ed8bb57](https://github.com/ysoft-org/ysoft-admin/commit/ed8bb57fe24dfbe8f45b8f53370ebb79f1511268)) +- 优化配置文件格式 ([3399bc8](https://github.com/ysoft-org/ysoft-admin/commit/3399bc8dde0c8c8ac6d3e583ffbe299f7e6dd80b)) + +### 🐛 问题修复 + +- 修复代码生成相关错误 ([3fdc50d](https://github.com/ysoft-org/ysoft-admin/commit/3fdc50d78ec50a878cec2b35c7d5028e741c42d7)) +- 更新仪表盘帮助文档部分过期链接 ([ac42836](https://github.com/ysoft-org/ysoft-admin/commit/ac4283679a847ed372db28aae1ea05fd791651b8)) + +### 💥 破坏性变更 + +- 适配 ContiNew Starter QueryTypeEnum 命名变更 ([97c273f](https://github.com/ysoft-org/ysoft-admin/commit/97c273f99ecb038e041e3d39dbfacf326d49cc1b)) +- 适配 ContiNew Starter Log HttpTracePro(日志模块) ([9bf0150](https://github.com/ysoft-org/ysoft-admin/commit/9bf015059b96f41c29f05ecbf7612d611b3a98c3)) +- 适配 ContiNew Starter 全局异常处理器 ([4ed4ddd](https://github.com/ysoft-org/ysoft-admin/commit/4ed4ddd4f055cefe1f85482bd6b9ef760978691b)) +- 适配 ContiNew Starter 数据权限解决方案(数据访问模块-MyBatis Plus) ([0849426](https://github.com/ysoft-org/ysoft-admin/commit/084942630ab0e1846c1836b8dc4bf5b2c9a5b16e)) +- 调整 IBaseEnum 所属包 ([e6c6e1c](https://github.com/ysoft-org/ysoft-admin/commit/e6c6e1cb0e326c5f531ca5cb2e17a1e26efac7d9)) +- 重构原有文件上传接口并优化配置文件配置格式 ([5e37025](https://github.com/ysoft-org/ysoft-admin/commit/5e370254dd00deaab62438c5feb4de14192ad7e6)) + +### 📦 依赖升级 + +- ContiNew Starter 1.0.0 => 1.1.0 ([fc80921](https://github.com/ysoft-org/ysoft-admin/commit/fc80921c047862b424ca625317f4657667bc2c6b)) (更多依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/ysoft-org/ysoft-starter/blob/dev/CHANGELOG.md)) +- Arco Design Vue 2.53.0 => 2.53.3 ([2720275](https://github.com/ysoft-org/ysoft-admin/commit/2720275b97334545dde71d548173bfcda7e660cb)) +- Vite 4.5.0 => 4.5.1 ([2720275](https://github.com/ysoft-org/ysoft-admin/commit/2720275b97334545dde71d548173bfcda7e660cb)) +- TypeScript 5.2.2 => 5.3.3 ([2720275](https://github.com/ysoft-org/ysoft-admin/commit/2720275b97334545dde71d548173bfcda7e660cb)) +- unplugin-vue-components 0.25.2 => 0.26.0 ([2720275](https://github.com/ysoft-org/ysoft-admin/commit/2720275b97334545dde71d548173bfcda7e660cb)) +- @kangc/v-md-editor 2.3.17 => 2.3.18 ([2720275](https://github.com/ysoft-org/ysoft-admin/commit/2720275b97334545dde71d548173bfcda7e660cb)) +- eslint 8.53.0 => 8.56.0 ([2720275](https://github.com/ysoft-org/ysoft-admin/commit/2720275b97334545dde71d548173bfcda7e660cb)) +- @vueuse/core 10.5.0 => 10.7.0 ([2720275](https://github.com/ysoft-org/ysoft-admin/commit/2720275b97334545dde71d548173bfcda7e660cb)) +- vue-i18n 9.6.5 => 9.8.0 ([2720275](https://github.com/ysoft-org/ysoft-admin/commit/2720275b97334545dde71d548173bfcda7e660cb)) +- vue-json-pretty 2.2.4 => 2.3.0 ([2720275](https://github.com/ysoft-org/ysoft-admin/commit/2720275b97334545dde71d548173bfcda7e660cb)) +- 由于篇幅限制,仅列出部分前端依赖升级情况,更多请查看 [提交记录](https://github.com/ysoft-org/ysoft-admin/commit/2720275b97334545dde71d548173bfcda7e660cb) + +## [v2.1.0](https://github.com/ysoft-org/ysoft-admin/compare/v2.0.0...v2.1.0) (2023-12-03) + +### 💎 功能优化 + +- 优化数据权限注解 ([bb59a78](https://github.com/ysoft-org/ysoft-admin/commit/bb59a78573bec521e8852f1c88ce6078fb14b14e)) +- 回退全局响应结果处理器 ([c7a4e32](https://github.com/ysoft-org/ysoft-admin/commit/c7a4e329945d8368a9b93a2488c059cf3333feba)) +- 优化字典 Controller CRUD 注解使用 ([8c1c4b0](https://github.com/ysoft-org/ysoft-admin/commit/8c1c4b014463d073e848e2f2abc33e089efa2abb)) +- 优化常量命名风格,XxxConsts => XxxConstants ([ec28705](https://github.com/ysoft-org/ysoft-admin/commit/ec28705b6ff6dd26ec3ef673fb3827259f1b9c41)) +- 移除 XML 文件头部的协议信息 ([b476956](https://github.com/ysoft-org/ysoft-admin/commit/b47695603afb0c19679c4100c1e3c23bc8007238)) +- 优化菜单标题校验 ([3dd81a1](https://github.com/ysoft-org/ysoft-admin/commit/3dd81a1192c4e340dad0b1bae5e29d1d7218fb25)) + +### 🐛 问题修复 + +- 修复 mock 被错误关闭的问题 ([a34070f](https://github.com/ysoft-org/ysoft-admin/commit/a34070ffed3044ad2bea604701b074665e7b4e42)) +- 修复保存生成配置校验失效的问题,并优化部分提示效果 ([c34e934](https://github.com/ysoft-org/ysoft-admin/commit/c34e934bb553d7f814d0fb5aa87eac0f565289b4)) + +### 💥 破坏性变更 + +- 项目包结构 top.charles7c.cnadmin => top.charles7c.ysoft.admin ([b86fe32](https://github.com/ysoft-org/ysoft-admin/commit/b86fe329d07317fed6a7d0b7015856de4b9e75d1)) +- 适配 ContiNew Starter 全局错误处理配置 ([b62095d](https://github.com/ysoft-org/ysoft-admin/commit/b62095d66e2318d35e4af07b128203b5a5e016f7)) +- 适配 ContiNew Starter CRUD(扩展模块) ([ce5a2ec](https://github.com/ysoft-org/ysoft-admin/commit/ce5a2ec9319b86e69a6bda67a886e1c96079ffc2)) +- 适配 ContiNew Starter Mail(消息模块) ([ce785dd](https://github.com/ysoft-org/ysoft-admin/commit/ce785ddce28733eeefbf970ed08b01e36e0abd4b)) +- 适配 ContiNew Starter Excel(文件处理模块) ([1311ae3](https://github.com/ysoft-org/ysoft-admin/commit/1311ae3603a26dc44dfffc5be86ea1ab81ff7958)) +- 适配 ContiNew Starter 认证模块-JustAuth ([7ad8d17](https://github.com/ysoft-org/ysoft-admin/commit/7ad8d1773a8e50e37a326b8f73f9ba38a3a7ff3a)) ([f28fbd1](https://github.com/ysoft-org/ysoft-admin/commit/f28fbd14fa83a82df49b07f16070e0ff3385b0ec)) +- 适配 ContiNew Starter 认证模块-SaToken ([86ca8f0](https://github.com/ysoft-org/ysoft-admin/commit/86ca8f094ff6d1c00b52c1406985bc00105b297f)) +- 适配 ContiNew Starter 图形验证码 ([8a11a02](https://github.com/ysoft-org/ysoft-admin/commit/8a11a020e04e271da7b700b5d73cf8475b50ee5c)) +- 适配 ContiNew Starter MyBatis Plus 自动配置 ([7306cd9](https://github.com/ysoft-org/ysoft-admin/commit/7306cd9d2f9492aa39e11b8f0dc8c7c11b534a3f)) +- 适配 ContiNew Starter Redisson 自动配置 ([a40e609](https://github.com/ysoft-org/ysoft-admin/commit/a40e609ea14acda840d2771f05ca9690d41236a1)) +- 适配 ContiNew Starter Jackson、API 文档(Knife4j:Spring Doc)自动配置 ([a86f3a5](https://github.com/ysoft-org/ysoft-admin/commit/a86f3a5047eda2f67cc9ad7721006d2db1fd710f)) +- 适配 ContiNew Starter 线程池自动配置 ([ec1daaf](https://github.com/ysoft-org/ysoft-admin/commit/ec1daaf0456296dbc3704ae700b9577001bdd5bb)) +- 引入 ContiNew Starter,适配跨域自动配置 ([2c4f511](https://github.com/ysoft-org/ysoft-admin/commit/2c4f5116c999b9316ab0bee4fa661338fea63c11)) +- 项目 group id top.charles7c => top.charles7c.ysoft ([3e23acb](https://github.com/ysoft-org/ysoft-admin/commit/3e23acb3e257d5813b858356aa926a96c906acf1)) + +## [v2.0.0](https://github.com/ysoft-org/ysoft-admin/compare/v1.3.1...v2.0.0) (2023-11-15) + +### 💎 功能优化 + +- 优化部分代码格式 ([2f87310](https://github.com/ysoft-org/ysoft-admin/commit/2f87310bc886af604a2667285a973ec6ae983430)) +- 优化 401 状态处理逻辑 ([c70e28a](https://github.com/ysoft-org/ysoft-admin/commit/c70e28a535c78214fe8d68a09824c786c457ef06)) +- 优化超时登录处理逻辑 ([d5da184](https://github.com/ysoft-org/ysoft-admin/commit/d5da1847e33e6cd7a0e5c3434335044167c1241c)) + +### 🐛 问题修复 + +- sms4j 3.0.3 => 3.0.4 ([23558d4](https://github.com/ysoft-org/ysoft-admin/commit/23558d45620a48ed82b32a5bdd2f948a4a37263d)) +- 发送消息增加事务处理 ([Gitee#7](https://gitee.com/ysoft/ysoft-admin/pulls/7)) ([1ca6f6c](https://github.com/ysoft-org/ysoft-admin/commit/1ca6f6c7e5f8a7c78f74df547f14517293241ac4)) +- 修复前端控制台 eslint 告警 ([Gitee#6](https://gitee.com/ysoft/ysoft-admin/pulls/6)) ([f4523d2](https://github.com/ysoft-org/ysoft-admin/commit/f4523d24817b4fee5c015eaba6b98fe99f350bba)) ([2304f28](https://github.com/ysoft-org/ysoft-admin/commit/2304f28a942fa8ea3e6d36fbebbe9346b0d3b741)) +- 修复仪表盘访问趋势区块 y 轴数值过大时无法展示的问题 ([fea6024](https://github.com/ysoft-org/ysoft-admin/commit/fea602439a3c9589bee078bfa9ff1e7efb378d71)) +- 修复控制台报错 Please use theme before using plugins ([98fbe05](https://github.com/ysoft-org/ysoft-admin/commit/98fbe0506c1cbe2f3c16347d9610ebfa5688b506)) +- 调整 Logback 配置,取消启动时打印 Logback 状态日志 ([1f7fef5](https://github.com/ysoft-org/ysoft-admin/commit/1f7fef5b31212e94652777be37bea4d4e02eb8c7)) + +### 💥 破坏性变更 + +- 优化部署相关脚本,mariadb => mysql ([5f4f0f1](https://github.com/ysoft-org/ysoft-admin/commit/5f4f0f1b21fe882dc51801d7c508c10b87d7af36)) +- 适配 Java 16 新特性 ([cf30443](https://github.com/ysoft-org/ysoft-admin/commit/cf3044312c8631a8c2b306e466e3d4d663d8eb6d)) +- 适配 Java 14 新特性 ([38f52aa](https://github.com/ysoft-org/ysoft-admin/commit/38f52aaafa22ebc958a22b7c38b084c655064fbc)) +- 适配 Java 11 新特性 ([5a5bd16](https://github.com/ysoft-org/ysoft-admin/commit/5a5bd1681e076ac6814d552da5415a8f154b93af)) +- 升级前端依赖 ([79fa2c8](https://github.com/ysoft-org/ysoft-admin/commit/79fa2c8abcf5f70f96ae7c6de35c47dbae76ee2d)) ([c44162d](https://github.com/ysoft-org/ysoft-admin/commit/c44162d431cb87cae251067fff9a5ae707aed9b3)) + - Arco Design Vue 2.52.0 => 2.53.0 + - Vue 3.3.4 => 3.3.7 + - Vite 3.2.7 => 4.5.0 + - vue-router 4.2.4 => 4.2.5 + - vue-i18n 9.5.0 => 9.6.5 + - vue-tsc 1.2.0 => 1.8.22 + - @vueuse/core 9.13.0 => 10.5.0 + - pinia 2.1.6 => 2.1.7 + - rollup 3.20.2 => 4.3.0 + - vue-cropper 1.0.9 => 1.1.1 + - crypto-js 4.1.1 => 4.2.0 + - vite-svg-loader 3.6.0 => 4.0.0 + - highlight.js 11.8.0 => 11.9.0 + - mitt 3.0.0 => 3.0.1 + - consola 2.15.3 => 3.2.3 + - prettier 2.8.7 => 3.0.3 + - less 4.1.3 => 4.2.0 + - eslint 8.48.0 => 8.53.0 + - stylelint 15.10.3 => 15.11.0 + - lint-staged 13.2.0 => 3.0.3 +- 升级后端依赖 ([dea160a](https://github.com/ysoft-org/ysoft-admin/commit/dea160a7b2d69e1b46edc936c9a697048bbb507a)) ([95c27ea](https://github.com/ysoft-org/ysoft-admin/commit/95c27ea323e015c915d352618158df830b4d1c05)) ([fa23287](https://github.com/ysoft-org/ysoft-admin/commit/fa232874aa88ab14fdc669e54a907e5ef05d2a7e)) ([8dbec9d](https://github.com/ysoft-org/ysoft-admin/commit/8dbec9d1a3bcb0f6d7ef4bbfb9715effd61b2025)) ([3bd56d8](https://github.com/ysoft-org/ysoft-admin/commit/3bd56d8a1ee274aac6d4ea57d61f6d470de0dc9c)) ([7b741d5](https://github.com/ysoft-org/ysoft-admin/commit/7b741d5f8c42d154c5b325326d0cc954fb566502)) + - Spring Boot 2.7.16 => 3.0.5 => 3.1.5 + - javax.* => jakarta.* + - ServletUtil => JakartaServletUtil(Hutool) + - 其他配置变更 + - JDK 8 => JDK 17 + - Sa-Token 1.36.0 => 1.37.0(适配 Spring Boot 3.x) + - MyBatis Plus 3.5.3.2 => 3.5.4(适配 Spring Boot 3.x) + - Dynamic Datasource 3.6.1 => 4.2.0(适配 Spring Boot 3.x) + - Redisson 3.20.1 => 3.24.3(适配 Spring Boot 3.x) + - Knife4j 适配 Spring Boot 3.x + - ip2region 2.7.15 => 3.1.5.1(适配 Spring Boot 3.x) + - spotless 2.30.0 => 2.40.0 + +## [v1.3.1](https://github.com/ysoft-org/ysoft-admin/compare/v1.3.0...v1.3.1) (2023-11-15) + +### 💎 功能优化 + +- 完善 Redis 部署配置 ([39969eb](https://github.com/ysoft-org/ysoft-admin/commit/39969ebf6173fc379dc3501e9204a344d1cf62cf)) +- 优化 401 状态处理逻辑 ([8820c1d](https://github.com/ysoft-org/ysoft-admin/commit/8820c1dfc858b9ef9df470e90dfe9ba4b1166e29)) +- 优化超时登录处理逻辑 ([712eedb](https://github.com/ysoft-org/ysoft-admin/commit/712eedba1be0ec371119745d4596cd35c2ce25d6)) +- 优化部分变量命名 ([f15494d](https://github.com/ysoft-org/ysoft-admin/commit/f15494d34823ded87efc396d98e2eb0108f74a3d)) + +### 🐛 问题修复 + +- sms4j 3.0.3 => 3.0.4 ([3fcdb54](https://github.com/ysoft-org/ysoft-admin/commit/3fcdb54442b380e76838478fa46e8dfb70a2759b)) +- 发送消息增加事务处理 ([5d159c6](https://github.com/ysoft-org/ysoft-admin/commit/5d159c6ab337a9432419d84cf246cff506500567)) +- 修复仪表盘访问趋势区块 y 轴数值过大时无法展示的问题 ([47a5746](https://github.com/ysoft-org/ysoft-admin/commit/47a5746794e552faf9c41fbcc21af091a878eb95)) +- 修复控制台报错 Please use theme before using plugins ([47a8160](https://github.com/ysoft-org/ysoft-admin/commit/47a8160d70862a5ee7284c165004cece2714a10f)) +- 修复 Swagger 分组接口缺失 ([b63d7d7](https://github.com/ysoft-org/ysoft-admin/commit/b63d7d725da5e9e9b2db9fd59bd140d64b50040c)) + +## [v1.3.0](https://github.com/ysoft-org/ysoft-admin/compare/v1.2.0...v1.3.0) (2023-11-04) + +### ✨ 新特性 + +* 消息管理:提供消息查看、标记已读、全部已读、删除等功能(适配对接导航栏站内信功能) +* 新增头像上传前裁剪功能 ([Gitee#5](https://gitee.com/ysoft/ysoft-admin/pulls/5)) ([cbc652d](https://gitee.com/ysoft/ysoft-admin/commit/cbc652de77200d29bcd42bb399c86c2e7df29c4d)) ([28f4791](https://gitee.com/ysoft/ysoft-admin/commit/28f4791833060469d132c4383665e81458f9c852)) +* 支持手机号登录(演示环境不开放) ([4d70bc8](https://github.com/ysoft-org/ysoft-admin/commit/4d70bc84db47c36c13d8e41e3a33e5a589483de8)) +* 支持邮箱登录 ([17b169e](https://github.com/ysoft-org/ysoft-admin/commit/17b169eb0ea2ded759b6bccb213c78bfb3425941)) +* 个人中心-安全设置,支持绑定、解绑三方账号 ([efe4557](https://github.com/ysoft-org/ysoft-admin/commit/efe455736c158e73bf0c6514c31bec5d83fe843b)) +* 支持第三方账号登录 ([05cb609](https://github.com/ysoft-org/ysoft-admin/commit/05cb60978017edbd14f1c7af83053f8a91800b5c)) + +### 💎 功能优化 + +- 新增接口文档菜单,演示环境开放接口文档 ([4a42336](https://github.com/ysoft-org/ysoft-admin/commit/4a4233647f2ea212b007f591aafc50380b15c099)) +- 项目配置增加是否为生产环境配置项 ([38deb95](https://github.com/ysoft-org/ysoft-admin/commit/38deb950ac7b2ed81f0e10816e943156aa076795)) +- 优化校验相关方法命名 ([f25de2d](https://github.com/ysoft-org/ysoft-admin/commit/f25de2d7f835a3fa75d59d3de0a014c37b3b32e1)) +- 新增全局响应结果处理器 ([Gitee#3](https://gitee.com/ysoft/ysoft-admin/pulls/3)) ([992a8fc](https://gitee.com/ysoft/ysoft-admin/commit/992a8fca173ea76722b388aca462cff8a1128803)) ([Gitee#4](https://gitee.com/ysoft/ysoft-admin/pulls/4)) ([a0b1afc](https://gitee.com/ysoft/ysoft-admin/commit/a0b1afc546657766cb6031794b98ccc2b6e4cb2d)) +- 优化部分代码格式及注释 ([3a176ac](https://github.com/ysoft-org/ysoft-admin/commit/3a176ac5efbda4aea1e883b29e68861bd352d642)) +- 重构登录页面 UI 以适配多维度认证、第三方登录等场景 ([d40d5b4](https://github.com/ysoft-org/ysoft-admin/commit/d40d5b4ae61d858fbee3ffa0606ebebb4282d9a2)) ([a5a4cd4](https://github.com/ysoft-org/ysoft-admin/commit/a5a4cd49646db3fa1108a8b917ef70c7757e81ad)) +- 升级前端依赖 ([698a725](https://github.com/ysoft-org/ysoft-admin/commit/698a7251b742e6b679694f21bfc174904dca8990)) + - Arco Design Vue 2.51.0 => 2.52.0 + - vue-i18n 9.2.2 => 9.5.0 + - dayjs 1.11.9 => 1.11.10 + +- 升级后端依赖 ([698a725](https://github.com/ysoft-org/ysoft-admin/commit/698a7251b742e6b679694f21bfc174904dca8990)) + - Spring Boot 2.7.15 => 2.7.16 + - Sa-Token 1.35.0.RC => 1.36.0 + - Hutool 5.8.20 => 5.8.22 + + +### 🐛 问题修复 + +- 开放前端项目IP访问 ([22a291d](https://github.com/ysoft-org/ysoft-admin/commit/22a291d4cf48e33dc2415e44b5d991b46451e7eb)) +- 修复获取验证码倒计时显示 ([2f2905e](https://github.com/ysoft-org/ysoft-admin/commit/2f2905efdc0baec2f2c38f686f72306394801ebf)) +- 用户邮箱信息增加脱敏处理 ([5bb35a1](https://github.com/ysoft-org/ysoft-admin/commit/5bb35a13d6b5801317a295eacc67d88b2c3e1682)) +- 修复重载校验方法定义及使用错误 ([a1ccc42](https://github.com/ysoft-org/ysoft-admin/commit/a1ccc421c440e5fef54e5d22b9bed26d2b16dda5)) +- 修复个人中心密码设置状态显示错误的问题 ([b04a228](https://github.com/ysoft-org/ysoft-admin/commit/b04a228a1a5bc0a575dd9e29e515285708b8ca85)) +- 修复登录后访问首页却跳转到登录页面的问题 ([Fixes #23](https://github.com/ysoft-org/ysoft-admin/issues/23)) ([7cf5e00](https://github.com/ysoft-org/ysoft-admin/commit/7cf5e0018c87720303f731317b5eb3cb7d127327)) +- 修复字典名称表单校验 ([#22](https://github.com/ysoft-org/ysoft-admin/pull/22)) ([c0ee2ea](https://github.com/ysoft-org/ysoft-admin/commit/c0ee2eac026d2d5a950a41b6f0a475b95b71d47a)) + +### 💥 破坏性变更 + +- 调整后端请求、响应参数模型命名风格 ([87f9056](https://github.com/ysoft-org/ysoft-admin/commit/87f90567dbd99f873aea1b85510c7b9939a2abb8)) +- 枚举接口 BaseEnum => IBaseEnum ([f5e8b09](https://github.com/ysoft-org/ysoft-admin/commit/f5e8b0943c6076c476b7d78bb623707740fb452f)) +- 优化前端登录模块 API 路径 ([43590bf](https://github.com/ysoft-org/ysoft-admin/commit/43590bf66e7e4873a85bdd416bd38b269f3af80e)) +- 优化后端部分参数模型命名 ([51f5528](https://github.com/ysoft-org/ysoft-admin/commit/51f552892ccb11ed594bf908069a1fd426324b69)) +- 优化个人中心路由地址 ([36d52d3](https://github.com/ysoft-org/ysoft-admin/commit/36d52d3e1522cd221cf3f03d76efd3e0eaf1b18f)) +- 还原前端 loginStore 命名,重命名为 userStore ([8d39493](https://github.com/ysoft-org/ysoft-admin/commit/8d394937cfc8418799215bd3659d26bed1f834c5)) + +## [v1.2.0](https://github.com/ysoft-org/ysoft-admin/compare/v1.1.2...v1.2.0) (2023-09-24) + +### ✨ 新特性 + +* 字典管理:提供对系统公用数据字典的维护,例如:公告类型,支持字典标签背景色和排序等配置 +* 系统配置:提供修改系统标题、Logo、favicon 等基础配置功能,以方便用户系统与其自身品牌形象保持一致 +* 完善仪表盘最近访问区块内容 ([36fda57](https://github.com/ysoft-org/ysoft-admin/commit/36fda57d499b0c3fb092a13f269bc9ffb7a26a9e)) +* 完善仪表盘访问趋势区块内容 ([a1c20af](https://github.com/ysoft-org/ysoft-admin/commit/a1c20afb1b9eb447f62bfd2e4f2996dfdf37c8ca)) ([1722133](https://github.com/ysoft-org/ysoft-admin/commit/1722133ac4872b40d6d47f65f359dea8a354b91a)) +* 完善仪表盘访客地域分布区块内容 ([dc1691f](https://github.com/ysoft-org/ysoft-admin/commit/dc1691f0195ef6c96aee36f50fc7e86cfcf651b9)) +* 完善仪表盘热门模块区块内容 ([83b2e2a](https://github.com/ysoft-org/ysoft-admin/commit/83b2e2a7c02d38c7041497e0ac5b3b0e78abac29)) +* 完善仪表盘总计区块内容 ([3440aa4](https://github.com/ysoft-org/ysoft-admin/commit/3440aa4faa23e267735f564476d8bccaf8c0208f)) +* 完善仪表盘快捷操作区块内容 ([0178fbb](https://github.com/ysoft-org/ysoft-admin/commit/0178fbb89a0e75729aa60443a812496bd5b19cb8)) + +### 💎 功能优化 + +- 前端表单重置优化 ([e947312](https://github.com/ysoft-org/ysoft-admin/commit/e947312f244d6af01f18b542ff7395440c68b089)) +- 优化登录和菜单加载相关提示 ([d080120](https://github.com/ysoft-org/ysoft-admin/commit/d080120d4228e77200d8f152397b0ebee413b089)) +- 完善前后端校验 ([90d825a](https://github.com/ysoft-org/ysoft-admin/commit/90d825a02fdc54e8685508a6fe4fb2d5f20e77f4)) ([8e506dc](https://github.com/ysoft-org/ysoft-admin/commit/8e506dc6e69529627a0aace6118f7310cc2f030a)) +- 优化枚举字典处理,增加颜色类型 ([1f73aa7](https://github.com/ysoft-org/ysoft-admin/commit/1f73aa732d101c7f7a58bc678e85d597d54d9770)) +- 公告类型适配字典数据 ([3a3a5d6](https://github.com/ysoft-org/ysoft-admin/commit/3a3a5d6b712f435d77ea04301afa0bdd8703567f)) +- 优化通用查询注解多字段模糊查询 ([3758107](https://github.com/ysoft-org/ysoft-admin/commit/375810772aa8cb928fb1f6820e781cb43f869e03)) +- 合并菜单管理图标和标题列 ([36d38ae](https://github.com/ysoft-org/ysoft-admin/commit/36d38aec1602f5ac6d2afbb5c5adf4d6e455ab97)) +- 封装 Spring Boot 默认错误处理 ([b874ca0](https://github.com/ysoft-org/ysoft-admin/commit/b874ca0782eb116bdedfc08023959a977f170a94)) +- 优化分页查询登录日志列表接口实现 ([566c9a1](https://github.com/ysoft-org/ysoft-admin/commit/566c9a122453980b585bd68442bb545073504a3d)) +- 更换登录页面 banner ([6f19660](https://github.com/ysoft-org/ysoft-admin/commit/6f19660cfbc3be6e0d702e3f488e266c50622f0a)) +- 优化登录用户信息角色相关信息命名 ([be394f3](https://github.com/ysoft-org/ysoft-admin/commit/be394f3de4ea7ea692042db3556f706a3d141b51)) ([31f0abb](https://github.com/ysoft-org/ysoft-admin/commit/31f0abbae2e38d1cfa3f6221c9be0b54cf5337ad)) +- 升级前端依赖 ([c665902](https://github.com/ysoft-org/ysoft-admin/commit/c6659020f8bac7319c5c407389cd745527a8cd97)) +- 升级后端依赖 ([5049e1e](https://github.com/ysoft-org/ysoft-admin/commit/5049e1e312ab500e284abccbbee4186db2710d01)) ([d20aadf](https://github.com/ysoft-org/ysoft-admin/commit/d20aadfc93b54339d19d173fce364310e90b016d)) ([32904b5](https://github.com/ysoft-org/ysoft-admin/commit/32904b54ef63536ef5c5106adc00a7376b907632)) + +### 🐛 问题修复 + +- 修复删除列表数据后 Select 选择框重置问题 ([#21](https://github.com/ysoft-org/ysoft-admin/pull/21)) ([3288f2d](https://github.com/ysoft-org/ysoft-admin/commit/3288f2d38dfebc1381842d67cdfb17675c786859)) +- 修复前端部分拼写错误 ([62021f8](https://github.com/ysoft-org/ysoft-admin/commit/62021f8fdc171ad04d07c25c5a9357a64cc4a087)) + +### 💥 破坏性变更 + +- 优化系统内置类型数据标识 ([8a02401](https://github.com/ysoft-org/ysoft-admin/commit/8a02401a24b546f2a6aab04cf05371ecb4236ca0)) +- 分离 HTTP 状态码和业务状态码 ([b3b6446](https://github.com/ysoft-org/ysoft-admin/commit/b3b6446433972422cf62dfc47c031134b91cd7ec)) +- 调整生产环境本地存储、日志位置 ([2254e55](https://github.com/ysoft-org/ysoft-admin/commit/2254e555af9cade4897d5335b252a0312d6805eb)) +- 调整项目打包结构,分离依赖、配置文件 ([e679abf](https://github.com/ysoft-org/ysoft-admin/commit/e679abfccc6c80198512958b6d07b363074d9d76)) + +## [v1.1.2](https://github.com/ysoft-org/ysoft-admin/compare/v1.1.1...v1.1.2) (2023-09-24) + +### 💎 功能优化 + +- 优化后端程序启动成功输出内容 ([6322859](https://github.com/ysoft-org/ysoft-admin/commit/63228598d9fcd6e5d00172c12418a371d4c96766)) +- 配置子级菜单图标 ([5544836](https://github.com/ysoft-org/ysoft-admin/commit/55448364a39085debb776463f5e95a15b186c447)) + +### 🐛 问题修复 + +- 修复生产环境和开发环境样式不一致的问题 ([be8732d](https://github.com/ysoft-org/ysoft-admin/commit/be8732d812e021631864b0ff6225b4da24cafcee)) +- 排除路径配置放开 /error ([0428fe7](https://github.com/ysoft-org/ysoft-admin/commit/0428fe776224afb64601901cef4d3100e5d30bd6)) +- 修复初始数据缺失字段列表的问题 ([d5138e1](https://github.com/ysoft-org/ysoft-admin/commit/d5138e1e43bdc8b347e061890131ac2646b2dd3c)) +- 修复系统日志表索引缺失导致查询耗时较长的问题 ([ac43833](https://github.com/ysoft-org/ysoft-admin/commit/ac438337219f5a160d49b255805774da36ab865c)) +- 修复部分菜单数据 component 信息配置错误 ([11ea072](https://github.com/ysoft-org/ysoft-admin/commit/11ea072d600f24fe97fe8145208e821712b84839)) +- 修复图标 SVG 内容格式错误 ([20f1e8a](https://github.com/ysoft-org/ysoft-admin/commit/20f1e8aecc737b28ab869d363957513d868b4ab7)) + +## [v1.1.1](https://github.com/ysoft-org/ysoft-admin/compare/v1.1.0...v1.1.1) (2023-09-06) + +### 💎 功能优化 + +- 调整 Mock 响应时长,以解决前端偶发需重复登录问题 ([df19c5d](https://github.com/ysoft-org/ysoft-admin/commit/df19c5d2197fabb61cbdd4dccf1c427fb23d77d4)) + +### 🐛 问题修复 + +- 还原登录 Helper 优化(导致重大登录问题及查询在线用户错误) ([#15](https://github.com/ysoft-org/ysoft-admin/pull/15)) ([7a6db2d](https://github.com/ysoft-org/ysoft-admin/commit/7a6db2d14e60a5fcc1a2786e6eaa3d46a0714e6c)) ([#9](https://github.com/ysoft-org/ysoft-admin/pull/9)) ([9e2a5ef](https://github.com/ysoft-org/ysoft-admin/commit/9e2a5ef1249fd93dd10f2c255bf77c3eaa64a241)) +- 修复刷新页面后,选中菜单无法保持展开状态的问题 ([3fc7adb](https://github.com/ysoft-org/ysoft-admin/commit/3fc7adb1e2bd4b648753bd2999df725417e01680)) +- 修复侧边栏菜单无法显示自定义图标的问题 ([10ca5d8](https://github.com/ysoft-org/ysoft-admin/commit/10ca5d8c76aa39a207ea7db4442bf63ff4578273)) +- 更正 README 文档项目结构部分内容 ([486da2f](https://github.com/ysoft-org/ysoft-admin/commit/486da2f79bfc5379213bf666b8f325fb8096ebc6)) +- 修复公告缺失待发布状态的问题 ([#14](https://github.com/ysoft-org/ysoft-admin/pull/14)) ([46cc4c9](https://github.com/ysoft-org/ysoft-admin/commit/46cc4c9307e3cc7060ae436f59f007831104884a)) + +## [v1.1.0](https://github.com/ysoft-org/ysoft-admin/compare/v1.0.1...v1.1.0) (2023-09-01) + +### ✨ 新特性 + +* 公告管理:提供公告的发布、查看和删除等功能。管理员可以在后台发布公告,并可以设置公告的生效时间、终止时间,以 markdown-it 为内核渲染 Markdown 格式内容显示 +* 代码生成:提供根据数据库表自动生成相应的前后端 CRUD 代码的功能 +* 允许表格调整列宽,不允许新增/修改类表单对话框按 Esc 关闭 ([1b06a96](https://github.com/ysoft-org/ysoft-admin/commit/1b06a96cfbe5774931d8c4c0d7827703caa096df)) + +### 💎 功能优化 + +- 最终适配及启用 Arco Design Pro Vue 动态路由 ([9baf341](https://github.com/ysoft-org/ysoft-admin/commit/9baf3410138cb8a152ec51f70340d500fa009510)) +- 优化分页总记录数数据类型 ([bfea689](https://github.com/ysoft-org/ysoft-admin/commit/bfea689b0eaf44c8d54b4fd59c042d72ac71e395)) +- 修复在线用户列表等自定义分页查询 NPE 的问题 ([015ff55](https://github.com/ysoft-org/ysoft-admin/commit/015ff5512b3662efce88d02ab1dda6d55501a501)) +- 对获取路由信息接口增加缓存处理 ([4639d13](https://github.com/ysoft-org/ysoft-admin/commit/4639d13ba61abfaed3c9d3da0e057892577b5c40))⚡ +- 完善前端 axios 请求响应拦截器 ([bb398d8](https://github.com/ysoft-org/ysoft-admin/commit/bb398d8101e3780f450c6508852fc727fb936cee)) ([e18692f](https://github.com/ysoft-org/ysoft-admin/commit/e18692fa74e0a0d9558db6643b945c6c6a00db36)) +- 优化仪表盘公告区块、帮助文档区块内容 ([b59a819](https://github.com/ysoft-org/ysoft-admin/commit/b59a819ad5f2bdbd357951f070d155e91f2d7903)) ([315c059](https://github.com/ysoft-org/ysoft-admin/commit/315c059713833be10b0cf05d302259a3146f3707)) ([6d024a9](https://github.com/ysoft-org/ysoft-admin/commit/6d024a90d7a231439c8e260b9bd625e8b5027515)) +- 将 Swagger 文档中的额外请求参数隐藏 ([#11](https://github.com/ysoft-org/ysoft-admin/pull/11)) ([a9ed02b](https://github.com/ysoft-org/ysoft-admin/commit/a9ed02bf4ff6a8a4d9f68db2d62d29000c543943)) +- 优化前端 CRUD 相关命名 ([6d81928](https://github.com/ysoft-org/ysoft-admin/commit/6d81928541f4da568e9c7138f91d4dc1c5c6dd4e)) +- 优化部分超链接标签属性 ([46a75d0](https://github.com/ysoft-org/ysoft-admin/commit/46a75d029798e8d5a162b53b8a61c8e3c3f4dd9e)) +- 使用属性变量消除配置文件中分散的 ContiNew Admin 品牌元素 ([54ea410](https://github.com/ysoft-org/ysoft-admin/commit/54ea41048abd096cf1e2c32ee871c1eb85d4ece1)) +- 拆分 Swagger 接口文档分组 ([#10](https://github.com/ysoft-org/ysoft-admin/pull/10)) ([72df45e](https://github.com/ysoft-org/ysoft-admin/commit/72df45e9b3373d28f1845af16a81cb8bd8408647)) +- 优化登录 Helper ([#9](https://github.com/ysoft-org/ysoft-admin/pull/9)) ([9e2a5ef](https://github.com/ysoft-org/ysoft-admin/commit/9e2a5ef1249fd93dd10f2c255bf77c3eaa64a241)) +- 将全局异常处理器未知异常的异常类型从 Exception 调整为 Throwable ([90e1c64](https://github.com/ysoft-org/ysoft-admin/commit/90e1c64db684df97454e4753932b7f4017d8e23d)) +- 优化 == 及 != 表达式格式 ([487fa82](https://github.com/ysoft-org/ysoft-admin/commit/487fa82306fbd84033f6c39ad20b72755b03e875)) +- 集成 Spring Cache,优化查询用户昵称性能 ([b23b00d](https://github.com/ysoft-org/ysoft-admin/commit/b23b00d02a4738a61b4a13676fab6d2c9ec927de)) ([76622c2](https://github.com/ysoft-org/ysoft-admin/commit/76622c238f1d6028826407490e50a14bdba25ade))⚡ +- 将验证码唯一标识格式从无符号 UUID 调整为带符号 UUID ([a61196c](https://github.com/ysoft-org/ysoft-admin/commit/a61196cd62cea4f684154bb42a949656650f626b)) +- 完善接口文档示例信息 ([#7](https://github.com/ysoft-org/ysoft-admin/pull/7)) ([ad7d699](https://github.com/ysoft-org/ysoft-admin/commit/ad7d6995ba40a0cb70a194693fa450bdbb3cc7a0)) ([#8](https://github.com/ysoft-org/ysoft-admin/pull/8)) ([0ac0213](https://github.com/ysoft-org/ysoft-admin/commit/0ac0213628023c04b5be531522d76f09712f7317)) ([190385e](https://github.com/ysoft-org/ysoft-admin/commit/190385ed3636206224bc90780fcede2e49f9c118)) ([332bd6c](https://github.com/ysoft-org/ysoft-admin/commit/332bd6cd2a9b4e25678a3eec565965c5b2702aa2)) +- 使用 DatePattern 中的日期格式常量替代字符串常量中的日期格式 ([241a9cf](https://github.com/ysoft-org/ysoft-admin/commit/241a9cf85b3c19eb093d4d661c35d71c490adf1f)) +- 优化分组校验 ([78a5d5e](https://github.com/ysoft-org/ysoft-admin/commit/78a5d5ec7a14ee37d92a9520211adca23f12b287)) +- 优化 springdoc-openapi 对象型参数处理 ([ae8d294](https://github.com/ysoft-org/ysoft-admin/commit/ae8d294705536e99d6c30a9ff5257fdb3ee5b35f)) +- 升级前端依赖,并更换包管理器 yarn => pnpm ([6164110](https://github.com/ysoft-org/ysoft-admin/commit/6164110462cc3aff66d79539f54e84d47c6d5894)) +- 升级后端依赖 ([51a82d8](https://github.com/ysoft-org/ysoft-admin/commit/51a82d8f4eabd6aa27e1a991f05f516171b6ae03)) + +### 🐛 问题修复 + +- 完善部分数据库表的唯一索引 ([88d6118](https://github.com/ysoft-org/ysoft-admin/commit/88d6118693586fbd8da573df3b2f942d049e4b3c)) +- 修复访问 doc.html 接口文档,控制台报 No mapping for GET /favicon.ico 警告的问题 ([94f88ba](https://github.com/ysoft-org/ysoft-admin/commit/94f88bad2278d64a4b8a3bc930a9f754fb00cba6)) +- 登录页面输入错误时,自动清空验证码输入框 ([a76f47f](https://github.com/ysoft-org/ysoft-admin/commit/a76f47fbd86bfa7fbf85440c653ae6259fce7969)) + +### 💥 破坏性变更 + +- 更新信息调整为仅在更新数据时自动填充 ([df77e57](https://github.com/ysoft-org/ysoft-admin/commit/df77e574cca605afd89f1b3781f1cde699bcb7e6)) +- 将时间戳单位从毫秒调整为秒 ([fa916b9](https://github.com/ysoft-org/ysoft-admin/commit/fa916b93247e10462eb44185ad45cdca4dedda7d)) +- 移除所有的 @Accessors(chain = true),并全局配置禁止使用 ([76c6546](https://github.com/ysoft-org/ysoft-admin/commit/76c65463c2e5ddf0c90fa1622fd86706a4373c80)) + +## [v1.0.1](https://github.com/ysoft-org/ysoft-admin/compare/v1.0.0...v1.0.1) (2023-08-17) + +### 💎 功能优化 + +- 优化根据 ID 查询用户昵称方法 ([4a8af1f](https://github.com/ysoft-org/ysoft-admin/commit/4a8af1f72d9249afa1c013e08674f492f453b020)) +- 优化 BaseController 中部分权限码的使用 ([b0b1127](https://github.com/ysoft-org/ysoft-admin/commit/b0b1127b5bd39e9bc431e9fa9c86201bbc18e891)) +- 优化分页总记录数数据类型 ([76f04dd](https://github.com/ysoft-org/ysoft-admin/commit/76f04dd38f90aad6abf82d2dccba031d4d9108cf)) +- 优化通用查询注解解析器 ([a623acd](https://github.com/ysoft-org/ysoft-admin/commit/a623acd4a5529ae42898ec359f595716acc5bab8)) ([b632c18](https://github.com/ysoft-org/ysoft-admin/commit/b632c183994ac71382180a38bf7bdb7a6315c1e6)) +- 优化数据库表结构中部分类型长度 ([f3fabea](https://github.com/ysoft-org/ysoft-admin/commit/f3fabea7dd736d94badecbc08091eec6274f5fb7)) +- 使用常量优化部分魔法值 ([e6f7429](https://github.com/ysoft-org/ysoft-admin/commit/e6f7429fa30cbc87c03a073a53b6f7df24d33d8d)) +- 优化部分 Properties 用法 ([48de2e8](https://github.com/ysoft-org/ysoft-admin/commit/48de2e85e0fbf60f10769cd3529f79ac3c531e92)) + +### 🐛 问题修复 + +- 修复获取字典参数为空时的判断条件 ([#6](https://github.com/ysoft-org/ysoft-admin/pull/6)) ([104f69e](https://github.com/ysoft-org/ysoft-admin/commit/104f69e8a09ce36163f6f9680b2d8d61bb45f11a)) +- 完善查询用户数据权限 ([026247f](https://github.com/ysoft-org/ysoft-admin/commit/026247f677110ae199124a67c68503729cbaec92)) +- 解决 IDE 报 Delete ␍ eslint(prettier/prettier) 警告的问题 ([8743ed1](https://github.com/ysoft-org/ysoft-admin/commit/8743ed14d927ab52814ed5f5f166afaa7a6b78b2)) +- 修复分页查询条件默认值未生效的问题 ([2d2a7e7](https://github.com/ysoft-org/ysoft-admin/commit/2d2a7e7c8e31763ac3ea514d8a92c3938376dd3a)) +- 完善各模块事务注解 ([18c54a7](https://github.com/ysoft-org/ysoft-admin/commit/18c54a74fc6ff0650ff53eeadc094d7e1df0b0a5)) +- 修复邮箱健康检查报错问题并优化部分配置写法 ([5968f40](https://github.com/ysoft-org/ysoft-admin/commit/5968f402ed478244d36f5825373190ed00d8c1f1)) +- 完善各模块参数校验 ([8b955a0](https://github.com/ysoft-org/ysoft-admin/commit/8b955a0b1bde4e8959fc0dfbc11a326d9eec0b45)) + +## v1.0.0 (2023-03-26) + +### ✨ 新特性 + +* 用户管理:提供用户的相关配置,新增用户后,默认密码为 123456 +* 角色管理:对权限与菜单进行分配,可根据部门设置角色的数据权限 +* 部门管理:可配置系统组织架构,树形表格展示 +* 菜单管理:已实现菜单动态路由,后端可配置化,支持多级菜单 +* 在线用户:管理当前登录用户,可一键踢下线 +* 日志管理:提供在线用户监控、登录日志监控、操作日志监控和系统日志监控等监控功能 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f50c997 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5bf3ca5 --- /dev/null +++ b/README.md @@ -0,0 +1,507 @@ +# ContiNew Admin 中后台管理框架 + + +Release + + +ContiNew Starter + + +Spring Boot + + +Open JDK + + +Codacy + + +Sonar + +
+ +License + + +GitHub Stars + + +GitHub Forks + + +Gitee Stars + + +Gitee Forks + + +GitCode Stars + + +📚 [在线文档](https://ysoft.top) | 🚀 [演示地址](https://admin.ysoft.top)(账号/密码:admin/admin123) + +## 简介 + +ContiNew Admin(Continue New Admin)持续迭代优化的前后端分离中后台管理系统框架。开箱即用,重视每一处代码规范,重视每一种解决方案细节,持续提供舒适的前、后端开发体验。 + +当前采用的技术栈:Spring Boot3(Java17)、Vue3 & Arco Design & TS & Vite、Sa-Token、MyBatis Plus、Redisson、JetCache、JustAuth、Crane4j、EasyExcel、Liquibase、Hutool 等。 + +## 项目源码 + +| | 后端 | 前端 | +| :------ | :----------------------------------------------------------- | :----------------------------------------------------------- | +| Gitee | [ysoft/ysoft-admin](https://gitee.com/ysoft/ysoft-admin) | [ysoft/ysoft-admin-ui](https://gitee.com/ysoft/ysoft-admin-ui) | +| GitCode | [ysoft/ysoft-admin](https://gitcode.com/ysoft/ysoft-admin) | [ysoft/ysoft-admin-ui](https://gitcode.com/ysoft/ysoft-admin-ui) | +| GitHub | [ysoft-org/ysoft-admin](https://github.com/ysoft-org/ysoft-admin) | [ysoft-org/ysoft-admin-ui](https://github.com/ysoft-org/ysoft-admin-ui) | + +## 项目起源 + +我热衷于做数据归档,归档后的数据可以提高学习/工作效率,为记忆“减负”,在持续的数据归档中,优质的“沉淀”会带来非匀速、跨越式的学习/工作体验。**数据归档是一件需要持续去做的事情**。 + +从接触程序代码的第一天,我的程序数据归档也随之开始了,刷过的算法题、笔记、对接各种组件的配置文件,甚至于一些亮眼的样式设计、“如诗”的代码片段。这些数据的沉淀丰富了我的解决方案,提高了我的编程效率,逐渐为各种场景落实成了一个个雏形程序。再后来,我意识到,我归档的这些雏形程序,有一个更为妥贴的名称:**程序框架/脚手架**。 + +技术的发展,导致这些雏形程序的生命周期很是短暂,它们有别于我归档的其他数据,有时由于工作的原因,没有时间很好的去沉淀它们,在使用时变得越来越不顺手。所以,某段时间,我放弃了维护,而是去采用一些更为成熟的框架。 + +不过,在陆续几年使用了一些成熟框架后,我前后遇到了一些困难: + +1. 代码洁癖想要找到一个**扩展性佳,代码规范良好,开发体验舒适**的框架很不容易,总是差些什么 +2. 项目上手困难或是基础版功能不全,需要的全在专业版,亦或者代码阅读性差,文档收费 +3. 部分解决方案缺失,已有解决方案也过于偏向样板化,无法形成良好的逻辑闭环 +4. 好不容易找到一些相较合适的,没过多久,部分作者可能暂时没法对外发“电”了,随着了解深入,很多 Bug 或新技术趋势还是需要自己研究解决 + +在工作中,很多想法/设计受限于客户需求、开发工期,必须优先以交付为导向,但一些优秀的实践需要花时间持续进行沉淀,只要我没跳出这个圈子,我还是需要一直去做好程序归档。“种一棵树最好的时间是十年前,其次是现在”,最终,我选择在业余时间更加正视这件事,从头归档沉淀,从添加每一个依赖开始,我希望它能持续的迭代优化、演进,所以我把它命名为 **ContiNew(Continue New)**。并且这次我选择了开源,我希望它不仅仅能吸收我的需求和沉淀,而是依托开源协作模式,及时发现更多的问题,接受更多的可能性,沉淀更优秀的思考,设计。 + +## 为什么选我们? + +> [!TIP] +> 更为完整的图文描述请查阅[《在线文档》](https://ysoft.top/admin/intro/why.html)。 + +1.**甄选技术栈:** ContiNew(Continue New) 项目致力于持续迭代优化,让技术不掉队。在技术选型时,进行深度广泛地调研,从流行度、成熟度和发展潜力等多方面甄选技术栈。 + +2.**Starter 组件:** 从 v2.1.0 版本开始,抽取并封装后端基础组件及各框架集成配置到 ContiNew Starter 项目,且 **[已发布至 Maven 中央仓库](https://central.sonatype.com/search?q=ysoft-starter&namespace=top.ysoft)**,可在你的任意项目中直接引入所需依赖使用。即使你不用脚手架项目,难道能让你搭项目框架更快、更爽、更省力的 Starter 也要 Say No 吗? + +3.**CRUD 套件:** 封装通用增删改查套件,适配后端各分层,几分钟即可提供一套 CRUD API,包括新增、修改、批量删除、查询详情、分页列表查询、全部列表查询、树型列表查询、导出到 Excel,且 API 支持按实际所需开放或扩展。 + +```java +@Tag(name = "部门管理 API") +@RestController +@CrudRequestMapping(value = "/system/dept", api = {Api.TREE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}) +public class DeptController extends BaseController {} +``` + +4.**代码生成器:** 提供代码生成器,已配套前、后端代码生成模板,数据表设计完之后,简单配置一下即可生成前、后端 80% 的代码,包含 CRUD API、权限控制、参数校验、接口文档等内容。如果业务不复杂,也可能就是 95% 的代码。 + +5.**改善开发体验:** 持续优化及适配能改善开发体验的组件。 + +- 适配 ContiNew Starter 各组件,针对 Spring 基础配置、通用解决方案以及流行框架进行了深度封装的 starter 集合,改善你在开发每个 Spring Boot Web 项目的体验。(枚举参数处理、默认线程池、跨域、加密、脱敏、限流、日志、异常及响应通用解决方案等等,更多细节可查看 Starter 源码) +- 适配 Crane4j 数据填充组件,减少因为一个用户名而产生的联表回填; +- 适配 P6Spy SQL 性能分析组件,开发期间方便监控 SQL 执行; +- 适配 TLog 链路追踪组件,方便在杂乱的日志文件中追踪你某次请求的日志记录; +- 适配 JetCache 缓存框架(比 Spring Cache 更强大易用),通过注解声明即可快速实现方法级缓存,极大改善编码式缓存体验,且支持灵活的二级缓存配置、分布式自动刷新等能力; +- 前端适配 Vue Devtools(Vue 官方提供的调试浏览器插件),极大提高 Vue 开发及调试效率 + +6.**Almost最佳后端规范:** 后端严格遵循阿里巴巴 Java 编码规范,注释覆盖率 > 45%,接口参数示例 100%,代码分层使用体验佳,变量、方法命名清晰统一,前端代码也使用严格的 ESLint、StyleLint 等检查。良好的设计,代码复用率极高!写代码时,让你有一种无需多写,理应如此的感觉。我是代码洁癖,我实际写的时候很清楚这到底是不是乱吹。 + +7.**卓越工程:** 后端采用模块化工程结构,并适配了统一项目版本号、编译项目自动代码格式化、代码混淆等插件,提供了自定义打包部署结构配置(配置文件、三方依赖和主程序分离),提供全套环境及应用的 Docker Compose 部署脚本。为了减少您开发新项目时的改造耗时,项目品牌配置持续进行深度聚合,简单的配置和结构修改即可快速开始独属于你的新项目。 + +8.**业务脚手架:** 有颜有料,不止是说说而已,持续打磨 UI 设计与色彩主题。提供基于 RBAC 的权限控制、通用数据权限,包含丰富的通用业务功能:第三方登录,邮箱、短信(生产级炸弹漏洞处理方案),个人中心、用户管理、角色管理、部门管理、系统配置(基础站点配置、邮件配置、安全配置)、系统日志、消息中心、通知公告等,设计用心,逻辑合理闭环。 + +> 一个好的脚手架项目,不仅仅是提供一系列组件集成与配置,也不仅仅是封装一堆好用的工具,还更应该提供一系列通用基础业务解决方案及设计,为初创团队项目减负。 + +9.**质量与安全:** CI 已集成 Sonar、Codacy,Push 即扫描代码质量,定期扫描 CVE 漏洞,及时解决潜在问题。封装数据库字段加密、JSON 脱敏、XSS 过滤等工具,提供诸多安全解决方案。 + +由于篇幅有限,且项目正处于高速发展期,更多功能正在陆续上线(敬请关注仓库或群内动态)。另外像最基本的统一异常、错误处理,基础线程池等配置就不在此赘述,细节优化详情请 clone 代码查看。 +> Talk is cheap, show the code. + +## 系统功能 + +> [!TIP] +> 更多功能和优化正在赶来💦,最新项目计划、进展请进群或关注 [需求墙](https://ysoft.top/require.html) 和 [更新日志](https://ysoft.top/admin/other/changelog.html)。 + +- 仪表盘:提供工作台、分析页,工作台提供功能快捷导航入口、最新公告、动态;分析页提供全面数据可视化能力 +- 个人中心:支持基础信息修改、密码修改、邮箱绑定、手机号绑定(并提供行为验证码、短信限流等安全处理)、第三方账号绑定/解绑、头像裁剪上传 +- 消息中心:提供站内信消息统一查看、标记已读、全部已读、删除等功能(目前仅支持系统通知消息) +- 用户管理:管理系统用户,包含新增、修改、删除、导入、导出、重置密码、分配角色等功能 + +- 角色管理:管理系统用户的功能权限及数据权限,包含新增、修改、删除、分配角色等功能 + +- 菜单管理:管理系统菜单及按钮权限,支持多级菜单,动态路由,包含新增、修改、删除等功能 + +- 部门管理:管理系统组织架构,包含新增、修改、删除、导出等功能,以树形列表进行展示 + +- 字典管理:管理系统公用数据字典,例如:消息类型。支持字典标签背景色和排序等配置 + +- 通知公告:管理系统公告,支持设置公告的生效时间、终止时间、通知范围(所有人、指定用户) + +- 文件管理:管理系统文件,支持上传、下载、预览(目前支持图片、音视频、PDF、Word、Excel、PPT)、重命名、切换视图(列表、网格)等功能 + +- 存储管理:管理文件存储配置,支持本地存储、兼容 S3 协议对象存储 + +- 终端管理:多端认证管理,可设置不同的 token 有效期 + +- 系统配置: + - 基础配置:提供修改系统标题、Logo、favicon、版权信息等基础配置功能,以方便用户系统与其自身品牌形象保持一致 + - 邮件配置:提供系统发件箱配置,也支持通过配置文件指定 + - 安全配置:提供密码策略修改,支持丰富的密码策略设定,包括但不限于 `密码有效期`、`密码重复次数`、`密码错误锁定账号次数、时间` 等 + +- 在线用户:管理当前登录用户,可一键踢除下线 + +- 日志管理:管理系统登录日志、操作日志,支持查看日志详情,包含请求头、响应头等报文信息 + +- 任务管理:管理系统定时任务,包含新增、修改、删除、执行功能,支持 Cron(可配置式生成 Cron 表达式) 和固定频率 + +- 任务日志:管理定时任务执行日志,包含停止、重试指定批次,查询集群各节点的详细输出日志等功能 + +- 应用管理:管理第三方系统应用 AK、SK,包含新增、修改、删除、查看密钥、重置密钥等功能,支持设置密钥有效期 + +- 代码生成:提供根据数据库表自动生成相应的前后端 CRUD 代码的功能,支持同步最新表结构及代码生成预览 + +## 系统截图 + +> [!TIP] +> 受篇幅长度及功能更新频率影响,下方仅为系统 **部分** 功能于 **2024年11月18日** 进行的截图,更多新增功能及细节请登录演示环境或 clone 代码到本地启动查看。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
登录页面登录页面-H5
仪表盘分析页
个人中心消息中心
安全设置-修改邮箱安全设置-修改邮箱-邮箱验证码
系统管理-系统配置系统管理-安全配置
系统管理-用户管理-列表系统管理-用户管理-新增
系统管理-角色管理-列表系统管理-角色管理-新增
系统管理-菜单管理-列表系统管理-菜单管理-新增
系统管理-公告管理-列表系统管理-公告管理-修改
系统管理-字典管理-列表系统管理-字典项管理
系统管理-文件管理-列表-2系统管理-文件管理-查看文档
系统工具-代码生成-配置系统工具-代码生成-预览
系统监控-在线用户系统监控-系统日志-登录日志
系统监控-系统日志-操作日志系统监控-系统日志-操作日志-详情
+ + +## 核心技术栈 + +| 名称 | 版本 | 简介 | +|:----------------------------------------------------------------------------------------------------------------------------------|:-------------| :----------------------------------------------------------- | +| Vue | 3.5.4 | 渐进式 JavaScript 框架,易学易用,性能出色,适用场景丰富的 Web 前端框架。 | +| Arco Design | 2.56.0 | 字节跳动推出的前端 UI 框架,年轻化的色彩和组件设计。 | +| TypeScript | 5.0.4 | TypeScript 是微软开发的一个开源的编程语言,通过在 JavaScript 的基础上添加静态类型定义构建而成。 | +| Vite | 5.1.5 | 下一代的前端工具链,为开发提供极速响应。 | +| [ContiNew Starter](https://github.com/ysoft-org/ysoft-starter) | 2.9.0 | ContiNew Starter 包含了一系列经过企业实践优化的依赖包(如 MyBatis-Plus、SaToken),可轻松集成到应用中,为开发人员减少手动引入依赖及配置的麻烦,为 Spring Boot Web 项目的灵活快速构建提供支持。 | +| Spring Boot | 3.2.12 | 简化 Spring 应用的初始搭建和开发过程,基于“约定优于配置”的理念,使开发人员不再需要定义样板化的配置。(Spring Boot 3.0 开始,要求 Java 17 作为最低版本) | +| Undertow | 2.3.17.Final | 采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。 | +| Sa-Token + JWT | 1.39.0 | 轻量级 Java 权限认证框架,让鉴权变得简单、优雅。 | +| MyBatis Plus | 3.5.8 | MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率。 | +| dynamic-datasource-spring-boot-starter | 4.3.1 | 基于 Spring Boot 的快速集成多数据源的启动器。 | +| Hikari | 5.0.1 | JDBC 连接池,号称 “史上最快连接池”,SpringBoot 在 2.0 之后,采用的默认数据库连接池就是 Hikari。 | +| MySQL | 8.0.33 | 体积小、速度快、总体拥有成本低,是最流行的关系型数据库管理系统之一。 | +| mysql-connector-j | 8.3.0 | MySQL Java 驱动。 | +| P6Spy | 3.9.1 | SQL 性能分析组件。 | +| Liquibase | 4.24.0 | 用于管理数据库版本,跟踪、管理和应用数据库变化。 | +| [JetCache](https://github.com/alibaba/jetcache/blob/master/docs/CN/Readme.md) | 2.7.7 | 一个基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新,还提供了 Cache 接口用于手工缓存操作。 | +| Redisson | 3.41.0 | 不仅仅是一个 Redis Java 客户端,Redisson 充分的利用了 Redis 键值数据库提供的一系列优势,为使用者提供了一系列具有分布式特性的常用工具:分布式锁、限流器等。 | +| Redis | 7.2.3 | 高性能的 key-value 数据库。 | +| [Snail Job](https://snailjob.opensnail.com/) | 1.2.0 | 灵活,可靠和快速的分布式任务重试和分布式任务调度平台。 | +| [X File Storage](https://x-file-storage.xuyanwu.cn/#/) | 2.2.1 | 一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS...等其它兼容 S3 协议的存储平台。 | +| SMS4J | 3.3.3 | 短信聚合框架,轻松集成多家短信服务,解决接入多个短信 SDK 的繁琐流程。 | +| Just Auth | 1.16.7 | 开箱即用的整合第三方登录的开源组件,脱离繁琐的第三方登录 SDK,让登录变得 So easy! | +| Easy Excel | 3.3.4 | 一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具。 | +| [AJ-Captcha](https://ajcaptcha.beliefteam.cn/captcha-doc/) | 1.3.0 | Java 行为验证码,包含滑动拼图、文字点选两种方式,UI支持弹出和嵌入两种方式。 | +| Easy Captcha | 1.6.2 | Java 图形验证码,支持 gif、中文、算术等类型,可用于 Java Web、JavaSE 等项目。 | +| [Crane4j](https://createsequence.gitee.io/crane4j-doc/#/) | 2.9.0 | 一个基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架。 | +| [CosID](https://cosid.ahoo.me/guide/getting-started.html) | 2.10.1 | 旨在提供通用、灵活、高性能的分布式 ID 生成器。 | +| [Graceful Response](https://doc.feiniaojin.com/graceful-response/home.html) | 5.0.4-boot3 | 一个Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量。 | +| Knife4j | 4.5.0 | 前身是 swagger-bootstrap-ui,集 Swagger2 和 OpenAPI3 为一体的增强解决方案。 | +| Hutool | 5.8.34 | 小而全的 Java 工具类库,通过静态方法封装,降低相关 API 的学习成本,提高工作效率,使 Java 拥有函数式语言般的优雅,让 Java 语言也可以“甜甜的”。 | +| Lombok | 1.18.36 | 在 Java 开发过程中用注解的方式,简化了 JavaBean 的编写,避免了冗余和样板式代码,让编写的类更加简洁。 | + +## 快速开始 + +> [!TIP] +> 更详细的流程,请查看在线文档[《快速开始》](https://ysoft.top/admin/intro/quick-start.html)。 + +```bash +# 1.克隆本项目 +git clone https://github.com/ysoft-org/ysoft-admin.git + +# 2.在 IDE(IntelliJ IDEA/Eclipse)中打开本项目 + +# 3.修改配置文件中的数据源配置信息、Redis 配置信息、邮件配置信息等 +# [3.也可以在 IntelliJ IDEA 中直接配置程序启动环境变量(DB_HOST、DB_PORT、DB_USER、DB_PWD、DB_NAME;REDIS_HOST、REDIS_PORT、REDIS_PWD、REDIS_DB)] + +# 4.启动程序 +# 4.1 启动成功:访问 http://localhost:8000/,页面输出:Xxx started successfully. +# 4.2 接口文档:http://localhost:8000/doc.html + +# 5.部署 +# 5.1 Docker 部署 +# 5.1.1 服务器安装好 docker 及 docker-compose(参考:https://blog.charles7c.top/categories/fragments/2022/10/31/CentOS%E5%AE%89%E8%A3%85Docker) +# 5.1.2 执行 mvn package 进行项目打包,将 target/app 目录下的所有内容放到 /docker/ysoft-admin 目录下 +# 5.1.3 将 docker 目录上传到服务器 / 目录下,并授权(chmod -R 777 /docker) +# 5.1.4 修改 docker-compose.yml 中的 MySQL 配置、Redis 配置、ysoft-admin-server 配置、Nginx 配置 +# 5.1.5 执行 docker-compose up -d 创建并后台运行所有容器 +# 5.2 其他方式部署 +``` + +## 项目结构 + +> [!TIP] +> 后端采用按功能拆分模块的开发方式,下方项目目录结构是按照模块的层次顺序进行介绍的,实际 IDE 中 `ysoft-admin-common` 模块会因为字母排序原因排在上方。 + +``` +ysoft-admin +├─ ysoft-webapi(API 及打包部署模块) +│ ├─ src +│ │ ├─ main +│ │ │ ├─ java/top/ysoft/admin +│ │ │ │ ├─ config (配置) +│ │ │ │ ├─ controller +│ │ │ │ │ ├─ auth(系统认证相关 API) +│ │ │ │ │ ├─ common(通用相关 API) +│ │ │ │ │ ├─ monitor(系统监控相关 API) +│ │ │ │ │ ├─ system(系统管理相关 API) +│ │ │ │ │ └─ tool(系统工具相关 API) +│ │ │ │ └─ ContiNewAdminApplication.java(ContiNew Admin 启动程序) +│ │ │ └─ resources +│ │ │ ├─ config(核心配置目录) +│ │ │ │ ├─ application-dev.yml(开发环境配置文件) +│ │ │ │ ├─ application-prod.yml(生产环境配置文件) +│ │ │ │ └─ application.yml(通用配置文件) +│ │ │ ├─ db/changelog(Liquibase 数据脚本配置目录) +│ │ │ │ ├─ mysql(MySQL 数据库初始 SQL 脚本目录) +│ │ │ │ ├─ postgresql(PostgreSQL 数据库初始 SQL 脚本目录) +│ │ │ │ └─ db.changelog-master.yaml(Liquibase 变更记录文件) +│ │ │ ├─ templates(模板配置目录,例如:邮件模板) +│ │ │ ├─ banner.txt(Banner 配置文件) +│ │ │ └─ logback-spring.xml(日志配置文件) +│ │ └─ test(测试相关代码目录) +│ └─ pom.xml(包含打包相关配置) +├─ ysoft-module-system(系统管理模块,存放系统管理相关业务功能,例如:部门管理、角色管理、用户管理等) +│ ├─ src +│ │ ├─ main +│ │ │ ├─ java/top/ysoft/admin +│ │ │ │ ├─ auth(系统认证相关业务) +│ │ │ │ │ ├─ model(系统认证相关模型) +│ │ │ │ │ │ ├─ query(系统认证相关查询条件) +│ │ │ │ │ │ ├─ req(系统认证相关请求对象(Request)) +│ │ │ │ │ │ └─ resp(系统认证相关响应对象(Response)) +│ │ │ │ │ └─ service(系统认证相关业务接口及实现类) +│ │ │ │ └─ system(系统管理相关业务) +│ │ │ │ ├─ config(系统管理相关配置) +│ │ │ │ ├─ enums(系统管理相关枚举) +│ │ │ │ ├─ mapper(系统管理相关 Mapper) +│ │ │ │ ├─ model(系统管理相关模型) +│ │ │ │ │ ├─ entity(系统管理相关实体对象) +│ │ │ │ │ ├─ query(系统管理相关查询条件) +│ │ │ │ │ ├─ req(系统管理相关请求对象(Request)) +│ │ │ │ │ └─ resp(系统管理相关响应对象(Response)) +│ │ │ │ ├─ service(系统管理相关业务接口及实现类) +│ │ │ │ └─ util(系统管理相关工具类) +│ │ │ └─ resources +│ │ │ └─ mapper(系统管理相关 Mapper XML 文件目录) +│ │ └─ test(测试相关代码目录) +│ └─ pom.xml +├─ ysoft-plugin(插件模块,存放代码生成、任务调度等扩展模块,后续会进行插件化改造) +│ ├─ ysoft-plugin-schedule(任务调度插件模块) +│ │ ├─ src +│ │ │ ├─ main/java/top/ysoft/admin/schedule +│ │ │ │ ├─ api(任务调度中心相关 API) +│ │ │ │ ├─ config(任务调度相关配置) +│ │ │ │ ├─ constant(任务调度相关常量) +│ │ │ │ ├─ enums(任务调度相关枚举) +│ │ │ │ ├─ model(任务调度相关模型) +│ │ │ │ │ ├─ query(任务调度相关查询条件) +│ │ │ │ │ ├─ req(任务调度相关请求对象(Request)) +│ │ │ │ │ └─ resp(任务调度相关响应对象(Response)) +│ │ │ │ └─ service(代码生成器相关业务接口及实现类) +│ │ │ └─ test(测试相关代码目录) +│ │ └─ pom.xml +│ ├─ ysoft-plugin-open(能力开放插件模块) +│ │ ├─ src +│ │ │ ├─ main/java/top/ysoft/admin/open +│ │ │ │ ├─ mapper(代码生成器相关 Mapper) +│ │ │ │ ├─ model(能力开放相关模型) +│ │ │ │ │ ├─ entity(能力开放相关实体对象) +│ │ │ │ │ ├─ query(能力开放相关查询条件) +│ │ │ │ │ ├─ req(能力开放相关请求对象(Request)) +│ │ │ │ │ └─ resp(能力开放相关响应对象(Response)) +│ │ │ │ └─ service(能力开放相关业务接口及实现类) +│ │ │ └─ test(测试相关代码目录) +│ │ └─ pom.xml +│ ├─ ysoft-plugin-generator(代码生成器插件模块) +│ │ ├─ src +│ │ │ ├─ main +│ │ │ │ ├─ java/top/ysoft/admin/generator +│ │ │ │ │ ├─ config(代码生成器相关配置) +│ │ │ │ │ ├─ enums(代码生成器相关枚举) +│ │ │ │ │ ├─ mapper(代码生成器相关 Mapper) +│ │ │ │ │ ├─ model(代码生成器相关模型) +│ │ │ │ │ │ ├─ entity(代码生成器相关实体对象) +│ │ │ │ │ │ ├─ query(代码生成器相关查询条件) +│ │ │ │ │ │ ├─ req(代码生成器相关请求对象(Request)) +│ │ │ │ │ │ └─ resp(代码生成器相关响应对象(Response)) +│ │ │ │ │ └─ service(代码生成器相关业务接口及实现类) +│ │ │ │ └─ resources +│ │ │ │ ├─ templates/generator(代码生成相关模板目录) +│ │ │ │ ├─ application.yml(代码生成配置文件) +│ │ │ │ └─ generator.properties(代码生成类型映射配置文件) +│ │ │ └─ test(测试相关代码目录) +│ │ └─ pom.xml +│ └─ pom.xml +├─ ysoft-common(公共模块,存放公共工具类,公共配置等) +│ ├─ src +│ │ ├─ main/java/top/ysoft/admin/common +│ │ │ ├─ config(公共配置) +│ │ │ ├─ constant(公共常量) +│ │ │ ├─ enums(公共枚举) +│ │ │ ├─ model(公共模型) +│ │ │ │ ├─ dto(公共 DTO(Data Transfer Object)) +│ │ │ │ ├─ req(公共请求对象(Request)) +│ │ │ │ └─ resp(公共响应对象(Response)) +│ │ │ └─ util(公共工具类) +│ │ └─ test(测试相关代码目录) +│ └─ pom.xml +├─ ysoft-extension(扩展模块) +│ ├─ ysoft-extension-schedule-server(任务调度服务端模块,实际开发时如果是公司统一提供环境,可直接删除本模块) +│ │ ├─ src +│ │ │ ├─ main +│ │ │ │ ├─ java/top/ysoft/admin/extension/schedule +│ │ │ │ │ └─ ScheduleServerApplication.java(任务调度服务端启动程序) +│ │ │ │ └─ resources +│ │ │ │ ├─ config(核心配置目录) +│ │ │ │ │ ├─ application-dev.yml(开发环境配置文件) +│ │ │ │ │ ├─ application-prod.yml(生产环境配置文件) +│ │ │ │ │ └─ application.yml(通用配置文件) +│ │ │ │ ├─ db/changelog(Liquibase 数据脚本配置目录) +│ │ │ │ │ ├─ mysql(MySQL 数据库初始 SQL 脚本目录) +│ │ │ │ │ ├─ postgresql(PostgreSQL 数据库初始 SQL 脚本目录) +│ │ │ │ │ └─ db.changelog-master.yaml(Liquibase 变更记录文件) +│ │ │ │ └─ logback-spring.xml(日志配置文件) +│ │ │ └─ test(测试相关代码目录) +│ │ └─ pom.xml +│ └─ pom.xml +├─ .github(GitHub 相关配置目录,实际开发时直接删除) +├─ .idea +│ └─ icon.png(IDEA 项目图标,实际开发时直接删除) +├─ .image(截图目录,实际开发时直接删除) +├─ .style(代码格式、License文件头相关配置目录,实际开发时根据需要取舍,删除时注意删除 spotless 插件配置) +├─ .gitignore(Git 忽略文件相关配置文件) +├─ docker(项目部署相关配置目录,实际开发时可备份后直接删除) +├─ LICENSE(开源协议文件) +├─ CHANGELOG.md(更新日志文件,实际开发时直接删除) +├─ README.md(项目 README 文件,实际开发时替换为真实内容) +├─ lombok.config(Lombok 全局配置文件) +└─ pom.xml(包含版本锁定及全局插件相关配置) +``` + +## 贡献指南 + +ContiNew Admin 致力于提供开箱即用,持续舒适的开发体验。作为一个开源项目,Creator 的初心是希望 ContiNew Admin 依托开源协作模式,提升技术透明度、放大集体智慧、共创优秀实践,源源不断地为企业级项目开发提供助力。 + +我们非常欢迎广大社区用户为 ContiNew Admin **贡献(开发,测试、文档、答疑等)** 或优化代码,欢迎各位感兴趣的小伙伴儿,[添加微信](https://ysoft.top/support.html) 讨论或认领任务。 + +### 分支说明 + +ContiNew Admin 的分支目前分为下个大版本的开发分支和上个大版本的维护分支,PR 前请注意对应分支是否处于维护状态,版本支持情况请查看 [更新日志/版本支持](https://ysoft.top/admin/other/changelog.html#%E7%89%88%E6%9C%AC%E6%94%AF%E6%8C%81)。 + +| 分支 | 说明 | +| ----- | ------------------------------------------------------------ | +| dev | 开发分支,默认为下个大版本的 SNAPSHOT 版本,接受新功能或新功能优化 PR | +| x.x.x | 维护分支,在 vx.x.x 版本维护期终止前(一般为下个大版本发布前),用于修复上个版本的 Bug,只接受已有功能修复,不接受新功能 PR | + +### 贡献代码 + +如果您想提交新功能或优化现有代码,可以按照以下步骤操作: + +1. 首先,在 Gitee 或 Github 上将项目 fork 到您自己的仓库 +2. 然后,将 fork 过来的项目(即您的项目)克隆到本地 +3. 切换到当前仍在维护的分支(请务必充分了解分支使用说明,可进群联系维护者确认) +4. 开始修改代码,修改完成后,将代码 commit 并 push 到您的远程仓库 +5. 在 Gitee 或 Github 上新建 pull request(pr),选择好源和目标,按模板要求填写说明信息后提交即可(多多参考 [已批准合并的 pr 记录](https://github.com/ysoft-org/ysoft-admin/pulls?q=is%3Apr+is%3Amerged),会大大增加批准合并率) +6. 最后,耐心等待维护者合并您的请求即可 + +请记住,如果您有任何疑问或需要帮助,我们将随时提供支持。 + +> [!IMPORTANT] +> 欢迎大家为 ContiNew Admin 贡献代码,我们非常感谢您的支持!为了更好地管理项目,维护者有一些要求: +> +> 1. 请确保代码、配置文件的结构和命名规范良好,完善的代码注释甚至包括接口文档参数示例,并遵循阿里巴巴的 《Java开发手册(黄山版)》 中的代码规范,保证代码质量和可维护性 +> 2. 在提交代码前,请按照 [Angular 提交规范](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular) 编写 commit 的 message(建议在 IntelliJ IDEA 中下载并安装 Git Commit Template 插件,以便按照规范进行 commit) +> 3. 提交代码之前,请关闭所有代码窗口,执行 `mvn compile` 命令(代码格式化插件会在项目编译时对全局代码进行格式修正),编译通过后,不要再打开查看任何代码窗口,直接提交即可,以免不同的 IDE 配置会自动进行代码格式化 + +## 反馈交流 + +欢迎各位小伙伴儿扫描下方二维码加入项目交流群,与项目维护团队及其他大佬用户实时交流讨论。 + +
+ 二维码 +
+ +## 鸣谢 + +### 鸣谢 + +感谢参与贡献的每一位小伙伴🥰 + + + contributors + + +### 特别鸣谢 + +- 感谢 JetBrains 提供的 非商业开源软件开发授权 +- 感谢 MyBatis PlusSa-TokenJetCacheCrane4jKnife4jHutool 等开源组件作者为国内开源世界作出的贡献 +- 感谢项目使用或未使用到的每一款开源组件,致敬各位开源先驱 :fire: + +## License + +- 遵循 Apache-2.0 开源许可协议 +- Copyright © 2022-present Charles7c + +## GitHub Star 趋势 + +![GitHub Star 趋势](https://starchart.cc/charles7c/ysoft-admin.svg) \ No newline at end of file diff --git a/docker/continew-admin/Dockerfile b/docker/continew-admin/Dockerfile new file mode 100644 index 0000000..2647684 --- /dev/null +++ b/docker/continew-admin/Dockerfile @@ -0,0 +1,14 @@ +FROM openjdk:17 + +MAINTAINER Charles7c charles7c@126.com + +ARG JAR_FILE=./bin/*.jar +COPY ${JAR_FILE} /app/bin/app.jar +WORKDIR /app/bin + +ENTRYPOINT ["java", \ + "-jar", \ + "-XX:+UseZGC", \ + "-Djava.security.egd=file:/dev/./urandom", \ + "-Dspring.profiles.active=prod", \ + "app.jar"] \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..e8ee6f3 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,112 @@ +version: '3' +services: + mysql: + image: mysql:8.0.33 + restart: always + container_name: mysql + ports: + - '3306:3306' + environment: + TZ: Asia/Shanghai + MYSQL_ROOT_PASSWORD: 你的root用户密码 + # 初始化数据库(后续的初始化 SQL 会在这个库执行) + MYSQL_DATABASE: ysoft_admin + #MYSQL_USER: 你的数据库用户名 + #MYSQL_PASSWORD: 你的数据库密码 + volumes: + - /docker/mysql/conf/:/etc/mysql/conf.d/ + - /docker/mysql/data/:/var/lib/mysql/ + command: + --default-authentication-plugin=mysql_native_password + --character-set-server=utf8mb4 + --collation-server=utf8mb4_general_ci + --explicit_defaults_for_timestamp=true + --lower_case_table_names=1 +# postgresql: +# image: postgres:14.2 +# restart: always +# container_name: postgresql +# ports: +# - '5432:5432' +# environment: +# TZ: Asia/Shanghai +# POSTGRES_USER: 你的用户名 +# POSTGRES_PASSWORD: 你的用户密码 +# POSTGRES_DB: ysoft_admin +# volumes: +# - /docker/postgresql/data/:/var/lib/postgresql/data/ + redis: + image: redis:7.2.3 + restart: always + container_name: redis + ports: + - '6379:6379' + environment: + TZ: Asia/Shanghai + volumes: + - /docker/redis/conf/redis.conf:/usr/local/redis/config/redis.conf + - /docker/redis/data/:/data/ + - /docker/redis/logs/:/logs/ + command: 'redis-server /usr/local/redis/config/redis.conf --appendonly yes --requirepass 你的 Redis 密码' + ysoft-admin-server: + build: ./ysoft-admin + restart: always + container_name: ysoft-admin-server + ports: + - '18000:18000' + - '1789:1789' + environment: + TZ: Asia/Shanghai + DB_HOST: 172.17.0.1 + DB_PORT: 3306 + DB_USER: 你的数据库用户名 + DB_PWD: 你的数据库密码 + DB_NAME: ysoft_admin + REDIS_HOST: 172.17.0.1 + REDIS_PORT: 6379 + REDIS_PWD: 你的 Redis 密码 + REDIS_DB: 0 + SCHEDULE_HOST: 172.17.0.1 + SCHEDULE_PORT: 1788 + SCHEDULE_TOKEN: 任务调度服务端 Token + volumes: + - /docker/ysoft-admin/config/:/app/config/ + - /docker/ysoft-admin/data/file/:/app/data/file/ + - /docker/ysoft-admin/logs/:/app/logs/ + - /docker/ysoft-admin/lib/:/app/lib/ + depends_on: + - redis + - mysql + schedule-server: + build: ./schedule-server + restart: always + container_name: ysoft-admin-schedule-server + ports: + - '18001:18001' + - '1788:1788' + environment: + TZ: Asia/Shanghai + DB_HOST: 172.17.0.1 + DB_PORT: 3306 + DB_USER: 你的数据库用户名 + DB_PWD: 你的数据库密码 + DB_NAME: ysoft_admin_job + volumes: + - /docker/schedule-server/logs/:/app/logs/ + depends_on: + - mysql + nginx: + image: nginx:1.25.3 + restart: always + container_name: nginx + ports: + - '80:80' + - '443:443' + environment: + TZ: Asia/Shanghai + volumes: + - /docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf + - /docker/nginx/cert/:/etc/nginx/cert/ + - /docker/nginx/logs/:/var/log/nginx/ + # 前端页面目录 + - /docker/ysoft-admin/html/:/usr/share/nginx/html/ diff --git a/docker/nginx/conf/nginx.conf b/docker/nginx/conf/nginx.conf new file mode 100644 index 0000000..c8c8b46 --- /dev/null +++ b/docker/nginx/conf/nginx.conf @@ -0,0 +1,112 @@ +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + # 限制 body 大小 + client_max_body_size 100m; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + # 后端项目 + upstream admin-server { + ip_hash; + server 172.17.0.1:18000; + } + + server { + listen 443 ssl; + server_name api.ysoft.top; + + # 证书直接存放 /docker/nginx/cert 目录下即可(更改证书名称即可,无需更改证书路径) + ssl_certificate /etc/nginx/cert/xxx.local.pem; # /etc/nginx/cert/ 为 docker 映射路径 不允许更改 + ssl_certificate_key /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 为 docker 映射路径 不允许更改 + ssl_session_timeout 5m; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + + location / { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_ignore_client_abort on; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://admin-server/; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + } + + # HTTP 请求 将转发到 HTTPS + server { + listen 80; + server_name api.ysoft.top; + rewrite ^ https://$http_host$request_uri? permanent; + } + + # 前端项目 + server { + listen 443 ssl; + server_name admin.ysoft.top; + + # 证书直接存放 /docker/nginx/cert 目录下即可(更改证书名称即可,无需更改证书路径) + ssl_certificate /etc/nginx/cert/xxx.local.pem; # /etc/nginx/cert/ 为 docker 映射路径 不允许更改 + ssl_certificate_key /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 为 docker 映射路径 不允许更改 + ssl_session_timeout 5m; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + index index.html index.htm; + # Nginx 部署时,POST 请求本地静态文件会返回 405 错误(Not Allowed) + # 用于解决一半 mock 数据,一半后端接口的情况 + error_page 405 =200 https://$host$request_uri; + } + + # /api/ 代理到后端(如果使用 /api/ 前缀代理而不使用 api 域名提供后端服务,可放开此配置) + #location /api/ { + # proxy_http_version 1.1; + # proxy_set_header Upgrade $http_upgrade; + # proxy_set_header Connection "upgrade"; + # proxy_ignore_client_abort on; + # proxy_set_header Host $http_host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_pass http://admin-server/; + #} + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + } + + # 将 HTTP 请求转发到 HTTPS + server { + listen 80; + server_name admin.ysoft.top; + rewrite ^ https://$http_host$request_uri? permanent; + } +} diff --git a/docker/redis/conf/redis.conf b/docker/redis/conf/redis.conf new file mode 100644 index 0000000..ad9246d --- /dev/null +++ b/docker/redis/conf/redis.conf @@ -0,0 +1,29 @@ +bind 0.0.0.0 +# redis 密码 +requirepass 你的 Redis 密码 + +# key 监听器配置 +# notify-keyspace-events Ex + +# 配置持久化文件存储路径 +dir ../data +# 配置rdb +# 15分钟内有至少1个key被更改则进行快照 +save 900 1 +# 5分钟内有至少10个key被更改则进行快照 +save 300 10 +# 1分钟内有至少10000个key被更改则进行快照 +save 60 10000 +# 开启压缩 +rdbcompression yes +# rdb文件名 用默认的即可 +dbfilename dump.rdb + +# 开启aof +appendonly yes +# 文件名 +appendfilename "appendonly.aof" +# 持久化策略,no:不同步,everysec:每秒一次,always:总是同步,速度比较慢 +# appendfsync always +appendfsync everysec +# appendfsync no diff --git a/docker/redis/data/README.md b/docker/redis/data/README.md new file mode 100644 index 0000000..a7ce096 --- /dev/null +++ b/docker/redis/data/README.md @@ -0,0 +1 @@ +Redis 数据存储目录,请确保赋予了读写权限,否则将无法写入数据 \ No newline at end of file diff --git a/docker/schedule-server/Dockerfile b/docker/schedule-server/Dockerfile new file mode 100644 index 0000000..2db96c1 --- /dev/null +++ b/docker/schedule-server/Dockerfile @@ -0,0 +1,14 @@ +FROM openjdk:17 + +MAINTAINER Charles7c charles7c@126.com + +ARG JAR_FILE=*.jar +COPY ${JAR_FILE} /app/bin/app.jar +WORKDIR /app/bin + +ENTRYPOINT ["java", \ + "-jar", \ + "-XX:+UseZGC", \ + "-Djava.security.egd=file:/dev/./urandom", \ + "-Dspring.profiles.active=prod", \ + "app.jar"] \ No newline at end of file diff --git a/logs/2025-03-18/ysoft-admin.2025-03-18.0.log.gz b/logs/2025-03-18/ysoft-admin.2025-03-18.0.log.gz new file mode 100644 index 0000000..0239669 Binary files /dev/null and b/logs/2025-03-18/ysoft-admin.2025-03-18.0.log.gz differ diff --git a/logs/2025-12-29/ysoft-admin.2025-12-29.0.log.gz b/logs/2025-12-29/ysoft-admin.2025-12-29.0.log.gz new file mode 100644 index 0000000..a8a9487 Binary files /dev/null and b/logs/2025-12-29/ysoft-admin.2025-12-29.0.log.gz differ diff --git a/logs/2025-12-30/ysoft-admin.2025-12-30.0.log.gz b/logs/2025-12-30/ysoft-admin.2025-12-30.0.log.gz new file mode 100644 index 0000000..5adecc0 Binary files /dev/null and b/logs/2025-12-30/ysoft-admin.2025-12-30.0.log.gz differ diff --git a/logs/2026-01-13/ysoft-admin.2026-01-13.0.log.gz b/logs/2026-01-13/ysoft-admin.2026-01-13.0.log.gz new file mode 100644 index 0000000..fcff430 Binary files /dev/null and b/logs/2026-01-13/ysoft-admin.2026-01-13.0.log.gz differ diff --git a/logs/continew-admin-schedule-server.log b/logs/continew-admin-schedule-server.log new file mode 100644 index 0000000..e69de29 diff --git a/logs/continew-admin.log b/logs/continew-admin.log new file mode 100644 index 0000000..e69de29 diff --git a/logs/ysoft-admin.log b/logs/ysoft-admin.log new file mode 100644 index 0000000..e61ac1e --- /dev/null +++ b/logs/ysoft-admin.log @@ -0,0 +1,307 @@ +2026-01-19 10:20:42 INFO [background-preinit] org.hibernate.validator.internal.util.Version - HV000001: Hibernate Validator 8.0.1.Final +2026-01-19 10:20:42 INFO [main] top.ysoft.admin.YsoftAdminApplication - Starting YsoftAdminApplication using Java 21.0.6 with PID 31392 (D:\ysoft\ysoft-admin\ysoft-webapi\target\classes started by jingy in D:\ysoft\ysoft-admin) +2026-01-19 10:20:42 INFO [main] top.ysoft.admin.YsoftAdminApplication - The following 2 profiles are active: "generator", "prod" +2026-01-19 10:20:44 INFO [main] o.s.d.r.config.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode +2026-01-19 10:20:44 INFO [main] o.s.d.r.config.RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode. +2026-01-19 10:20:44 INFO [main] o.s.d.r.config.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 74 ms. Found 0 Redis repository interfaces. +2026-01-19 10:20:44 WARN [main] org.mybatis.spring.mapper.ClassPathMapperScanner - No MyBatis mapper was found in '[top.ysoft.admin]' package. Please check your configuration. +2026-01-19 10:20:45 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'cn.crane4j.spring.boot.config.Crane4jAutoConfiguration' of type [cn.crane4j.spring.boot.config.Crane4jAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). The currently created BeanPostProcessor [namedComponentAliasProcessor] is declared through a non-static factory method on that class; consider declaring it as static instead. +2026-01-19 10:20:45 DEBUG [main] t.c.s.e.c.a.CrudRestControllerAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Extension-CRUD REST Controller' completed initialization. +2026-01-19 10:20:45 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'top.continew.starter.extension.crud.autoconfigure.CrudRestControllerAutoConfiguration' of type [top.continew.starter.extension.crud.autoconfigure.CrudRestControllerAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [namedComponentAliasProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE. +2026-01-19 10:20:45 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'crudApiAnnotationInterceptor' of type [top.continew.starter.extension.crud.aop.CrudApiAnnotationInterceptor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [namedComponentAliasProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE. +2026-01-19 10:20:45 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'crudApiAnnotationAdvisor' of type [top.continew.starter.extension.crud.aop.CrudApiAnnotationAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [namedComponentAliasProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE. +2026-01-19 10:20:45 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'com.alicp.jetcache.anno.config.JetCacheProxyConfiguration' of type [com.alicp.jetcache.anno.config.JetCacheProxyConfiguration$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [namedComponentAliasProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE. +2026-01-19 10:20:45 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'com.alicp.jetcache.anno.config.CommonConfiguration' of type [com.alicp.jetcache.anno.config.CommonConfiguration$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [namedComponentAliasProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE. +2026-01-19 10:20:45 WARN [main] io.undertow.websockets.jsr - UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used +2026-01-19 10:20:45 INFO [main] io.undertow.servlet - Initializing Spring embedded WebApplicationContext +2026-01-19 10:20:45 INFO [main] o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 3017 ms +2026-01-19 10:20:45 DEBUG [main] t.c.s.w.autoconfigure.trace.TraceAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Web-Trace' completed initialization. +2026-01-19 10:20:45 DEBUG [main] t.c.starter.log.autoconfigure.LogAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Log-Interceptor' completed initialization. +2026-01-19 10:20:45 DEBUG [main] t.c.s.web.autoconfigure.cors.CorsAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Web-CorsFilter' completed initialization. +2026-01-19 10:20:45 DEBUG [main] t.c.s.d.m.a.MybatisPlusAutoConfiguration - [ContiNew Starter] - Auto Configuration 'MyBatis Plus' completed initialization. +2026-01-19 10:20:45 DEBUG [main] t.c.s.e.d.a.DataPermissionAutoConfiguration - [ContiNew Starter] - Auto Configuration 'DataPermission' completed initialization. +2026-01-19 10:20:45 DEBUG [main] t.c.s.s.c.autoconfigure.CryptoAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Security-Crypto' completed initialization. +2026-01-19 10:20:45 DEBUG [main] t.c.s.d.m.a.i.MyBatisPlusIdGeneratorConfiguration - [ContiNew Starter] - Auto Configuration 'MyBatis Plus-IdGenerator-CosId' completed initialization. +2026-01-19 10:20:46 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.peopleBranch.model.entity.BranchRuleDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:20:46 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.system.model.entity.MessageUserDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:20:46 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.system.model.entity.RoleMenuDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:20:46 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.system.model.entity.RuleRelationDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:20:46 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... +2026-01-19 10:20:48 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.p6spy.engine.wrapper.ConnectionWrapper@765d67dd +2026-01-19 10:20:48 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. +2026-01-19 10:20:49 INFO [main] liquibase.changelog - Reading from ysoft_admin.DATABASECHANGELOG +2026-01-19 10:20:49 INFO [main] liquibase.changelog - Reading from ysoft_admin.DATABASECHANGELOG +2026-01-19 10:20:49 INFO [main] liquibase.util - UPDATE SUMMARY +2026-01-19 10:20:49 INFO [main] liquibase.util - Run: 0 +2026-01-19 10:20:49 INFO [main] liquibase.util - Previously run: 0 +2026-01-19 10:20:49 INFO [main] liquibase.util - Filtered out: 0 +2026-01-19 10:20:49 INFO [main] liquibase.util - ------------------------------- +2026-01-19 10:20:49 INFO [main] liquibase.util - Total change sets: 0 +2026-01-19 10:20:49 INFO [main] liquibase.util - Update summary generated +2026-01-19 10:20:50 INFO [main] liquibase.lockservice - Successfully released change log lock +2026-01-19 10:20:50 INFO [main] liquibase.command - Command execution complete +2026-01-19 10:20:50 DEBUG [main] t.c.s.s.p.a.PasswordEncoderAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Security-PasswordEncoder' completed initialization. +2026-01-19 10:20:50 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.system.model.entity.RoleDeptDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:20:50 DEBUG [main] t.c.s.c.a.threadpool.ThreadPoolAutoConfiguration - [ContiNew Starter] - Auto Configuration 'TaskExecutor' completed initialization. +2026-01-19 10:20:50 DEBUG [main] t.c.s.a.j.autoconfigure.JustAuthAutoConfiguration - [ContiNew Starter] - Auto Configuration 'JustAuth' completed initialization. +2026-01-19 10:20:50 DEBUG [main] t.c.s.a.j.autoconfigure.JustAuthAutoConfiguration - [ContiNew Starter] - Auto Configuration 'JustAuth-AuthStateCache-Redis' completed initialization. +2026-01-19 10:20:51 INFO [main] c.anji.captcha.service.impl.CaptchaServiceFactory - supported-captchaCache-service:[local] +2026-01-19 10:20:51 INFO [main] c.anji.captcha.service.impl.CaptchaServiceFactory - supported-captchaTypes-service:[clickWord, default, blockPuzzle] +2026-01-19 10:20:51 DEBUG [main] t.c.s.c.b.a.c.BehaviorCaptchaCacheAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Captcha-Behavior-Cache-Redis' completed initialization. +2026-01-19 10:20:51 DEBUG [main] t.c.s.c.b.a.BehaviorCaptchaAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Captcha-Behavior' completed initialization. +2026-01-19 10:20:51 INFO [main] com.anji.captcha.util.ImageUtils - 初始化底图:[SLIDING_BLOCK=[Ljava.lang.String;@2aec09a3, ORIGINAL=[Ljava.lang.String;@79864d6, PIC_CLICK=[Ljava.lang.String;@472d7f34] +2026-01-19 10:20:51 INFO [main] c.a.c.service.impl.BlockPuzzleCaptchaServiceImpl - --->>>初始化验证码底图<<<---blockPuzzle +2026-01-19 10:20:51 DEBUG [main] t.c.s.c.g.a.GraphicCaptchaAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Captcha-Graphic' completed initialization. +2026-01-19 10:20:51 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.equipment.model.entity.PeopleUpperDeviceDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:20:52 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.post.model.entity.PeoplePostDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:20:52 DEBUG [main] t.c.s.j.j.autoconfigure.JacksonAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Jackson' completed initialization. +2026-01-19 10:20:53 DEBUG [main] t.c.s.s.l.a.RateLimiterAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Security-RateLimiter' completed initialization. +2026-01-19 10:20:53 DEBUG [main] t.c.s.c.r.autoconfigure.RedissonAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Redisson' completed initialization. +2026-01-19 10:20:53 INFO [main] org.redisson.Version - Redisson 3.41.0 +2026-01-19 10:20:53 INFO [redisson-netty-1-6] org.redisson.connection.ConnectionsHolder - 1 connections initialized for 121.199.16.218/121.199.16.218:6379 +2026-01-19 10:20:54 INFO [redisson-netty-1-19] org.redisson.connection.ConnectionsHolder - 24 connections initialized for 121.199.16.218/121.199.16.218:6379 +2026-01-19 10:20:54 DEBUG [main] t.c.s.w.a.response.GlobalResponseAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Web-Global Response' completed initialization. +2026-01-19 10:20:54 DEBUG [main] t.c.s.a.autoconfigure.SpringDocAutoConfiguration - [ContiNew Starter] - Auto Configuration 'ApiDoc' completed initialization. +2026-01-19 10:20:54 DEBUG [main] t.c.s.a.s.autoconfigure.SaTokenAutoConfiguration - [ContiNew Starter] - Auto Configuration 'SaToken' completed initialization. +2026-01-19 10:20:54 DEBUG [main] t.c.s.w.autoconfigure.mvc.WebMvcAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Web MVC' completed initialization. +2026-01-19 10:20:55 DEBUG [main] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - 264 mappings in 'requestMappingHandlerMapping' +2026-01-19 10:20:55 INFO [main] c.c.extension.spring.Crane4jApplicationContext - install container lifecycle processor [containerInstanceLifecycleProcessor] +2026-01-19 10:20:55 INFO [main] c.c.extension.spring.Crane4jApplicationContext - install container lifecycle processor [containerRegisterLogger] +2026-01-19 10:20:55 INFO [main] c.c.extension.spring.Crane4jApplicationContext - install container lifecycle processor [springCacheableContainerProcessor] +2026-01-19 10:20:55 INFO [main] c.c.extension.spring.BeanMethodContainerRegistrar - register method container factory [cn.crane4j.core.support.container.CacheableMethodContainerFactory] with name [cacheableMethodContainerFactory] +2026-01-19 10:20:55 DEBUG [main] t.c.s.a.s.a.dao.SaTokenDaoConfiguration - [ContiNew Starter] - Auto Configuration 'SaToken-Dao-Redis' completed initialization. +2026-01-19 10:20:55 INFO [main] c.a.jetcache.autoconfigure.AbstractCacheAutoInit - init cache area default , type= caffeine +2026-01-19 10:20:55 INFO [main] c.a.jetcache.autoconfigure.AbstractCacheAutoInit - init cache area default , type= redisson +2026-01-19 10:20:55 INFO [main] com.alicp.jetcache.support.DefaultMetricsManager - cache stat period at 15 MINUTES +2026-01-19 10:20:55 INFO [main] m.a.c.spring.redis.SpringRedisMachineIdDistributor - Distribute Remote instanceId:[InstanceId{instanceId=192.168.2.21:31392, stable=false}] - machineBit:[10] @ namespace:[ysoft-admin]. +2026-01-19 10:20:55 INFO [main] m.a.c.spring.redis.SpringRedisMachineIdDistributor - Distribute Remote machineState:[MachineState{machineId=15, lastTimeStamp=1768789255758}] - instanceId:[InstanceId{instanceId=192.168.2.21:31392, stable=false}] - machineBit:[10] @ namespace:[ysoft-admin]. +2026-01-19 10:20:56 DEBUG [main] t.c.s.c.j.autoconfigure.JetCacheAutoConfiguration - [ContiNew Starter] - Auto Configuration 'JetCache' completed initialization. +2026-01-19 10:20:56 DEBUG [main] t.c.s.c.autoconfigure.ValidatorAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Validator' completed initialization. +2026-01-19 10:20:56 DEBUG [main] t.c.s.m.mail.autoconfigure.MailAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Mail' completed initialization. +2026-01-19 10:20:56 DEBUG [main] t.c.s.m.w.autoconfigure.WebSocketAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Messaging-WebSocket' completed initialization. +2026-01-19 10:20:56 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register container bean [fileInfoContainer] bean from spring context, actual namespace is [FileInfo] +2026-01-19 10:20:56 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register container provider [crane4jApplicationContext] from spring context +2026-01-19 10:20:56 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register property mapping strategy manager [joinAsStringMappingStrategy](cn.crane4j.core.parser.handler.strategy.CollJoinAsStringMappingStrategy@cd7871e) from spring context +2026-01-19 10:20:56 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register property mapping strategy manager [overwriteNotNullMappingStrategy](cn.crane4j.core.parser.handler.strategy.OverwriteNotNullMappingStrategy@b14a2c) from spring context +2026-01-19 10:20:56 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register property mapping strategy manager [overwriteMappingStrategy](cn.crane4j.core.parser.handler.strategy.OverwriteMappingStrategy@b7da2c4) from spring context +2026-01-19 10:20:56 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register property mapping strategy manager [referenceMappingStrategy](cn.crane4j.core.parser.handler.strategy.ReferenceMappingStrategy@3a7c75d9) from spring context +2026-01-19 10:20:56 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [ProductNameAvatar] for method [public java.util.List top.ysoft.admin.equipment.service.impl.ProductServiceImpl.getProductNameAvatarList()], mapping type is [ONE_TO_ONE] +2026-01-19 10:20:56 WARN [main] c.c.c.s.container.DefaultMethodContainerFactory - The container method [public java.util.List top.ysoft.admin.equipment.service.impl.ProductServiceImpl.getProductNameAvatarList()] has no parameters, but it is set skipQueryIfKeyCollIsEmpty= true, Therefore, the method will never be called. Is this correct? +2026-01-19 10:20:56 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [PostName] for method [public java.lang.String top.ysoft.admin.post.service.impl.PostServiceImpl.getPostName(java.lang.Long)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:20:56 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [SpaceName] for method [public java.lang.String top.ysoft.admin.space.service.impl.SpaceServiceImpl.getSpaceNameById(java.lang.Long)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:20:56 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [UserRoleNameList] for method [public java.util.List top.ysoft.admin.system.service.impl.RoleServiceImpl.listNameByIds(java.util.List)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:20:56 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [UserRoleIdList] for method [public java.util.List top.ysoft.admin.system.service.impl.UserRoleServiceImpl.listRoleIdByUserId(java.lang.Long)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:20:56 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [UserNameList] for method [public java.util.List top.ysoft.admin.system.service.impl.UserServiceImpl.listNameByIds(java.util.List)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:20:56 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [UserName] for method [public java.lang.String top.ysoft.admin.system.service.impl.UserServiceImpl.userNameByIds(java.lang.Long)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:20:56 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [UserNickname] for method [public abstract java.lang.String top.ysoft.admin.common.service.CommonUserService.getNicknameById(java.lang.Long)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:20:56 DEBUG [main] t.c.s.j.j.autoconfigure.JacksonAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Jackson' completed initialization. +2026-01-19 10:20:56 DEBUG [main] t.c.s.c.a.threadpool.AsyncAutoConfiguration - [ContiNew Starter] - Auto Configuration 'AsyncConfigurer' completed initialization. +2026-01-19 10:20:57 DEBUG [main] top.continew.starter.log.LogFilter - Filter 'logFilter' configured for use +2026-01-19 10:20:57 INFO [main] io.undertow - starting server: Undertow - 2.3.17.Final +2026-01-19 10:20:57 INFO [main] org.xnio - XNIO version 3.8.16.Final +2026-01-19 10:20:57 INFO [main] org.xnio.nio - XNIO NIO Implementation Version 3.8.16.Final +2026-01-19 10:20:57 INFO [main] org.jboss.threads - JBoss Threads version 3.5.0.Final +2026-01-19 10:20:57 INFO [main] o.s.boot.web.embedded.undertow.UndertowWebServer - Undertow started on port 6609 (http) +2026-01-19 10:20:57 INFO [main] top.ysoft.admin.YsoftAdminApplication - Started YsoftAdminApplication in 15.268 seconds (process running for 17.266) +2026-01-19 10:20:57 INFO [main] c.c.s.b.c.Crane4jAutoConfiguration$Crane4jInitializer - crane4j component initialization completed. +2026-01-19 10:20:57 INFO [main] o.d.x.file.storage.core.FileStorageServiceBuilder - 加载 Amazon S3 存储平台:minio +2026-01-19 10:30:00 INFO [JetCacheDefaultExecutor] com.alicp.jetcache.support.StatInfoLogger - jetcache stat from 2026-01-19 10:20:55,520 to 2026-01-19 10:30:00,013 +cache | qps| rate| get| hit| fail| expire|avgLoadTime|maxLoadTime +------------+----------+-------+--------------+--------------+--------------+--------------+-----------+----------- +USER: | 0.00| 0.00%| 1| 0| 0| 0| 78.0| 78 +USER:_local | 0.00| 0.00%| 1| 0| 0| 0| 0.0| 0 +USER:_remote| 0.00| 0.00%| 1| 0| 0| 0| 0.0| 0 +------------+----------+-------+--------------+--------------+--------------+--------------+-----------+----------- + +2026-01-19 10:32:57 INFO [XNIO-1 task-3] io.undertow.servlet - Initializing Spring DispatcherServlet 'dispatcherServlet' +2026-01-19 10:32:57 INFO [XNIO-1 task-3] org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet' +2026-01-19 10:32:57 INFO [XNIO-1 task-3] org.springframework.web.servlet.DispatcherServlet - Completed initialization in 4 ms +2026-01-19 10:32:57 DEBUG [XNIO-1 task-2] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - [0][1165236278558273537] Mapped to top.ysoft.admin.controller.common.CaptchaController#getImageCaptcha() +2026-01-19 10:32:57 DEBUG [XNIO-1 task-3] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - [0][1165236278558273536] Mapped to top.ysoft.admin.controller.common.CommonController#listSiteOptionDict() +2026-01-19 10:32:57 INFO [XNIO-1 task-3] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236278558273536] [GET] /common/dict/option/site +2026-01-19 10:32:57 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236278558273537] [GET] /captcha/image +2026-01-19 10:32:58 INFO [XNIO-1 task-3] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236278558273536] [GET] /common/dict/option/site 200 233ms +2026-01-19 10:32:58 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236278558273537] [GET] /captcha/image 200 566ms +2026-01-19 10:33:03 DEBUG [XNIO-1 task-2] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - [0][1165236301333344256] Mapped to top.ysoft.admin.controller.common.CaptchaController#getImageCaptcha() +2026-01-19 10:33:03 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236301333344256] [GET] /captcha/image +2026-01-19 10:33:03 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236301333344256] [GET] /captcha/image 200 166ms +2026-01-19 10:33:10 DEBUG [XNIO-1 task-2] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - [0][1165236332908064768] Mapped to top.ysoft.admin.controller.common.CaptchaController#getImageCaptcha() +2026-01-19 10:33:10 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236332908064768] [GET] /captcha/image +2026-01-19 10:33:10 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236332908064768] [GET] /captcha/image 200 116ms +2026-01-19 10:33:16 DEBUG [XNIO-1 task-2] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - [0][1165236356425527296] Mapped to top.ysoft.admin.controller.common.CommonController#listSiteOptionDict() +2026-01-19 10:33:16 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236356425527296] [GET] /common/dict/option/site +2026-01-19 10:33:16 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236356425527296] [GET] /common/dict/option/site 200 31ms +2026-01-19 10:33:16 DEBUG [XNIO-1 task-2] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - [0][1165236356849152000] Mapped to top.ysoft.admin.controller.common.CaptchaController#getImageCaptcha() +2026-01-19 10:33:16 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236356849152000] [GET] /captcha/image +2026-01-19 10:33:16 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165236356849152000] [GET] /captcha/image 200 132ms +2026-01-19 10:33:28 INFO [SpringApplicationShutdownHook] m.a.c.spring.redis.SpringRedisMachineIdDistributor - Revert Remote [MachineState{machineId=15, lastTimeStamp=1768790008773}] instanceId:[InstanceId{instanceId=192.168.2.21:31392, stable=false}] @ namespace:[ysoft-admin]. +2026-01-19 10:33:28 INFO [SpringApplicationShutdownHook] io.undertow - stopping server: Undertow - 2.3.17.Final +2026-01-19 10:33:28 INFO [SpringApplicationShutdownHook] io.undertow.servlet - Destroying Spring FrameworkServlet 'dispatcherServlet' +2026-01-19 10:33:30 INFO [SpringApplicationShutdownHook] com.alicp.jetcache.support.DefaultMetricsManager - cache stat canceled +2026-01-19 10:33:30 INFO [SpringApplicationShutdownHook] c.c.extension.spring.Crane4jApplicationContext - global configuration has been destroyed. +2026-01-19 10:33:30 INFO [SpringApplicationShutdownHook] cn.crane4j.core.container.DefaultContainerManager - clear all cache for container manager +2026-01-19 10:33:30 INFO [SpringApplicationShutdownHook] o.dromara.x.file.storage.core.FileStorageService - 销毁存储平台 minio 成功 +2026-01-19 10:33:30 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... +2026-01-19 10:33:31 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. +2026-01-19 10:33:31 WARN [SpringApplicationShutdownHook] o.s.beans.factory.support.DisposableBeanAdapter - Invocation of destroy method failed on bean with name 'methodResultAutoOperateAdvisor': org.springframework.beans.factory.BeanCreationNotAllowedException: Error creating bean with name 'methodResultAutoOperateSupport': Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!) +2026-01-19 10:33:31 WARN [SpringApplicationShutdownHook] o.s.beans.factory.support.DisposableBeanAdapter - Invocation of destroy method failed on bean with name 'methodArgumentAutoOperateAdvisor': org.springframework.beans.factory.BeanCreationNotAllowedException: Error creating bean with name 'argAutoOperateProxyMethodFactory': Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!) +2026-01-19 10:33:34 INFO [background-preinit] org.hibernate.validator.internal.util.Version - HV000001: Hibernate Validator 8.0.1.Final +2026-01-19 10:33:34 INFO [main] top.ysoft.admin.YsoftAdminApplication - Starting YsoftAdminApplication using Java 21.0.6 with PID 19420 (D:\ysoft\ysoft-admin\ysoft-webapi\target\classes started by jingy in D:\ysoft\ysoft-admin) +2026-01-19 10:33:34 INFO [main] top.ysoft.admin.YsoftAdminApplication - The following 2 profiles are active: "generator", "prod" +2026-01-19 10:33:35 INFO [main] o.s.d.r.config.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode +2026-01-19 10:33:35 INFO [main] o.s.d.r.config.RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode. +2026-01-19 10:33:35 INFO [main] o.s.d.r.config.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 73 ms. Found 0 Redis repository interfaces. +2026-01-19 10:33:35 WARN [main] org.mybatis.spring.mapper.ClassPathMapperScanner - No MyBatis mapper was found in '[top.ysoft.admin]' package. Please check your configuration. +2026-01-19 10:33:36 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'cn.crane4j.spring.boot.config.Crane4jAutoConfiguration' of type [cn.crane4j.spring.boot.config.Crane4jAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). The currently created BeanPostProcessor [namedComponentAliasProcessor] is declared through a non-static factory method on that class; consider declaring it as static instead. +2026-01-19 10:33:36 DEBUG [main] t.c.s.e.c.a.CrudRestControllerAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Extension-CRUD REST Controller' completed initialization. +2026-01-19 10:33:36 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'top.continew.starter.extension.crud.autoconfigure.CrudRestControllerAutoConfiguration' of type [top.continew.starter.extension.crud.autoconfigure.CrudRestControllerAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [namedComponentAliasProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE. +2026-01-19 10:33:36 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'crudApiAnnotationInterceptor' of type [top.continew.starter.extension.crud.aop.CrudApiAnnotationInterceptor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [namedComponentAliasProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE. +2026-01-19 10:33:36 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'crudApiAnnotationAdvisor' of type [top.continew.starter.extension.crud.aop.CrudApiAnnotationAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [namedComponentAliasProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE. +2026-01-19 10:33:36 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'com.alicp.jetcache.anno.config.JetCacheProxyConfiguration' of type [com.alicp.jetcache.anno.config.JetCacheProxyConfiguration$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [namedComponentAliasProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE. +2026-01-19 10:33:36 WARN [main] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'com.alicp.jetcache.anno.config.CommonConfiguration' of type [com.alicp.jetcache.anno.config.CommonConfiguration$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [namedComponentAliasProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE. +2026-01-19 10:33:36 WARN [main] io.undertow.websockets.jsr - UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used +2026-01-19 10:33:36 INFO [main] io.undertow.servlet - Initializing Spring embedded WebApplicationContext +2026-01-19 10:33:36 INFO [main] o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 2272 ms +2026-01-19 10:33:36 DEBUG [main] t.c.s.w.autoconfigure.trace.TraceAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Web-Trace' completed initialization. +2026-01-19 10:33:36 DEBUG [main] t.c.starter.log.autoconfigure.LogAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Log-Interceptor' completed initialization. +2026-01-19 10:33:36 DEBUG [main] t.c.s.web.autoconfigure.cors.CorsAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Web-CorsFilter' completed initialization. +2026-01-19 10:33:36 DEBUG [main] t.c.s.d.m.a.MybatisPlusAutoConfiguration - [ContiNew Starter] - Auto Configuration 'MyBatis Plus' completed initialization. +2026-01-19 10:33:36 DEBUG [main] t.c.s.e.d.a.DataPermissionAutoConfiguration - [ContiNew Starter] - Auto Configuration 'DataPermission' completed initialization. +2026-01-19 10:33:36 DEBUG [main] t.c.s.s.c.autoconfigure.CryptoAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Security-Crypto' completed initialization. +2026-01-19 10:33:36 DEBUG [main] t.c.s.d.m.a.i.MyBatisPlusIdGeneratorConfiguration - [ContiNew Starter] - Auto Configuration 'MyBatis Plus-IdGenerator-CosId' completed initialization. +2026-01-19 10:33:37 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.peopleBranch.model.entity.BranchRuleDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:33:37 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.system.model.entity.MessageUserDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:33:37 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.system.model.entity.RoleMenuDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:33:37 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.system.model.entity.RuleRelationDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:33:37 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... +2026-01-19 10:33:38 INFO [main] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.p6spy.engine.wrapper.ConnectionWrapper@25291901 +2026-01-19 10:33:38 INFO [main] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. +2026-01-19 10:33:39 INFO [main] liquibase.changelog - Reading from ysoft_admin.DATABASECHANGELOG +2026-01-19 10:33:39 INFO [main] liquibase.changelog - Reading from ysoft_admin.DATABASECHANGELOG +2026-01-19 10:33:39 INFO [main] liquibase.util - UPDATE SUMMARY +2026-01-19 10:33:39 INFO [main] liquibase.util - Run: 0 +2026-01-19 10:33:39 INFO [main] liquibase.util - Previously run: 0 +2026-01-19 10:33:39 INFO [main] liquibase.util - Filtered out: 0 +2026-01-19 10:33:39 INFO [main] liquibase.util - ------------------------------- +2026-01-19 10:33:39 INFO [main] liquibase.util - Total change sets: 0 +2026-01-19 10:33:39 INFO [main] liquibase.util - Update summary generated +2026-01-19 10:33:39 INFO [main] liquibase.lockservice - Successfully released change log lock +2026-01-19 10:33:39 INFO [main] liquibase.command - Command execution complete +2026-01-19 10:33:39 DEBUG [main] t.c.s.s.p.a.PasswordEncoderAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Security-PasswordEncoder' completed initialization. +2026-01-19 10:33:39 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.system.model.entity.RoleDeptDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:33:39 DEBUG [main] t.c.s.c.a.threadpool.ThreadPoolAutoConfiguration - [ContiNew Starter] - Auto Configuration 'TaskExecutor' completed initialization. +2026-01-19 10:33:39 DEBUG [main] t.c.s.a.j.autoconfigure.JustAuthAutoConfiguration - [ContiNew Starter] - Auto Configuration 'JustAuth' completed initialization. +2026-01-19 10:33:39 DEBUG [main] t.c.s.a.j.autoconfigure.JustAuthAutoConfiguration - [ContiNew Starter] - Auto Configuration 'JustAuth-AuthStateCache-Redis' completed initialization. +2026-01-19 10:33:40 INFO [main] c.anji.captcha.service.impl.CaptchaServiceFactory - supported-captchaCache-service:[local] +2026-01-19 10:33:40 INFO [main] c.anji.captcha.service.impl.CaptchaServiceFactory - supported-captchaTypes-service:[clickWord, default, blockPuzzle] +2026-01-19 10:33:40 DEBUG [main] t.c.s.c.b.a.c.BehaviorCaptchaCacheAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Captcha-Behavior-Cache-Redis' completed initialization. +2026-01-19 10:33:40 DEBUG [main] t.c.s.c.b.a.BehaviorCaptchaAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Captcha-Behavior' completed initialization. +2026-01-19 10:33:40 INFO [main] com.anji.captcha.util.ImageUtils - 初始化底图:[SLIDING_BLOCK=[Ljava.lang.String;@7a174e4b, ORIGINAL=[Ljava.lang.String;@7a8316fa, PIC_CLICK=[Ljava.lang.String;@46a5aff] +2026-01-19 10:33:40 INFO [main] c.a.c.service.impl.BlockPuzzleCaptchaServiceImpl - --->>>初始化验证码底图<<<---blockPuzzle +2026-01-19 10:33:40 DEBUG [main] t.c.s.c.g.a.GraphicCaptchaAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Captcha-Graphic' completed initialization. +2026-01-19 10:33:40 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.equipment.model.entity.PeopleUpperDeviceDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:33:40 WARN [main] c.b.mybatisplus.core.injector.DefaultSqlInjector - class top.ysoft.admin.post.model.entity.PeoplePostDO ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method. +2026-01-19 10:33:41 DEBUG [main] t.c.s.j.j.autoconfigure.JacksonAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Jackson' completed initialization. +2026-01-19 10:33:41 DEBUG [main] t.c.s.s.l.a.RateLimiterAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Security-RateLimiter' completed initialization. +2026-01-19 10:33:41 DEBUG [main] t.c.s.c.r.autoconfigure.RedissonAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Redisson' completed initialization. +2026-01-19 10:33:41 INFO [main] org.redisson.Version - Redisson 3.41.0 +2026-01-19 10:33:41 INFO [redisson-netty-1-6] org.redisson.connection.ConnectionsHolder - 1 connections initialized for 121.199.16.218/121.199.16.218:6379 +2026-01-19 10:33:42 INFO [redisson-netty-1-19] org.redisson.connection.ConnectionsHolder - 24 connections initialized for 121.199.16.218/121.199.16.218:6379 +2026-01-19 10:33:42 DEBUG [main] t.c.s.w.a.response.GlobalResponseAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Web-Global Response' completed initialization. +2026-01-19 10:33:42 DEBUG [main] t.c.s.a.autoconfigure.SpringDocAutoConfiguration - [ContiNew Starter] - Auto Configuration 'ApiDoc' completed initialization. +2026-01-19 10:33:43 DEBUG [main] t.c.s.a.s.autoconfigure.SaTokenAutoConfiguration - [ContiNew Starter] - Auto Configuration 'SaToken' completed initialization. +2026-01-19 10:33:43 DEBUG [main] t.c.s.w.autoconfigure.mvc.WebMvcAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Web MVC' completed initialization. +2026-01-19 10:33:43 DEBUG [main] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - 264 mappings in 'requestMappingHandlerMapping' +2026-01-19 10:33:43 INFO [main] c.c.extension.spring.Crane4jApplicationContext - install container lifecycle processor [containerInstanceLifecycleProcessor] +2026-01-19 10:33:43 INFO [main] c.c.extension.spring.Crane4jApplicationContext - install container lifecycle processor [containerRegisterLogger] +2026-01-19 10:33:43 INFO [main] c.c.extension.spring.Crane4jApplicationContext - install container lifecycle processor [springCacheableContainerProcessor] +2026-01-19 10:33:43 INFO [main] c.c.extension.spring.BeanMethodContainerRegistrar - register method container factory [cn.crane4j.core.support.container.CacheableMethodContainerFactory] with name [cacheableMethodContainerFactory] +2026-01-19 10:33:43 DEBUG [main] t.c.s.a.s.a.dao.SaTokenDaoConfiguration - [ContiNew Starter] - Auto Configuration 'SaToken-Dao-Redis' completed initialization. +2026-01-19 10:33:43 INFO [main] c.a.jetcache.autoconfigure.AbstractCacheAutoInit - init cache area default , type= caffeine +2026-01-19 10:33:43 INFO [main] c.a.jetcache.autoconfigure.AbstractCacheAutoInit - init cache area default , type= redisson +2026-01-19 10:33:43 INFO [main] com.alicp.jetcache.support.DefaultMetricsManager - cache stat period at 15 MINUTES +2026-01-19 10:33:43 INFO [main] m.a.c.spring.redis.SpringRedisMachineIdDistributor - Distribute Remote instanceId:[InstanceId{instanceId=192.168.2.21:19420, stable=false}] - machineBit:[10] @ namespace:[ysoft-admin]. +2026-01-19 10:33:43 INFO [main] m.a.c.spring.redis.SpringRedisMachineIdDistributor - Distribute Remote machineState:[MachineState{machineId=15, lastTimeStamp=1768790023737}] - instanceId:[InstanceId{instanceId=192.168.2.21:19420, stable=false}] - machineBit:[10] @ namespace:[ysoft-admin]. +2026-01-19 10:33:44 DEBUG [main] t.c.s.c.j.autoconfigure.JetCacheAutoConfiguration - [ContiNew Starter] - Auto Configuration 'JetCache' completed initialization. +2026-01-19 10:33:44 DEBUG [main] t.c.s.c.autoconfigure.ValidatorAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Validator' completed initialization. +2026-01-19 10:33:44 DEBUG [main] t.c.s.m.mail.autoconfigure.MailAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Mail' completed initialization. +2026-01-19 10:33:44 DEBUG [main] t.c.s.m.w.autoconfigure.WebSocketAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Messaging-WebSocket' completed initialization. +2026-01-19 10:33:44 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register container bean [fileInfoContainer] bean from spring context, actual namespace is [FileInfo] +2026-01-19 10:33:44 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register container provider [crane4jApplicationContext] from spring context +2026-01-19 10:33:44 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register property mapping strategy manager [joinAsStringMappingStrategy](cn.crane4j.core.parser.handler.strategy.CollJoinAsStringMappingStrategy@48f38142) from spring context +2026-01-19 10:33:44 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register property mapping strategy manager [overwriteNotNullMappingStrategy](cn.crane4j.core.parser.handler.strategy.OverwriteNotNullMappingStrategy@16e693c1) from spring context +2026-01-19 10:33:44 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register property mapping strategy manager [overwriteMappingStrategy](cn.crane4j.core.parser.handler.strategy.OverwriteMappingStrategy@43a2b00c) from spring context +2026-01-19 10:33:44 INFO [main] c.c.extension.spring.Crane4jApplicationContext - register property mapping strategy manager [referenceMappingStrategy](cn.crane4j.core.parser.handler.strategy.ReferenceMappingStrategy@5192abb4) from spring context +2026-01-19 10:33:44 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [ProductNameAvatar] for method [public java.util.List top.ysoft.admin.equipment.service.impl.ProductServiceImpl.getProductNameAvatarList()], mapping type is [ONE_TO_ONE] +2026-01-19 10:33:44 WARN [main] c.c.c.s.container.DefaultMethodContainerFactory - The container method [public java.util.List top.ysoft.admin.equipment.service.impl.ProductServiceImpl.getProductNameAvatarList()] has no parameters, but it is set skipQueryIfKeyCollIsEmpty= true, Therefore, the method will never be called. Is this correct? +2026-01-19 10:33:44 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [PostName] for method [public java.lang.String top.ysoft.admin.post.service.impl.PostServiceImpl.getPostName(java.lang.Long)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:33:44 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [SpaceName] for method [public java.lang.String top.ysoft.admin.space.service.impl.SpaceServiceImpl.getSpaceNameById(java.lang.Long)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:33:44 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [UserRoleNameList] for method [public java.util.List top.ysoft.admin.system.service.impl.RoleServiceImpl.listNameByIds(java.util.List)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:33:44 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [UserRoleIdList] for method [public java.util.List top.ysoft.admin.system.service.impl.UserRoleServiceImpl.listRoleIdByUserId(java.lang.Long)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:33:44 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [UserNameList] for method [public java.util.List top.ysoft.admin.system.service.impl.UserServiceImpl.listNameByIds(java.util.List)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:33:44 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [UserName] for method [public java.lang.String top.ysoft.admin.system.service.impl.UserServiceImpl.userNameByIds(java.lang.Long)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:33:44 INFO [main] c.c.c.s.container.MethodInvokerContainerCreator - create method invoker container [UserNickname] for method [public abstract java.lang.String top.ysoft.admin.common.service.CommonUserService.getNicknameById(java.lang.Long)], mapping type is [ORDER_OF_KEYS] +2026-01-19 10:33:44 DEBUG [main] t.c.s.j.j.autoconfigure.JacksonAutoConfiguration - [ContiNew Starter] - Auto Configuration 'Jackson' completed initialization. +2026-01-19 10:33:44 DEBUG [main] t.c.s.c.a.threadpool.AsyncAutoConfiguration - [ContiNew Starter] - Auto Configuration 'AsyncConfigurer' completed initialization. +2026-01-19 10:33:44 DEBUG [main] top.continew.starter.log.LogFilter - Filter 'logFilter' configured for use +2026-01-19 10:33:44 INFO [main] io.undertow - starting server: Undertow - 2.3.17.Final +2026-01-19 10:33:44 INFO [main] org.xnio - XNIO version 3.8.16.Final +2026-01-19 10:33:44 INFO [main] org.xnio.nio - XNIO NIO Implementation Version 3.8.16.Final +2026-01-19 10:33:44 INFO [main] org.jboss.threads - JBoss Threads version 3.5.0.Final +2026-01-19 10:33:44 INFO [main] o.s.boot.web.embedded.undertow.UndertowWebServer - Undertow started on port 6609 (http) +2026-01-19 10:33:44 INFO [main] top.ysoft.admin.YsoftAdminApplication - Started YsoftAdminApplication in 11.083 seconds (process running for 11.84) +2026-01-19 10:33:44 INFO [main] c.c.s.b.c.Crane4jAutoConfiguration$Crane4jInitializer - crane4j component initialization completed. +2026-01-19 10:33:45 INFO [main] o.d.x.file.storage.core.FileStorageServiceBuilder - 加载 Amazon S3 存储平台:minio +2026-01-19 10:45:00 INFO [JetCacheDefaultExecutor] com.alicp.jetcache.support.StatInfoLogger - jetcache stat from 2026-01-19 10:33:43,615 to 2026-01-19 10:45:00,012 +cache | qps| rate| get| hit| fail| expire|avgLoadTime|maxLoadTime +------------+----------+-------+--------------+--------------+--------------+--------------+-----------+----------- +USER: | 0.00|100.00%| 1| 1| 0| 0| 0.0| 0 +USER:_local | 0.00| 0.00%| 1| 0| 0| 0| 0.0| 0 +USER:_remote| 0.00|100.00%| 1| 1| 0| 0| 0.0| 0 +------------+----------+-------+--------------+--------------+--------------+--------------+-----------+----------- + +2026-01-19 11:00:00 INFO [JetCacheDefaultExecutor] com.alicp.jetcache.support.StatInfoLogger - jetcache stat from 2026-01-19 10:45:00,012 to 2026-01-19 11:00:00,008 +cache | qps| rate| get| hit| fail| expire|avgLoadTime|maxLoadTime +------------+----------+-------+--------------+--------------+--------------+--------------+-----------+----------- +USER: | 0.00| 0.00%| 0| 0| 0| 0| 0.0| 0 +USER:_local | 0.00| 0.00%| 0| 0| 0| 0| 0.0| 0 +USER:_remote| 0.00| 0.00%| 0| 0| 0| 0| 0.0| 0 +------------+----------+-------+--------------+--------------+--------------+--------------+-----------+----------- + +2026-01-19 11:15:00 INFO [JetCacheDefaultExecutor] com.alicp.jetcache.support.StatInfoLogger - jetcache stat from 2026-01-19 11:00:00,008 to 2026-01-19 11:15:00,002 +cache | qps| rate| get| hit| fail| expire|avgLoadTime|maxLoadTime +------------+----------+-------+--------------+--------------+--------------+--------------+-----------+----------- +USER: | 0.00| 0.00%| 0| 0| 0| 0| 0.0| 0 +USER:_local | 0.00| 0.00%| 0| 0| 0| 0| 0.0| 0 +USER:_remote| 0.00| 0.00%| 0| 0| 0| 0| 0.0| 0 +------------+----------+-------+--------------+--------------+--------------+--------------+-----------+----------- + +2026-01-19 11:23:29 INFO [XNIO-1 task-2] io.undertow.servlet - Initializing Spring DispatcherServlet 'dispatcherServlet' +2026-01-19 11:23:29 INFO [XNIO-1 task-2] org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet' +2026-01-19 11:23:29 INFO [XNIO-1 task-2] org.springframework.web.servlet.DispatcherServlet - Completed initialization in 51 ms +2026-01-19 11:23:29 DEBUG [XNIO-1 task-3] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - [0][1165248992630353920] Mapped to top.ysoft.admin.controller.common.CaptchaController#getImageCaptcha() +2026-01-19 11:23:29 DEBUG [XNIO-1 task-2] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - [0][1165248992630353921] Mapped to top.ysoft.admin.controller.common.CommonController#listSiteOptionDict() +2026-01-19 11:23:29 INFO [XNIO-1 task-3] t.continew.starter.log.interceptor.LogInterceptor - [0][1165248992630353920] [GET] /captcha/image +2026-01-19 11:23:29 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165248992630353921] [GET] /common/dict/option/site +2026-01-19 11:23:29 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165248992630353921] [GET] /common/dict/option/site 200 498ms +2026-01-19 11:23:29 INFO [XNIO-1 task-3] t.continew.starter.log.interceptor.LogInterceptor - [0][1165248992630353920] [GET] /captcha/image 200 498ms +2026-01-19 11:23:31 DEBUG [XNIO-1 task-2] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - [0][1165249001434198016] Mapped to top.ysoft.admin.controller.common.CaptchaController#getImageCaptcha() +2026-01-19 11:23:31 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165249001434198016] [GET] /captcha/image +2026-01-19 11:23:31 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165249001434198016] [GET] /captcha/image 200 100ms +2026-01-19 11:23:38 DEBUG [XNIO-1 task-2] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - [0][1165249032904060928] Mapped to top.ysoft.admin.controller.common.CaptchaController#getImageCaptcha() +2026-01-19 11:23:38 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165249032904060928] [GET] /captcha/image +2026-01-19 11:23:38 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165249032904060928] [GET] /captcha/image 200 98ms +2026-01-19 11:23:46 DEBUG [XNIO-1 task-2] t.c.s.e.c.a.CrudRequestMappingHandlerMapping - [0][1165249066890506240] Mapped to top.ysoft.admin.controller.common.CaptchaController#getImageCaptcha() +2026-01-19 11:23:46 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165249066890506240] [GET] /captcha/image +2026-01-19 11:23:47 INFO [XNIO-1 task-2] t.continew.starter.log.interceptor.LogInterceptor - [0][1165249066890506240] [GET] /captcha/image 200 209ms +2026-01-19 11:24:27 INFO [SpringApplicationShutdownHook] m.a.c.spring.redis.SpringRedisMachineIdDistributor - Revert Remote [MachineState{machineId=15, lastTimeStamp=1768793067999}] instanceId:[InstanceId{instanceId=192.168.2.21:19420, stable=false}] @ namespace:[ysoft-admin]. +2026-01-19 11:24:28 INFO [SpringApplicationShutdownHook] io.undertow - stopping server: Undertow - 2.3.17.Final +2026-01-19 11:24:28 INFO [SpringApplicationShutdownHook] io.undertow.servlet - Destroying Spring FrameworkServlet 'dispatcherServlet' +2026-01-19 11:24:30 INFO [SpringApplicationShutdownHook] com.alicp.jetcache.support.DefaultMetricsManager - cache stat canceled +2026-01-19 11:24:30 INFO [SpringApplicationShutdownHook] c.c.extension.spring.Crane4jApplicationContext - global configuration has been destroyed. +2026-01-19 11:24:30 INFO [SpringApplicationShutdownHook] cn.crane4j.core.container.DefaultContainerManager - clear all cache for container manager +2026-01-19 11:24:30 INFO [SpringApplicationShutdownHook] o.dromara.x.file.storage.core.FileStorageService - 销毁存储平台 minio 成功 +2026-01-19 11:24:30 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... +2026-01-19 11:24:30 INFO [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. +2026-01-19 11:24:30 WARN [SpringApplicationShutdownHook] o.s.beans.factory.support.DisposableBeanAdapter - Invocation of destroy method failed on bean with name 'methodResultAutoOperateAdvisor': org.springframework.beans.factory.BeanCreationNotAllowedException: Error creating bean with name 'methodResultAutoOperateSupport': Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!) +2026-01-19 11:24:30 WARN [SpringApplicationShutdownHook] o.s.beans.factory.support.DisposableBeanAdapter - Invocation of destroy method failed on bean with name 'methodArgumentAutoOperateAdvisor': org.springframework.beans.factory.BeanCreationNotAllowedException: Error creating bean with name 'argAutoOperateProxyMethodFactory': Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!) diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..1135ca1 --- /dev/null +++ b/lombok.config @@ -0,0 +1,7 @@ +config.stopBubbling=true +lombok.toString.callSuper=CALL +lombok.equalsAndHashCode.callSuper=CALL +clear lombok.val.flagUsage +lombok.val.flagUsage=ERROR +clear lombok.accessors.flagUsage +lombok.accessors.flagUsage=ERROR \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f2772bf --- /dev/null +++ b/pom.xml @@ -0,0 +1,264 @@ + + + 4.0.0 + + + top.continew + continew-starter + 2.9.0 + + + top.ysoft + ysoft-admin + ${revision} + pom + 园区管理系统 + https://github.com/ysoft-org/ysoft-admin + + + ysoft-webapi + ysoft-module-system + ysoft-plugin + ysoft-common + ysoft-extension + + + + + 3.6.0-SNAPSHOT + + + + + + + + top.ysoft + ysoft-webapi + ${revision} + + + + + top.ysoft + ysoft-module-system + ${revision} + + + + + top.ysoft + ysoft-common + ${revision} + + + + + top.ysoft + ysoft-plugin-schedule + ${revision} + + + + + top.ysoft + ysoft-plugin-open + ${revision} + + + + + top.ysoft + ysoft-plugin-generator + ${revision} + + + + + + + + cn.hutool + hutool-all + + + + + org.projectlombok + lombok + true + + + + + org.mapstruct + mapstruct + 1.5.5.Final + + + org.mapstruct + mapstruct-processor + 1.5.5.Final + + + + + commons-fileupload + commons-fileupload + 1.4 + + + + + commons-io + commons-io + 2.8.0 + + + + com.google.guava + guava + 29.0-jre + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + -parameters + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + true + + + + + com.diffplug.spotless + spotless-maven-plugin + + + compile + + apply + + + + + + + + .style/p3c-codestyle.xml + + + .style/license-header + + + + + + + org.codehaus.mojo + flatten-maven-plugin + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + + flatten + + + + flatten-clean + clean + + clean + + + + + + + + + + + sonar + + https://sonarcloud.io + charles7c + Charles7c_ysoft-admin + ${project.groupId}:${project.artifactId} + + + false + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + + + verify + + sonar + + + + + + + + + + + + + huawei-mirror + HuaweiCloud Mirror + https://mirrors.huaweicloud.com/repository/maven/ + + + ali-mirror + AliYun Mirror + https://maven.aliyun.com/repository/public/ + + + + + + + huawei-mirror + HuaweiCloud Mirror + https://mirrors.huaweicloud.com/repository/maven/ + + + ali-mirror + AliYun Mirror + https://maven.aliyun.com/repository/public/ + + + diff --git a/target/spotless-index b/target/spotless-index new file mode 100644 index 0000000..ef3166c --- /dev/null +++ b/target/spotless-index @@ -0,0 +1 @@ +nO9TaZSKPf1ylImyltTZX/68jGy+GEEGnIFWrxOoIa0= diff --git a/wms-common/.flattened-pom.xml b/wms-common/.flattened-pom.xml new file mode 100644 index 0000000..111aea8 --- /dev/null +++ b/wms-common/.flattened-pom.xml @@ -0,0 +1,123 @@ + + + 4.0.0 + + top.ysoft + ysoft-admin + 3.6.0-SNAPSHOT + + ysoft-common + 3.6.0-SNAPSHOT + 公共模块(存放公共工具类,公共配置等) + + + GNU LESSER GENERAL PUBLIC LICENSE + http://www.gnu.org/licenses/lgpl.html + + + + + me.ahoo.cosid + cosid-spring-boot-starter + + + me.ahoo.cosid + cosid-spring-redis + + + org.dromara.sms4j + sms4j-spring-boot-starter + + + org.dromara.x-file-storage + x-file-storage-spring + 2.2.1 + + + software.amazon.awssdk + s3 + 2.25.0 + + + org.freemarker + freemarker + + + com.mysql + mysql-connector-j + + + top.continew + continew-starter-extension-crud-mp + + + top.continew + continew-starter-auth-satoken + + + spring-boot-starter-web + org.springframework.boot + + + + + top.continew + continew-starter-auth-justauth + + + top.continew + continew-starter-cache-jetcache + + + top.continew + continew-starter-extension-datapermission-mp + + + top.continew + continew-starter-messaging-websocket + + + spring-boot-starter-web + org.springframework.boot + + + + + top.continew + continew-starter-messaging-mail + + + top.continew + continew-starter-captcha-graphic + + + top.continew + continew-starter-captcha-behavior + + + top.continew + continew-starter-security-limiter + + + top.continew + continew-starter-security-crypto + + + top.continew + continew-starter-security-mask + + + top.continew + continew-starter-security-password + + + top.continew + continew-starter-json-jackson + + + org.springframework + spring-test + + + diff --git a/wms-common/pom.xml b/wms-common/pom.xml new file mode 100644 index 0000000..32e7af6 --- /dev/null +++ b/wms-common/pom.xml @@ -0,0 +1,159 @@ + + + 4.0.0 + + top.ysoft + ysoft-admin + ${revision} + + + ysoft-common + 公共模块(存放公共工具类,公共配置等) + + + + + me.ahoo.cosid + cosid-spring-boot-starter + + + me.ahoo.cosid + cosid-spring-redis + + + + + org.dromara.sms4j + sms4j-spring-boot-starter + + + + + org.dromara.x-file-storage + x-file-storage-spring + 2.2.1 + + + + software.amazon.awssdk + s3 + 2.25.0 + + + + + org.freemarker + freemarker + + + + + com.mysql + mysql-connector-j + + + + + top.continew + continew-starter-extension-crud-mp + + + + + top.continew + continew-starter-auth-satoken + + + org.springframework.boot + spring-boot-starter-web + + + + + + + top.continew + continew-starter-auth-justauth + + + + + top.continew + continew-starter-cache-jetcache + + + + + top.continew + continew-starter-extension-datapermission-mp + + + + + top.continew + continew-starter-messaging-websocket + + + org.springframework.boot + spring-boot-starter-web + + + + + + + top.continew + continew-starter-messaging-mail + + + + + top.continew + continew-starter-captcha-graphic + + + + + top.continew + continew-starter-captcha-behavior + + + + + top.continew + continew-starter-security-limiter + + + + + top.continew + continew-starter-security-crypto + + + + + top.continew + continew-starter-security-mask + + + + + top.continew + continew-starter-security-password + + + + + top.continew + continew-starter-json-jackson + + + + org.springframework + spring-test + + + + \ No newline at end of file diff --git a/wms-common/src/main/java/top/wms/admin/common/config/doc/GlobalAuthenticationCustomizer.java b/wms-common/src/main/java/top/wms/admin/common/config/doc/GlobalAuthenticationCustomizer.java new file mode 100644 index 0000000..fb83c68 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/doc/GlobalAuthenticationCustomizer.java @@ -0,0 +1,200 @@ +package top.ysoft.admin.common.config.doc; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.core.map.MapUtil; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springdoc.core.customizers.GlobalOpenApiCustomizer; +import org.springframework.aop.support.AopUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.bind.annotation.*; +import top.continew.starter.apidoc.autoconfigure.SpringDocExtensionProperties; +import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 全局鉴权参数定制器 + * + * @author echo + * @since 2024/12/31 13:36 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class GlobalAuthenticationCustomizer implements GlobalOpenApiCustomizer { + + private final SpringDocExtensionProperties properties; + private final SaTokenExtensionProperties saTokenExtensionProperties; + private final ApplicationContext context; + private final AntPathMatcher pathMatcher = new AntPathMatcher(); + + /** + * 定制 OpenAPI 文档 + * + * @param openApi 当前 OpenAPI 对象 + */ + @Override + public void customise(OpenAPI openApi) { + if (MapUtil.isEmpty(openApi.getPaths())) { + return; + } + + // 收集需要排除的路径(包括 Sa-Token 配置中的排除路径和 @SaIgnore 注解路径) + Set excludedPaths = collectExcludedPaths(); + + // 遍历所有路径,为需要鉴权的路径添加安全认证配置 + openApi.getPaths().forEach((path, pathItem) -> { + if (isPathExcluded(path, excludedPaths)) { + // 路径在排除列表中,跳过处理 + return; + } + // 为路径添加安全认证参数 + addAuthenticationParameters(pathItem); + }); + } + + /** + * 收集所有需要排除的路径 + * + * @return 排除路径集合 + */ + private Set collectExcludedPaths() { + Set excludedPaths = new HashSet<>(); + excludedPaths.addAll(Arrays.asList(saTokenExtensionProperties.getSecurity().getExcludes())); + excludedPaths.addAll(resolveSaIgnorePaths()); + return excludedPaths; + } + + /** + * 为路径项添加认证参数 + * + * @param pathItem 当前路径项 + */ + private void addAuthenticationParameters(PathItem pathItem) { + Components components = properties.getComponents(); + if (components == null || MapUtil.isEmpty(components.getSecuritySchemes())) { + return; + } + Map securitySchemes = components.getSecuritySchemes(); + List schemeNames = securitySchemes.values().stream().map(SecurityScheme::getName).toList(); + pathItem.readOperations().forEach(operation -> { + SecurityRequirement securityRequirement = new SecurityRequirement(); + schemeNames.forEach(securityRequirement::addList); + operation.addSecurityItem(securityRequirement); + }); + } + + /** + * 解析所有带有 @SaIgnore 注解的路径 + * + * @return 被忽略的路径集合 + */ + private Set resolveSaIgnorePaths() { + // 获取所有标注 @RestController 的 Bean + Map controllers = context.getBeansWithAnnotation(RestController.class); + Set ignoredPaths = new HashSet<>(); + + // 遍历所有控制器,解析 @SaIgnore 注解路径 + controllers.values().forEach(controllerBean -> { + Class controllerClass = AopUtils.getTargetClass(controllerBean); + List classPaths = getClassPaths(controllerClass); + + // 类级别的 @SaIgnore 注解 + if (controllerClass.isAnnotationPresent(SaIgnore.class)) { + classPaths.forEach(classPath -> ignoredPaths.add(classPath + "/**")); + } + + // 方法级别的 @SaIgnore 注解 + Arrays.stream(controllerClass.getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(SaIgnore.class)) + .forEach(method -> ignoredPaths.addAll(combinePaths(classPaths, getMethodPaths(method)))); + }); + + return ignoredPaths; + } + + /** + * 获取类上的所有路径 + * + * @param controller 控制器类 + * @return 类路径列表 + */ + private List getClassPaths(Class controller) { + List classPaths = new ArrayList<>(); + // 处理 @RequestMapping 注解 + if (controller.isAnnotationPresent(RequestMapping.class)) { + RequestMapping mapping = controller.getAnnotation(RequestMapping.class); + classPaths.addAll(Arrays.asList(mapping.value())); + } + // 处理 @CrudRequestMapping 注解 + if (controller.isAnnotationPresent(CrudRequestMapping.class)) { + CrudRequestMapping mapping = controller.getAnnotation(CrudRequestMapping.class); + if (!mapping.value().isEmpty()) { + classPaths.add(mapping.value()); + } + } + return classPaths; + } + + /** + * 获取方法上的所有路径 + * + * @param method 控制器方法 + * @return 方法路径列表 + */ + private List getMethodPaths(Method method) { + List methodPaths = new ArrayList<>(); + + // 检查方法上的各种映射注解 + if (method.isAnnotationPresent(GetMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(GetMapping.class).value())); + } else if (method.isAnnotationPresent(PostMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(PostMapping.class).value())); + } else if (method.isAnnotationPresent(PutMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(PutMapping.class).value())); + } else if (method.isAnnotationPresent(DeleteMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(DeleteMapping.class).value())); + } else if (method.isAnnotationPresent(RequestMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(RequestMapping.class).value())); + } else if (method.isAnnotationPresent(PatchMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(PatchMapping.class).value())); + } + + return methodPaths; + } + + /** + * 组合类路径和方法路径 + * + * @param classPaths 类路径列表 + * @param methodPaths 方法路径列表 + * @return 完整路径集合 + */ + private Set combinePaths(List classPaths, List methodPaths) { + return classPaths.stream() + .flatMap(classPath -> methodPaths.stream().map(methodPath -> classPath + methodPath)) + .collect(Collectors.toSet()); + } + + /** + * 检查路径是否在排除列表中 + * + * @param path 当前路径 + * @param excludedPaths 排除路径集合,支持通配符 + * @return 是否匹配排除规则 + */ + private boolean isPathExcluded(String path, Set excludedPaths) { + return excludedPaths.stream().anyMatch(pattern -> pathMatcher.match(pattern, path)); + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/config/doc/GlobalDescriptionCustomizer.java b/wms-common/src/main/java/top/wms/admin/common/config/doc/GlobalDescriptionCustomizer.java new file mode 100644 index 0000000..81f440b --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/doc/GlobalDescriptionCustomizer.java @@ -0,0 +1,49 @@ +package top.ysoft.admin.common.config.doc; + +import cn.hutool.core.util.StrUtil; +import io.swagger.v3.oas.models.Operation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.springdoc.core.customizers.GlobalOperationCustomizer; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; + +import java.util.ArrayList; +import java.util.List; + +/** + * 全局描述定制器 - 处理 sa-token 的注解权限码 + * + * @author echo + * @since 2025/1/24 14:59 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class GlobalDescriptionCustomizer implements GlobalOperationCustomizer { + + @Override + public Operation customize(Operation operation, HandlerMethod handlerMethod) { + // 将 sa-token 注解数据添加到 operation 的描述中 + // 权限 + List noteList = new ArrayList<>(new OperationDescriptionCustomizer().getPermission(handlerMethod)); + + // 如果注解数据列表为空,直接返回原 operation + if (noteList.isEmpty()) { + return operation; + } + // 拼接注解数据为字符串 + String noteStr = StrUtil.join("
", noteList); + // 获取原描述 + String originalDescription = operation.getDescription(); + // 根据原描述是否为空,更新描述 + String newDescription = StringUtils.isNotEmpty(originalDescription) + ? originalDescription + "
" + noteStr + : noteStr; + + // 设置新描述 + operation.setDescription(newDescription); + return operation; + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/config/doc/OperationDescriptionCustomizer.java b/wms-common/src/main/java/top/wms/admin/common/config/doc/OperationDescriptionCustomizer.java new file mode 100644 index 0000000..a94bda1 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/doc/OperationDescriptionCustomizer.java @@ -0,0 +1,164 @@ +package top.ysoft.admin.common.config.doc; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaCheckRole; +import cn.dev33.satoken.annotation.SaMode; +import cn.hutool.core.text.CharSequenceUtil; +import org.springframework.web.method.HandlerMethod; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.extension.crud.annotation.CrudApi; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; + +/** + * Operation 描述定制器 处理 sa-token 鉴权标识符 + * + * @author echo + * @since 2024/6/14 11:18 + */ +public class OperationDescriptionCustomizer { + + /** + * 获取 sa-token 注解信息 + * + * @param handlerMethod 处理程序方法 + * @return 包含权限和角色校验信息的列表 + */ + public List getPermission(HandlerMethod handlerMethod) { + List values = new ArrayList<>(); + + // 获取权限校验信息 + String permissionInfo = getAnnotationInfo(handlerMethod, SaCheckPermission.class, "权限校验:"); + if (!permissionInfo.isEmpty()) { + values.add(permissionInfo); + } + + // 获取角色校验信息 + String roleInfo = getAnnotationInfo(handlerMethod, SaCheckRole.class, "角色校验:"); + if (!roleInfo.isEmpty()) { + values.add(roleInfo); + } + + // 处理 CrudRequestMapping 和 CrudApi 注解生成的权限信息 + String crudPermissionInfo = getCrudPermissionInfo(handlerMethod); + if (!crudPermissionInfo.isEmpty()) { + values.add(crudPermissionInfo); + } + return values; + } + + /** + * 获取类和方法上指定注解的信息 + * + * @param handlerMethod 处理程序方法 + * @param annotationClass 注解类 + * @param title 信息标题 + * @param 注解类型 + * @return 拼接好的注解信息字符串 + */ + @SuppressWarnings("unchecked") + private String getAnnotationInfo(HandlerMethod handlerMethod, + Class annotationClass, + String title) { + StringBuilder infoBuilder = new StringBuilder(); + + // 获取类上的注解 + A classAnnotation = handlerMethod.getBeanType().getAnnotation(annotationClass); + if (classAnnotation != null) { + appendAnnotationInfo(infoBuilder, "类:", classAnnotation); + } + + // 获取方法上的注解 + A methodAnnotation = handlerMethod.getMethodAnnotation(annotationClass); + if (methodAnnotation != null) { + appendAnnotationInfo(infoBuilder, "方法:", methodAnnotation); + } + + // 如果有注解信息,添加标题 + if (!infoBuilder.isEmpty()) { + infoBuilder.insert(0, "" + title + "
"); + } + + return infoBuilder.toString(); + } + + /** + * 拼接注解信息到 StringBuilder 中 + * + * @param builder 用于拼接信息的 StringBuilder + * @param prefix 前缀信息,如 "类:" 或 "方法:" + * @param annotation 注解对象 + */ + private void appendAnnotationInfo(StringBuilder builder, String prefix, Annotation annotation) { + String[] values = null; + SaMode mode = null; + String type = ""; + String[] orRole = new String[0]; + + if (annotation instanceof SaCheckPermission checkPermission) { + values = checkPermission.value(); + mode = checkPermission.mode(); + type = checkPermission.type(); + orRole = checkPermission.orRole(); + } else if (annotation instanceof SaCheckRole checkRole) { + values = checkRole.value(); + mode = checkRole.mode(); + type = checkRole.type(); + } + + if (values != null && mode != null) { + builder.append(""); + builder.append(prefix); + if (!type.isEmpty()) { + builder.append("(类型:").append(type).append(")"); + } + builder.append(getAnnotationNote(values, mode)); + if (orRole.length > 0) { + builder.append(" 或 角色校验(").append(getAnnotationNote(orRole, mode)).append(")"); + } + builder.append("
"); + } + } + + /** + * 根据注解的模式拼接注解值 + * + * @param values 注解的值数组 + * @param mode 注解的模式(AND 或 OR) + * @return 拼接好的注解值字符串 + */ + private String getAnnotationNote(String[] values, SaMode mode) { + if (mode.equals(SaMode.AND)) { + return String.join(" 且 ", values); + } else { + return String.join(" 或 ", values); + } + } + + /** + * 处理 CrudRequestMapping 和 CrudApi 注解生成的权限信息 + * + * @param handlerMethod 处理程序方法 + * @return 拼接好的权限信息字符串 + */ + private String getCrudPermissionInfo(HandlerMethod handlerMethod) { + CrudRequestMapping crudRequestMapping = handlerMethod.getBeanType().getAnnotation(CrudRequestMapping.class); + CrudApi crudApi = handlerMethod.getMethodAnnotation(CrudApi.class); + + if (crudRequestMapping == null || crudApi == null) { + return ""; + } + + String path = crudRequestMapping.value(); + String prefix = String.join(StringConstants.COLON, CharSequenceUtil.splitTrim(path, StringConstants.SLASH)); + Api api = crudApi.value(); + String apiName = Api.PAGE.equals(api) || Api.TREE.equals(api) ? Api.LIST.name() : api.name(); + String permission = "%s:%s".formatted(prefix, apiName.toLowerCase()); + + return "Crud 权限校验:
方法:" + permission + ""; + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/config/exception/GlobalExceptionHandler.java b/wms-common/src/main/java/top/wms/admin/common/config/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..660f780 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/exception/GlobalExceptionHandler.java @@ -0,0 +1,105 @@ +package top.ysoft.admin.common.config.exception; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.text.CharSequenceUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MultipartException; +import org.springframework.web.servlet.NoHandlerFoundException; +import top.continew.starter.core.exception.BadRequestException; +import top.continew.starter.core.exception.BusinessException; +import top.continew.starter.web.model.R; + +/** + * 全局异常处理器 + * + * @author Charles7c + * @author echo + * @since 2024/8/7 20:21 + */ +@Slf4j +@Order(99) +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 拦截业务异常 + */ + @ExceptionHandler(BusinessException.class) + public R handleBusinessException(BusinessException e, HttpServletRequest request) { + log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e); + return R.fail(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), e.getMessage()); + } + + /** + * 拦截自定义验证异常-错误请求 + */ + @ExceptionHandler(BadRequestException.class) + public R handleBadRequestException(BadRequestException e, HttpServletRequest request) { + log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e); + return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), e.getMessage()); + } + + /** + * 拦截校验异常-方法参数类型不匹配异常 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, + HttpServletRequest request) { + log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e); + return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), "参数 '%s' 类型不匹配".formatted(e.getName())); + } + + /** + * 拦截文件上传异常-超过上传大小限制 + */ + @ExceptionHandler(MultipartException.class) + public R handleMultipartException(MultipartException e, HttpServletRequest request) { + log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e); + String msg = e.getMessage(); + R defaultFail = R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), msg); + if (CharSequenceUtil.isBlank(msg)) { + return defaultFail; + } + String sizeLimit; + Throwable cause = e.getCause(); + if (null != cause) { + msg = msg.concat(cause.getMessage().toLowerCase()); + } + if (msg.contains("larger than")) { + sizeLimit = CharSequenceUtil.subAfter(msg, "larger than ", true); + } else if (msg.contains("size") && msg.contains("exceed")) { + sizeLimit = CharSequenceUtil.subBetween(msg, "the maximum size ", " for"); + } else { + return defaultFail; + } + return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), "请上传小于 %s 的文件".formatted(FileUtil + .readableFileSize(Long.parseLong(sizeLimit)))); + } + + /** + * 拦截请求 URL 不存在异常 + */ + @ExceptionHandler(NoHandlerFoundException.class) + public R handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) { + log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e); + return R.fail(String.valueOf(HttpStatus.NOT_FOUND.value()), "请求 URL '%s' 不存在".formatted(request + .getRequestURI())); + } + + /** + * 拦截不支持的 HTTP 请求方法异常 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public R handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) { + log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e); + return R.fail(String.valueOf(HttpStatus.METHOD_NOT_ALLOWED.value()), "请求方式 '%s' 不支持".formatted(e.getMethod())); + } +} \ No newline at end of file diff --git a/wms-common/src/main/java/top/wms/admin/common/config/exception/GlobalSaTokenExceptionHandler.java b/wms-common/src/main/java/top/wms/admin/common/config/exception/GlobalSaTokenExceptionHandler.java new file mode 100644 index 0000000..746f265 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/exception/GlobalSaTokenExceptionHandler.java @@ -0,0 +1,56 @@ +package top.ysoft.admin.common.config.exception; + +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotPermissionException; +import cn.dev33.satoken.exception.NotRoleException; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import top.continew.starter.web.model.R; + +/** + * 全局 SaToken 异常处理器 + * + * @author Charles7c + * @since 2024/8/7 20:21 + */ +@Slf4j +@Order(99) +@RestControllerAdvice +public class GlobalSaTokenExceptionHandler { + + /** + * 认证异常-登录认证 + */ + @ExceptionHandler(NotLoginException.class) + public R handleNotLoginException(NotLoginException e, HttpServletRequest request) { + log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e); + String errorMsg = switch (e.getType()) { + case NotLoginException.KICK_OUT -> "您已被踢下线"; + case NotLoginException.BE_REPLACED_MESSAGE -> "您已被顶下线"; + default -> "您的登录状态已过期,请重新登录"; + }; + return R.fail(String.valueOf(HttpStatus.UNAUTHORIZED.value()), errorMsg); + } + + /** + * 认证异常-权限认证 + */ + @ExceptionHandler(NotPermissionException.class) + public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) { + log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e); + return R.fail(String.valueOf(HttpStatus.FORBIDDEN.value()), "没有访问权限,请联系管理员授权"); + } + + /** + * 认证异常-角色认证 + */ + @ExceptionHandler(NotRoleException.class) + public R handleNotRoleException(NotRoleException e, HttpServletRequest request) { + log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e); + return R.fail(String.valueOf(HttpStatus.FORBIDDEN.value()), "没有访问权限,请联系管理员授权"); + } +} \ No newline at end of file diff --git a/wms-common/src/main/java/top/wms/admin/common/config/mybatis/BCryptEncryptor.java b/wms-common/src/main/java/top/wms/admin/common/config/mybatis/BCryptEncryptor.java new file mode 100644 index 0000000..93c25b8 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/mybatis/BCryptEncryptor.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.common.config.mybatis; + +import org.springframework.security.crypto.password.PasswordEncoder; +import top.continew.starter.security.crypto.encryptor.IEncryptor; + +/** + * BCrypt 加/解密处理器(不可逆) + * + * @author Charles7c + * @since 2024/2/8 22:29 + */ +public class BCryptEncryptor implements IEncryptor { + + private final PasswordEncoder passwordEncoder; + + public BCryptEncryptor(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + } + + @Override + public String encrypt(String plaintext, String password, String publicKey) throws Exception { + return passwordEncoder.encode(plaintext); + } + + @Override + public String decrypt(String ciphertext, String password, String privateKey) throws Exception { + return ciphertext; + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/config/mybatis/DataPermissionMapper.java b/wms-common/src/main/java/top/wms/admin/common/config/mybatis/DataPermissionMapper.java new file mode 100644 index 0000000..193f969 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/mybatis/DataPermissionMapper.java @@ -0,0 +1,41 @@ +package top.ysoft.admin.common.config.mybatis; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import top.continew.starter.data.mp.base.BaseMapper; +import top.continew.starter.extension.datapermission.annotation.DataPermission; + +import java.util.List; + +/** + * 数据权限 Mapper 基类 + * + * @param 实体类 + * @author Charles7c + * @since 2023/9/3 21:50 + */ +public interface DataPermissionMapper extends BaseMapper { + + /** + * 根据 entity 条件,查询全部记录 + * + * @param queryWrapper 实体对象封装操作类(可以为 null) + * @return 全部记录 + */ + @Override + @DataPermission + List selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 根据 entity 条件,查询全部记录(并翻页) + * + * @param page 分页查询条件 + * @param queryWrapper 实体对象封装操作类(可以为 null) + * @return 全部记录(并翻页) + */ + @Override + @DataPermission + List selectList(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper); +} diff --git a/wms-common/src/main/java/top/wms/admin/common/config/mybatis/DefaultDataPermissionUserContextProvider.java b/wms-common/src/main/java/top/wms/admin/common/config/mybatis/DefaultDataPermissionUserContextProvider.java new file mode 100644 index 0000000..71c9131 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/mybatis/DefaultDataPermissionUserContextProvider.java @@ -0,0 +1,37 @@ +package top.ysoft.admin.common.config.mybatis; + +import cn.hutool.core.convert.Convert; +import top.ysoft.admin.common.context.UserContextHolder; +import top.continew.starter.extension.datapermission.enums.DataScope; +import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider; +import top.continew.starter.extension.datapermission.model.RoleContext; +import top.continew.starter.extension.datapermission.model.UserContext; + +import java.util.stream.Collectors; + +/** + * 数据权限用户上下文提供者 + * + * @author Charles7c + * @since 2023/12/21 21:19 + */ +public class DefaultDataPermissionUserContextProvider implements DataPermissionUserContextProvider { + + @Override + public boolean isFilter() { + return !UserContextHolder.isAdmin(); + } + + @Override + public UserContext getUserContext() { + top.ysoft.admin.common.context.UserContext context = UserContextHolder.getContext(); + UserContext userContext = new UserContext(); + userContext.setUserId(Convert.toStr(context.getId())); + userContext.setDeptId(Convert.toStr(context.getDeptId())); + userContext.setRoles(context.getRoles() + .stream() + .map(r -> new RoleContext(Convert.toStr(r.getId()), DataScope.valueOf(r.getDataScope().name()))) + .collect(Collectors.toSet())); + return userContext; + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/config/mybatis/MyBatisPlusMetaObjectHandler.java b/wms-common/src/main/java/top/wms/admin/common/config/mybatis/MyBatisPlusMetaObjectHandler.java new file mode 100644 index 0000000..8d85078 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/mybatis/MyBatisPlusMetaObjectHandler.java @@ -0,0 +1,96 @@ +package top.ysoft.admin.common.config.mybatis; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.time.LocalDateTime; + +/** + * MyBatis Plus 元对象处理器配置(插入或修改时自动填充) + * + * @author Charles7c + * @since 2022/12/22 19:52 + */ +public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler { + + /** + * 创建人 + */ + private static final String CREATE_USER = "createUser"; + /** + * 创建时间 + */ + private static final String CREATE_TIME = "createTime"; + /** + * 修改人 + */ + private static final String UPDATE_USER = "updateUser"; + /** + * 修改时间 + */ + private static final String UPDATE_TIME = "updateTime"; + + /** + * 插入数据时填充 + * + * @param metaObject 元对象 + */ + @Override + public void insertFill(MetaObject metaObject) { + if (null == metaObject) { + return; + } + Long createUser = UserContextHolder.getUserId(); + LocalDateTime createTime = LocalDateTime.now(); + if (metaObject.getOriginalObject() instanceof BaseDO baseDO) { + // 继承了 BaseDO 的类,填充创建信息字段 + baseDO.setCreateUser(ObjectUtil.defaultIfNull(baseDO.getCreateUser(), createUser)); + baseDO.setCreateTime(ObjectUtil.defaultIfNull(baseDO.getCreateTime(), createTime)); + } else { + // 未继承 BaseDO 的类,如存在创建信息字段则进行填充 + this.fillFieldValue(metaObject, CREATE_USER, createUser, false); + this.fillFieldValue(metaObject, CREATE_TIME, createTime, false); + } + } + + /** + * 修改数据时填充 + * + * @param metaObject 元对象 + */ + @Override + public void updateFill(MetaObject metaObject) { + if (null == metaObject) { + return; + } + Long updateUser = UserContextHolder.getUserId(); + LocalDateTime updateTime = LocalDateTime.now(); + if (metaObject.getOriginalObject() instanceof BaseDO baseDO) { + // 继承了 BaseDO 的类,填充修改信息 + baseDO.setUpdateUser(updateUser); + baseDO.setUpdateTime(updateTime); + } else { + // 未继承 BaseDO 的类,根据类中拥有的修改信息字段进行填充,不存在修改信息字段不进行填充 + this.fillFieldValue(metaObject, UPDATE_USER, updateUser, true); + this.fillFieldValue(metaObject, UPDATE_TIME, updateTime, true); + } + } + + /** + * 填充字段值 + * + * @param metaObject 元数据对象 + * @param fieldName 要填充的字段名 + * @param fillFieldValue 要填充的字段值 + * @param isOverride 如果字段值不为空,是否覆盖(true:覆盖;false:不覆盖) + */ + private void fillFieldValue(MetaObject metaObject, String fieldName, Object fillFieldValue, boolean isOverride) { + if (metaObject.hasSetter(fieldName)) { + Object fieldValue = metaObject.getValue(fieldName); + setFieldValByName(fieldName, null != fieldValue && !isOverride ? fieldValue : fillFieldValue, metaObject); + } + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/config/mybatis/MybatisPlusConfiguration.java b/wms-common/src/main/java/top/wms/admin/common/config/mybatis/MybatisPlusConfiguration.java new file mode 100644 index 0000000..09fe05f --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/mybatis/MybatisPlusConfiguration.java @@ -0,0 +1,41 @@ +package top.ysoft.admin.common.config.mybatis; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.password.PasswordEncoder; +import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider; + +/** + * MyBatis Plus 配置 + * + * @author Charles7c + * @since 2022/12/22 19:51 + */ +@Configuration +public class MybatisPlusConfiguration { + + /** + * 元对象处理器配置(插入或修改时自动填充) + */ + @Bean + public MetaObjectHandler metaObjectHandler() { + return new MyBatisPlusMetaObjectHandler(); + } + + /** + * 数据权限用户上下文提供者 + */ + @Bean + public DataPermissionUserContextProvider dataPermissionUserContextProvider() { + return new DefaultDataPermissionUserContextProvider(); + } + + /** + * BCrypt 加/解密处理器 + */ + @Bean + public BCryptEncryptor bCryptEncryptor(PasswordEncoder passwordEncoder) { + return new BCryptEncryptor(passwordEncoder); + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/config/properties/CaptchaProperties.java b/wms-common/src/main/java/top/wms/admin/common/config/properties/CaptchaProperties.java new file mode 100644 index 0000000..6a9a6f7 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/properties/CaptchaProperties.java @@ -0,0 +1,76 @@ +package top.ysoft.admin.common.config.properties; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 验证码配置属性 + * + * @author Charles7c + * @since 2022/12/11 13:35 + */ +@Data +@Component +@ConfigurationProperties(prefix = "captcha") +public class CaptchaProperties { + + /** + * 图形验证码过期时间 + */ + @Value("${continew-starter.captcha.graphic.expirationInMinutes}") + private long expirationInMinutes; + + /** + * 邮箱验证码配置 + */ + private CaptchaMail mail; + + /** + * 短信验证码配置 + */ + private CaptchaSms sms; + + /** + * 邮箱验证码配置 + */ + @Data + public static class CaptchaMail { + /** + * 内容长度 + */ + private int length; + + /** + * 过期时间 + */ + private long expirationInMinutes; + + /** + * 模板路径 + */ + private String templatePath; + } + + /** + * 短信验证码配置 + */ + @Data + public static class CaptchaSms { + /** + * 内容长度 + */ + private int length; + + /** + * 过期时间 + */ + private long expirationInMinutes; + + /** + * 模板 ID + */ + private String templateId; + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/config/properties/RsaProperties.java b/wms-common/src/main/java/top/wms/admin/common/config/properties/RsaProperties.java new file mode 100644 index 0000000..9346f9f --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/properties/RsaProperties.java @@ -0,0 +1,27 @@ +package top.ysoft.admin.common.config.properties; + +import cn.hutool.extra.spring.SpringUtil; + +/** + * RSA 配置属性 + * + * @author Zheng Jie(ELADMIN) + * @author Charles7c + * @since 2022/12/21 20:21 + */ +public class RsaProperties { + + /** + * 私钥 + */ + public static final String PRIVATE_KEY; + public static final String PUBLIC_KEY; + + static { + PRIVATE_KEY = SpringUtil.getProperty("continew-starter.security.crypto.private-key"); + PUBLIC_KEY = SpringUtil.getProperty("continew-starter.security.crypto.public-key"); + } + + private RsaProperties() { + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/config/websocket/WebSocketClientServiceImpl.java b/wms-common/src/main/java/top/wms/admin/common/config/websocket/WebSocketClientServiceImpl.java new file mode 100644 index 0000000..fab890d --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/config/websocket/WebSocketClientServiceImpl.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.common.config.websocket; + +import cn.dev33.satoken.stp.StpUtil; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.stereotype.Component; +import top.continew.starter.core.exception.BusinessException; +import top.continew.starter.messaging.websocket.core.WebSocketClientService; + +/** + * 当前登录用户 Provider + * + * @author Charles7c + * @since 2024/6/4 22:13 + */ +@Component +public class WebSocketClientServiceImpl implements WebSocketClientService { + + @Override + public String getClientId(ServletServerHttpRequest request) { + HttpServletRequest servletRequest = request.getServletRequest(); + String token = servletRequest.getParameter("token"); + if (null == StpUtil.getLoginIdByToken(token)) { + throw new BusinessException("登录已过期,请重新登录"); + } + return token; + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/constant/CacheConstants.java b/wms-common/src/main/java/top/wms/admin/common/constant/CacheConstants.java new file mode 100644 index 0000000..11fedc8 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/constant/CacheConstants.java @@ -0,0 +1,65 @@ +package top.ysoft.admin.common.constant; + +import top.continew.starter.core.constant.StringConstants; + +/** + * 缓存相关常量 + * + * @author Charles7c + * @since 2022/12/22 19:30 + */ +public class CacheConstants { + + /** + * 分隔符 + */ + public static final String DELIMITER = StringConstants.COLON; + + /** + * 验证码键前缀 + */ + public static final String CAPTCHA_KEY_PREFIX = "CAPTCHA" + DELIMITER; + + /** + * 用户缓存键前缀 + */ + public static final String USER_KEY_PREFIX = "USER" + DELIMITER; + + /** + * 角色菜单缓存键前缀 + */ + public static final String ROLE_MENU_KEY_PREFIX = "ROLE_MENU" + DELIMITER; + + /** + * 字典缓存键前缀 + */ + public static final String DICT_KEY_PREFIX = "DICT" + DELIMITER; + + /** + * 参数缓存键前缀 + */ + public static final String OPTION_KEY_PREFIX = "OPTION" + DELIMITER; + + /** + * 仪表盘缓存键前缀 + */ + public static final String DASHBOARD_KEY_PREFIX = "DASHBOARD" + DELIMITER; + + /** + * 用户密码错误次数缓存键前缀 + */ + public static final String USER_PASSWORD_ERROR_KEY_PREFIX = USER_KEY_PREFIX + "PASSWORD_ERROR" + DELIMITER; + + /** + * 数据导入临时会话key + */ + public static final String DATA_IMPORT_KEY = "SYSTEM" + DELIMITER + "DATA_IMPORT" + DELIMITER; + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config:"; + + private CacheConstants() { + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/constant/Constants.java b/wms-common/src/main/java/top/wms/admin/common/constant/Constants.java new file mode 100644 index 0000000..7d8f0c8 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/constant/Constants.java @@ -0,0 +1,202 @@ +package top.ysoft.admin.common.constant; + +/** + * 通用常量信息 + * + * @author dcsoft + */ +public class Constants { + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * www主域 + */ + public static final String WWW = "www."; + + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi:"; + + /** + * LDAP 远程方法调用 + */ + public static final String LOOKUP_LDAP = "ldap:"; + + /** + * LDAPS 远程方法调用 + */ + public static final String LOOKUP_LDAPS = "ldaps:"; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 成功标记 + */ + public static final Integer SUCCESS = 200; + + /** + * 失败标记 + */ + public static final Integer FAIL = 500; + + /** + * 登录成功状态 + */ + public static final String LOGIN_SUCCESS_STATUS = "0"; + + /** + * 登录失败状态 + */ + public static final String LOGIN_FAIL_STATUS = "1"; + + /** + * 登录成功 + */ + public static final String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + public static final String LOGOUT = "Logout"; + + /** + * 注册 + */ + public static final String REGISTER = "Register"; + + /** + * 登录失败 + */ + public static final String LOGIN_FAIL = "Error"; + + /** + * 当前记录起始索引 + */ + public static final String PAGE_NUM = "pageNum"; + + /** + * 每页显示记录数 + */ + public static final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public static final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public static final String IS_ASC = "isAsc"; + + /** + * 验证码有效期(分钟) + */ + public static final long CAPTCHA_EXPIRATION = 2; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = {"com.dcsoft"}; + + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = {"java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", + "org.springframework", "org.apache", "com.dcsoft.common.core.utils.file"}; + + /** + * 0 + */ + public static final String ZERO = "0"; + + /** + * 1 + */ + public static final String ONE = "1"; + + /** + * 2 + */ + public static final String TWO = "2"; + + /** + * 5 + */ + public static final String FIVE = "5"; + + /** + * 200 + */ + public static final String CODE = "200"; + + /** + * OK + */ + public static final String OK = "ok"; + + /** + * 宇泛门禁设备开关门状态1开门 + */ + public static final Integer ACCESS_CONTROL_ONE = 1; + + /** + * 宇泛门禁设备开关门状态2关门 + */ + public static final Integer ACCESS_CONTROL_TWO = 2; + + /** + * + */ + public static final String TTS_MOD_CONTENT = "未授权,请联系管理员"; + + /** + * + */ + public static final String DISPLAY_MOD_CONTENT = "未授权,请联系管理员"; + + /** + * 梯控结束时间 + */ + public static final String END_TIME = "2099-12-31 23:59:59"; + + /** + * 梯控起始可用时间 + */ + public static final String START_DAY_TIME = "00:00:00"; + + /** + * 梯控结束可用时间 + */ + public static final String END_DAY_TIME = "23:59:59"; + + /** + * 梯控星期 + */ + public static final String WEEK = "1 1 1 1 1 1 1"; + + public static final String CONTENT = "{\n" + " \"touser\": \"%\", \n" + " \"template_id\": \"wODO_BDj8n1grelUSGTNvpZbSnFdhzC3odWHek0brSM\",\n" + " \"url\": \"#\",\n" + " \"data\": {\n" + " \"first\": {\n" + " \"value\": \"广州腾讯科技有限公司\"\n" + " },\n" + " \"keyword1\": {\n" + " \"value\": \"广州腾讯科技有限公司\"\n" + " },\n" + " \"keyword2\": {\n" + " \"value\": \"2019年8月8日\"\n" + " }\n" + " }\n" + "}"; + +} diff --git a/wms-common/src/main/java/top/wms/admin/common/constant/ContainerConstants.java b/wms-common/src/main/java/top/wms/admin/common/constant/ContainerConstants.java new file mode 100644 index 0000000..475e922 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/constant/ContainerConstants.java @@ -0,0 +1,73 @@ +package top.ysoft.admin.common.constant; + +/** + * 数据源容器相关常量(Crane4j 数据填充组件使用) + * + * @author Charles7c + * @since 2024/1/20 12:33 + */ +public class ContainerConstants { + + /** + * 用户昵称 + */ + public static final String USER_NICKNAME = "UserNickname"; + + /** + * 文件信息 + */ + public static final String FILE_INFO = "FileInfo"; + + /** + * 用户角色 ID 列表 + */ + public static final String USER_ROLE_ID_LIST = "UserRoleIdList"; + + /** + * 用户角色名称列表 + */ + public static final String USER_ROLE_NAME_LIST = "UserRoleNameList"; + + /** + * 用户列表 + */ + public static final String USER_NAME_LIST = "UserNameList"; + + /** + * 用户名称 + */ + public static final String USER_NAME = "UserName"; + + /** + * 部门名称 + */ + public static final String BRANCH_NAME = "BranchName"; + + /** + * 空间名称 + */ + public static final String SPACE_NAME = "SpaceName"; + + /** + * 产品名称图片 + */ + public static final String PRODUCT_NAME_AVATAR = "ProductNameAvatar"; + + /** + * 设备名称 + */ + public static final String EQUIPMENT_NAME = "EquipmentName"; + + /** + * 人员名称 + */ + public static final String PEOPLE_NAME = "PeopleName"; + + /** + * 规则名称 + */ + public static final String RULE_NAME = "RuleName"; + + private ContainerConstants() { + } +} \ No newline at end of file diff --git a/wms-common/src/main/java/top/wms/admin/common/constant/RegexConstants.java b/wms-common/src/main/java/top/wms/admin/common/constant/RegexConstants.java new file mode 100644 index 0000000..9b9926d --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/constant/RegexConstants.java @@ -0,0 +1,53 @@ +package top.ysoft.admin.common.constant; + +/** + * 正则相关常量 + * + * @author Charles7c + * @since 2023/1/10 20:06 + */ +public class RegexConstants { + + /** + * 用户名正则(用户名长度为 4-64 个字符,支持大小写字母、数字、下划线,以字母开头) + */ + public static final String USERNAME = "^[a-zA-Z][a-zA-Z0-9_]{3,64}$"; + + /** + * 密码正则模板(密码长度为 min-max 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字) + */ + public static final String PASSWORD_TEMPLATE = "^(?=.*\\d)(?=.*[a-z]).{%s,%s}$"; + + /** + * 密码正则(密码长度为 8-32 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字) + */ + public static final String PASSWORD = "^(?=.*\\d)(?=.*[a-z]).{8,32}$"; + + /** + * 特殊字符正则 + */ + public static final String SPECIAL_CHARACTER = "[-_`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]|\\n|\\r|\\t"; + + /** + * 通用编码正则(长度为 2-30 个字符,支持大小写字母、数字、下划线,以字母开头) + */ + public static final String GENERAL_CODE = "^[a-zA-Z][a-zA-Z0-9_]{1,29}$"; + + /** + * 通用名称正则(长度为 2-30 个字符,支持中文、字母、数字、下划线,短横线) + */ + public static final String GENERAL_NAME = "^[\\u4e00-\\u9fa5a-zA-Z0-9_-]{2,30}$"; + + /** + * 包名正则(可以包含大小写字母、数字、下划线,每一级包名不能以数字开头) + */ + public static final String PACKAGE_NAME = "^(?:[a-zA-Z_][a-zA-Z0-9_]*\\.)*[a-zA-Z_][a-zA-Z0-9_]*$"; + + /** + * 金额正则表达式:纯数字,保留两位小数 + */ + public static final String MONEY = "^\\d+(\\.\\d{2})?$"; + + private RegexConstants() { + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/constant/SysConstants.java b/wms-common/src/main/java/top/wms/admin/common/constant/SysConstants.java new file mode 100644 index 0000000..38caa18 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/constant/SysConstants.java @@ -0,0 +1,68 @@ +package top.ysoft.admin.common.constant; + +/** + * 系统相关常量 + * + * @author Charles7c + * @since 2023/2/9 22:11 + */ +public class SysConstants { + + /** + * 否 + */ + public static final Integer NO = 0; + + /** + * 是 + */ + public static final Integer YES = 1; + + /** + * 超管用户 ID + */ + public static final Long SUPER_USER_ID = 1L; + + /** + * 顶级部门 ID + */ + public static final Long SUPER_DEPT_ID = 1L; + + /** + * 顶级父 ID + */ + public static final Long SUPER_PARENT_ID = 0L; + + /** + * 超管角色编码 + */ + public static final String SUPER_ROLE_CODE = "admin"; + + /** + * 超管角色 ID + */ + public static final Long SUPER_ROLE_ID = 1L; + + /** + * 全部权限标识 + */ + public static final String ALL_PERMISSION = "*:*:*"; + + /** + * 登录 URI + */ + public static final String LOGIN_URI = "/auth/login"; + + /** + * 登出 URI + */ + public static final String LOGOUT_URI = "/auth/logout"; + + /** + * 描述类字段后缀 + */ + public static final String DESCRIPTION_FIELD_SUFFIX = "String"; + + private SysConstants() { + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/constant/UiConstants.java b/wms-common/src/main/java/top/wms/admin/common/constant/UiConstants.java new file mode 100644 index 0000000..e3eb2b5 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/constant/UiConstants.java @@ -0,0 +1,38 @@ +package top.ysoft.admin.common.constant; + +/** + * UI 相关常量 + * + * @author Charles7c + * @since 2023/9/17 14:12 + */ +public class UiConstants { + + /** + * 主色(极致蓝) + */ + public static final String COLOR_PRIMARY = "arcoblue"; + + /** + * 成功色(仙野绿) + */ + public static final String COLOR_SUCCESS = "green"; + + /** + * 警告色(活力橙) + */ + public static final String COLOR_WARNING = "orangered"; + + /** + * 错误色(浪漫红) + */ + public static final String COLOR_ERROR = "red"; + + /** + * 默认色(中性灰) + */ + public static final String COLOR_DEFAULT = "gray"; + + private UiConstants() { + } +} \ No newline at end of file diff --git a/wms-common/src/main/java/top/wms/admin/common/context/RoleContext.java b/wms-common/src/main/java/top/wms/admin/common/context/RoleContext.java new file mode 100644 index 0000000..fab7d0c --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/context/RoleContext.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.common.context; + +import lombok.Data; +import lombok.NoArgsConstructor; +import top.ysoft.admin.common.enums.DataScopeEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 角色上下文 + * + * @author Charles7c + * @since 2023/3/7 22:08 + */ +@Data +@NoArgsConstructor +public class RoleContext implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + private Long id; + + /** + * 角色编码 + */ + private String code; + + /** + * 数据权限 + */ + private DataScopeEnum dataScope; + + public RoleContext(Long id, String code, DataScopeEnum dataScope) { + this.id = id; + this.code = code; + this.dataScope = dataScope; + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/context/UserContext.java b/wms-common/src/main/java/top/wms/admin/common/context/UserContext.java new file mode 100644 index 0000000..b5f3f7f --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/context/UserContext.java @@ -0,0 +1,116 @@ +package top.ysoft.admin.common.context; + +import cn.hutool.core.collection.CollUtil; +import lombok.Data; +import lombok.NoArgsConstructor; +import top.ysoft.admin.common.constant.SysConstants; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 用户上下文 + * + * @author Charles7c + * @since 2024/10/9 20:29 + */ +@Data +@NoArgsConstructor +public class UserContext implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 部门 ID + */ + private Long deptId; + + /** + * 最后一次修改密码时间 + */ + private LocalDateTime pwdResetTime; + + /** + * 登录时系统设置的密码过期天数 + */ + private Integer passwordExpirationDays; + + /** + * 权限码集合 + */ + private Set permissions; + + /** + * 角色编码集合 + */ + private Set roleCodes; + + /** + * 角色集合 + */ + private Set roles; + + /** + * 终端类型 + */ + private String clientType; + + /** + * 终端 ID + */ + private String clientId; + + public UserContext(Set permissions, Set roles, Integer passwordExpirationDays) { + this.permissions = permissions; + this.setRoles(roles); + this.passwordExpirationDays = passwordExpirationDays; + } + + public void setRoles(Set roles) { + this.roles = roles; + this.roleCodes = roles.stream().map(RoleContext::getCode).collect(Collectors.toSet()); + } + + /** + * 是否为管理员 + * + * @return true:是;false:否 + */ + public boolean isAdmin() { + if (CollUtil.isEmpty(roleCodes)) { + return false; + } + return roleCodes.contains(SysConstants.SUPER_ROLE_CODE); + } + + /** + * 密码是否已过期 + * + * @return 是否过期 + */ + public boolean isPasswordExpired() { + // 永久有效 + if (this.passwordExpirationDays == null || this.passwordExpirationDays <= SysConstants.NO) { + return false; + } + // 初始密码(第三方登录用户)暂不提示修改 + if (this.pwdResetTime == null) { + return false; + } + return this.pwdResetTime.plusDays(this.passwordExpirationDays).isBefore(LocalDateTime.now()); + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/context/UserContextHolder.java b/wms-common/src/main/java/top/wms/admin/common/context/UserContextHolder.java new file mode 100644 index 0000000..c7804e9 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/context/UserContextHolder.java @@ -0,0 +1,167 @@ +package top.ysoft.admin.common.context; + +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.extra.spring.SpringUtil; +import top.ysoft.admin.common.service.CommonUserService; +import top.continew.starter.core.util.ExceptionUtils; + +/** + * 用户上下文 Holder + * + * @author Charles7c + * @since 2022/12/24 12:58 + */ +public class UserContextHolder { + + private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + private static final ThreadLocal EXTRA_CONTEXT_HOLDER = new ThreadLocal<>(); + + private UserContextHolder() { + } + + /** + * 设置上下文 + * + * @param context 上下文 + */ + public static void setContext(UserContext context) { + setContext(context, true); + } + + /** + * 设置上下文 + * + * @param context 上下文 + * @param isUpdate 是否更新 + */ + public static void setContext(UserContext context, boolean isUpdate) { + CONTEXT_HOLDER.set(context); + if (isUpdate) { + StpUtil.getSessionByLoginId(context.getId()).set(SaSession.USER, context); + } + } + + /** + * 获取上下文 + * + * @return 上下文 + */ + public static UserContext getContext() { + UserContext context = CONTEXT_HOLDER.get(); + if (null == context) { + context = StpUtil.getSession().getModel(SaSession.USER, UserContext.class); + CONTEXT_HOLDER.set(context); + } + return context; + } + + /** + * 获取指定用户的上下文 + * + * @param userId 用户 ID + * @return 上下文 + */ + public static UserContext getContext(Long userId) { + SaSession session = StpUtil.getSessionByLoginId(userId, false); + if (null == session) { + return null; + } + return session.getModel(SaSession.USER, UserContext.class); + } + + /** + * 设置额外上下文 + * + * @param context 额外上下文 + */ + public static void setExtraContext(UserExtraContext context) { + EXTRA_CONTEXT_HOLDER.set(context); + } + + /** + * 获取额外上下文 + * + * @return 额外上下文 + */ + public static UserExtraContext getExtraContext() { + UserExtraContext context = EXTRA_CONTEXT_HOLDER.get(); + if (null == context) { + context = getExtraContext(StpUtil.getTokenValue()); + EXTRA_CONTEXT_HOLDER.set(context); + } + return context; + } + + /** + * 获取额外上下文 + * + * @param token 令牌 + * @return 额外上下文 + */ + public static UserExtraContext getExtraContext(String token) { + UserExtraContext context = new UserExtraContext(); + context.setIp(Convert.toStr(StpUtil.getExtra(token, "ip"))); + context.setAddress(Convert.toStr(StpUtil.getExtra(token, "address"))); + context.setBrowser(Convert.toStr(StpUtil.getExtra(token, "browser"))); + context.setOs(Convert.toStr(StpUtil.getExtra(token, "os"))); + context.setLoginTime(Convert.toLocalDateTime(StpUtil.getExtra(token, "loginTime"))); + return context; + } + + /** + * 清除上下文 + */ + public static void clearContext() { + CONTEXT_HOLDER.remove(); + EXTRA_CONTEXT_HOLDER.remove(); + } + + /** + * 获取用户 ID + * + * @return 用户 ID + */ + public static Long getUserId() { + return ExceptionUtils.exToNull(() -> getContext().getId()); + } + + /** + * 获取用户名 + * + * @return 用户名 + */ + public static String getUsername() { + return ExceptionUtils.exToNull(() -> getContext().getUsername()); + } + + /** + * 获取用户昵称 + * + * @return 用户昵称 + */ + public static String getNickname() { + return getNickname(getUserId()); + } + + /** + * 获取用户昵称 + * + * @param userId 登录用户 ID + * @return 用户昵称 + */ + public static String getNickname(Long userId) { + return ExceptionUtils.exToNull(() -> SpringUtil.getBean(CommonUserService.class).getNicknameById(userId)); + } + + /** + * 是否为管理员 + * + * @return 是否为管理员 + */ + public static boolean isAdmin() { + StpUtil.checkLogin(); + return getContext().isAdmin(); + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/context/UserExtraContext.java b/wms-common/src/main/java/top/wms/admin/common/context/UserExtraContext.java new file mode 100644 index 0000000..657222f --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/context/UserExtraContext.java @@ -0,0 +1,61 @@ +package top.ysoft.admin.common.context; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.JakartaServletUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.Data; +import lombok.NoArgsConstructor; +import top.continew.starter.core.util.ExceptionUtils; +import top.continew.starter.core.util.IpUtils; +import top.continew.starter.web.util.ServletUtils; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户额外上下文 + * + * @author Charles7c + * @since 2024/10/9 20:29 + */ +@Data +@NoArgsConstructor +public class UserExtraContext implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * IP + */ + private String ip; + + /** + * IP 归属地 + */ + private String address; + + /** + * 浏览器 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 登录时间 + */ + private LocalDateTime loginTime; + + public UserExtraContext(HttpServletRequest request) { + this.ip = JakartaServletUtil.getClientIP(request); + this.address = ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(this.ip)); + this.setBrowser(ServletUtils.getBrowser(request)); + this.setLoginTime(LocalDateTime.now()); + this.setOs(StrUtil.subBefore(ServletUtils.getOs(request), " or", false)); + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/controller/BaseController.java b/wms-common/src/main/java/top/wms/admin/common/controller/BaseController.java new file mode 100644 index 0000000..643fa29 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/controller/BaseController.java @@ -0,0 +1,51 @@ +package top.ysoft.admin.common.controller; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.sign.SaSignTemplate; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.text.CharSequenceUtil; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.extension.crud.annotation.CrudApi; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.controller.AbstractBaseController; +import top.continew.starter.extension.crud.enums.Api; +import top.continew.starter.extension.crud.service.BaseService; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * 控制器基类 + * + * @param 业务接口 + * @param 列表类型 + * @param 详情类型 + * @param 查询条件 + * @param 创建或修改参数类型 + * @author Charles7c + * @since 2024/12/6 20:30 + */ +public class BaseController, L, D, Q, C> extends AbstractBaseController { + + @Override + public void preHandle(CrudApi crudApi, Object[] args, Method targetMethod, Class targetClass) throws Exception { + SaRequest saRequest = SaHolder.getRequest(); + List paramNames = saRequest.getParamNames(); + if (paramNames.stream().anyMatch(SaSignTemplate.sign::equals)) { + return; + } + if (AnnotationUtil.hasAnnotation(targetMethod, SaIgnore.class) || AnnotationUtil + .hasAnnotation(targetClass, SaIgnore.class)) { + return; + } + CrudRequestMapping crudRequestMapping = targetClass.getDeclaredAnnotation(CrudRequestMapping.class); + String path = crudRequestMapping.value(); + String prefix = String.join(StringConstants.COLON, CharSequenceUtil.splitTrim(path, StringConstants.SLASH)); + Api api = crudApi.value(); + String apiName = Api.PAGE.equals(api) || Api.TREE.equals(api) ? Api.LIST.name() : api.name(); + StpUtil.checkPermission("%s:%s".formatted(prefix, apiName.toLowerCase())); + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/CommonExceptionEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/CommonExceptionEnum.java new file mode 100644 index 0000000..9238664 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/CommonExceptionEnum.java @@ -0,0 +1,22 @@ +package top.ysoft.admin.common.enums; + +public enum CommonExceptionEnum { + RULE_ERROR(1000, "您所所在得部门所属规则未绑定设备"), REQUEST_ERROR(1001, "请求异常"), PEOPLE_ERROR(1002, "您的信息未录入"), + QR_CODE_ERROR(1003, "二维码过期"), DEVICE_INITIALIZE_ERROR(1004, "设备序列号不能为空"),; + + private Integer errCode; + private String errMsg; + + CommonExceptionEnum(Integer errCode, String errMsg) { + this.errCode = errCode; + this.errMsg = errMsg; + } + + public Integer getCode() { + return errCode; + } + + public String getMessage() { + return errMsg; + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeOrderTypeEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeOrderTypeEnum.java new file mode 100644 index 0000000..3e54825 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeOrderTypeEnum.java @@ -0,0 +1,24 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 交易状态枚举 + * + * @author Charles7c + * @since 2022/12/29 22:38 + */ +@Getter +@RequiredArgsConstructor +public enum ConsumeOrderTypeEnum implements BaseEnum { + + /** + * 在线消费 + */ + CONSUME(0, "消费"); + + private final Integer value; + private final String description; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/ConsumePayModeEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumePayModeEnum.java new file mode 100644 index 0000000..f606c35 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumePayModeEnum.java @@ -0,0 +1,49 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 支付方式枚举 + * + * @author Charles7c + * @since 2022/12/29 22:38 + */ +@Getter +@RequiredArgsConstructor +public enum ConsumePayModeEnum implements BaseEnum { + + /** + * 人脸 + */ + FACE(0, "人脸"), + + /** + * 云卡 + */ + CLOUD_CARD(1, "云卡"), + + /** + * 刷卡 + */ + SWIPE_CARD(2, "刷卡"), + + /** + * 支付宝 + */ + ALIPAY(3, "支付宝"), + + /** + * 微信 + */ + WECHAT(4, "微信"), + + /** + * 取餐码 + */ + MEAL_CODE(5, "取餐码"); + + private final Integer value; + private final String description; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeRechargeModeEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeRechargeModeEnum.java new file mode 100644 index 0000000..1fde42a --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeRechargeModeEnum.java @@ -0,0 +1,39 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 消费-充值方式 + * + * @author Charles7c + * @since 2022/12/29 22:38 + */ +@Getter +@RequiredArgsConstructor +public enum ConsumeRechargeModeEnum implements BaseEnum { + + /** + * 现金 + */ + CASH(0, "现金"), + + /** + * 支付宝 + */ + ALIPAY(1, "支付宝"), + + /** + * 微信 + */ + WECHAT(2, "微信"), + + /** + * 网银 + */ + ONLINE_BANKING(3, "网银"); + + private final Integer value; + private final String description; +} \ No newline at end of file diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeRechargeTypeEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeRechargeTypeEnum.java new file mode 100644 index 0000000..3097553 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeRechargeTypeEnum.java @@ -0,0 +1,49 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 消费-操作类型 + * + * @author Charles7c + * @since 2022/12/29 22:38 + */ +@Getter +@RequiredArgsConstructor +public enum ConsumeRechargeTypeEnum implements BaseEnum { + + /** + * 补贴 + */ + SUBSIDY(0, "补贴"), + + /** + * 充值 + */ + RECHARGE(1, "充值"), + + /** + * 退款 + */ + REFUND(2, "退款"), + + /** + * 清零(全部) + */ + CLEAR(3, "清零(全部)"), + + /** + * 清零(充值) + */ + CLEAR_CZ(4, "清零(充值)"), + + /** + * 清零(补贴) + */ + CLEAR_BT(5, "清零(补贴)"); + + private final Integer value; + private final String description; +} \ No newline at end of file diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeRechargeWayEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeRechargeWayEnum.java new file mode 100644 index 0000000..664bfdd --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeRechargeWayEnum.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 消费-充值来源 + * + * @author Charles7c + * @since 2022/12/29 22:38 + */ +@Getter +@RequiredArgsConstructor +public enum ConsumeRechargeWayEnum implements BaseEnum { + + /** + * 在线消费 + */ + WEB(0, "平台"), + + /** + * 离线消费 + */ + PHONE(1, "手机"); + + private final Integer value; + private final String description; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeResultEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeResultEnum.java new file mode 100644 index 0000000..b1b081a --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeResultEnum.java @@ -0,0 +1,44 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 交易状态枚举 + * + * @author Charles7c + * @since 2022/12/29 22:38 + */ +@Getter +@RequiredArgsConstructor +public enum ConsumeResultEnum implements BaseEnum { + + /** + * 在线消费 + */ + ONLINE_CONSUME(0, "在线消费"), + + /** + * 离线消费 + */ + OFFLINE_CONSUME(1, "离线消费"), + + /** + * 超时 + */ + TIMEOUT(2, "超时"), + + /** + * 消费异常 + */ + CONSUMPTION_EXCEPTION(3, "消费异常"), + + /** + * 异常消费 + */ + EXCEPTION_CONSUMPTION(4, "异常消费"); + + private final Integer value; + private final String description; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeTmrtypeEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeTmrtypeEnum.java new file mode 100644 index 0000000..07afd9f --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeTmrtypeEnum.java @@ -0,0 +1,38 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 消费-充值来源 + * + * @author Charles7c + * @since 2022/12/29 22:38 + */ +@Getter +@RequiredArgsConstructor +public enum ConsumeTmrtypeEnum implements BaseEnum { + /** + * 早餐 + */ + BREAKFAST(0, "早餐"), + + /** + * 中餐 + */ + LUNCH(1, "中餐"), + + /** + * 午餐 + */ + DINNER(2, "午餐"), + + /** + * 夜餐 + */ + NIGHT(3, "夜餐"); + + private final Integer value; + private final String description; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeTypeConverter.java b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeTypeConverter.java new file mode 100644 index 0000000..98a5961 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeTypeConverter.java @@ -0,0 +1,49 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 消费类型枚举 + * + * @author Charles7c + * @since 2022/12/29 22:38 + */ +@Getter +@RequiredArgsConstructor +public enum ConsumeTypeConverter implements BaseEnum { + + /** + * 单价 + */ + UNIT_PRICE(0, "单价"), + + /** + * 定额 + */ + FIXED_PRICE(1, "定额"), + + /** + * 时段模式 + */ + TIME_PERIOD(2, "时段模式"), + + /** + * 计次 + */ + COUNTING(3, "计次"), + + /** + * 点餐机模式 + */ + ORDERING_MACHINE(5, "点餐机模式"), + + /** + * 身份模式 + */ + IDENTITY(9, "身份模式"); + + private final Integer value; + private final String description; +} \ No newline at end of file diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeWalletModeEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeWalletModeEnum.java new file mode 100644 index 0000000..5196e04 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/ConsumeWalletModeEnum.java @@ -0,0 +1,34 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 钱包消费模式枚举 + * + * @author Charles7c + * @since 2022/12/29 22:38 + */ +@Getter +@RequiredArgsConstructor +public enum ConsumeWalletModeEnum implements BaseEnum { + + /** + * 先消费补贴再个人 + */ + SUBSIDY_THEN_PERSONAL(0, "先消费补贴再个人"), + + /** + * 仅现金 + */ + ONLY_CASH(1, "仅现金"), + + /** + * 仅补贴 + */ + ONLY_SUBSIDY(2, "仅补贴"); + + private final Integer value; + private final String description; +} \ No newline at end of file diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/DataScopeEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/DataScopeEnum.java new file mode 100644 index 0000000..65308de --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/DataScopeEnum.java @@ -0,0 +1,44 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 数据权限枚举 + * + * @author Charles7c + * @since 2023/2/8 22:58 + */ +@Getter +@RequiredArgsConstructor +public enum DataScopeEnum implements BaseEnum { + + /** + * 全部数据权限 + */ + ALL(1, "全部数据权限"), + + /** + * 本部门及以下数据权限 + */ + DEPT_AND_CHILD(2, "本部门及以下数据权限"), + + /** + * 本部门数据权限 + */ + DEPT(3, "本部门数据权限"), + + /** + * 仅本人数据权限 + */ + SELF(4, "仅本人数据权限"), + + /** + * 自定义数据权限 + */ + CUSTOM(5, "自定义数据权限"),; + + private final Integer value; + private final String description; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/DisEnableStatusEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/DisEnableStatusEnum.java new file mode 100644 index 0000000..8a8797a --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/DisEnableStatusEnum.java @@ -0,0 +1,31 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.ysoft.admin.common.constant.UiConstants; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 启用/禁用状态枚举 + * + * @author Charles7c + * @since 2022/12/29 22:38 + */ +@Getter +@RequiredArgsConstructor +public enum DisEnableStatusEnum implements BaseEnum { + + /** + * 启用 + */ + ENABLE(1, "启用", UiConstants.COLOR_SUCCESS), + + /** + * 禁用 + */ + DISABLE(2, "禁用", UiConstants.COLOR_ERROR),; + + private final Integer value; + private final String description; + private final String color; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/EnableEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/EnableEnum.java new file mode 100644 index 0000000..3f0c72e --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/EnableEnum.java @@ -0,0 +1,30 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 启用禁用 + * + * @author Charles7c + * @since 2022/12/29 22:38 + */ +@Getter +@RequiredArgsConstructor +public enum EnableEnum implements BaseEnum { + + /** + * 禁用 + */ + DISABLE(0, "禁用"), + + /** + * 在线消费 + */ + ENABLE(1, "启用"); + + + private final Integer value; + private final String description; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/GenderEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/GenderEnum.java new file mode 100644 index 0000000..34752db --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/GenderEnum.java @@ -0,0 +1,34 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 性别枚举 + * + * @author Charles7c + * @since 2022/12/29 21:59 + */ +@Getter +@RequiredArgsConstructor +public enum GenderEnum implements BaseEnum { + + /** + * 未知 + */ + UNKNOWN(0, "未知"), + + /** + * 男 + */ + MALE(1, "男"), + + /** + * 女 + */ + FEMALE(2, "女"),; + + private final Integer value; + private final String description; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/OperTypeEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/OperTypeEnum.java new file mode 100644 index 0000000..ded3ed8 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/OperTypeEnum.java @@ -0,0 +1,33 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +@Getter +@RequiredArgsConstructor +public enum OperTypeEnum implements BaseEnum { + + /** + * 新增 + */ + ADD(0, "新增"), + + /** + * 修改 + */ + UPDATE(1, "修改"), + + /** + * 下发 + */ + DOWN(2, "下发"), + + /** + * 删除 + */ + DEL(3, "删除"); + + private final Integer value; + private final String description; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/SuccessFailureStatusEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/SuccessFailureStatusEnum.java new file mode 100644 index 0000000..bbf52b9 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/SuccessFailureStatusEnum.java @@ -0,0 +1,31 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.ysoft.admin.common.constant.UiConstants; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 成功/失败状态枚举 + * + * @author Charles7c + * @since 2023/2/26 21:35 + */ +@Getter +@RequiredArgsConstructor +public enum SuccessFailureStatusEnum implements BaseEnum { + + /** + * 成功 + */ + SUCCESS(0, "成功", UiConstants.COLOR_SUCCESS), + + /** + * 失败 + */ + FAILURE(1, "失败", UiConstants.COLOR_ERROR),; + + private final Integer value; + private final String description; + private final String color; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/YesNoEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/YesNoEnum.java new file mode 100644 index 0000000..e7476f2 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/YesNoEnum.java @@ -0,0 +1,45 @@ +package top.ysoft.admin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; +import top.ysoft.admin.common.constant.UiConstants; + +/** + * 成功/失败状态枚举 + * + * @author Charles7c + * @since 2023/2/26 21:35 + */ +@Getter +@RequiredArgsConstructor +public enum YesNoEnum implements BaseEnum { + + /** + * 否 + */ + NO(0, "否", UiConstants.COLOR_ERROR), + + /** + * 是 + */ + YES(1, "是", UiConstants.COLOR_SUCCESS),; + + private final Integer value; + private final String description; + private final String color; + + /** + * 根据描述获取枚举常量 + * @param description 描述 + * @return 枚举常量 + */ + public static YesNoEnum getByDescription(String description) { + for (YesNoEnum enumValue : values()) { + if (enumValue.getDescription().equals(description)) { + return enumValue; + } + } + throw new IllegalArgumentException("No enum constant with description: " + description); + } +} diff --git a/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseCreateDO.java b/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseCreateDO.java new file mode 100644 index 0000000..a5ee63e --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseCreateDO.java @@ -0,0 +1,38 @@ +package top.ysoft.admin.common.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; +import top.continew.starter.extension.crud.model.entity.BaseIdDO; + +import java.io.Serial; +import java.time.LocalDateTime; + +/** + * 实体类基类 + * + *

+ * 通用字段:创建人、创建时间 + *

+ * + * @author Charles7c + * @since 2025/1/12 23:00 + */ +@Data +public class BaseCreateDO extends BaseIdDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 创建人 + */ + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseDO.java b/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseDO.java new file mode 100644 index 0000000..6e48ec2 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseDO.java @@ -0,0 +1,46 @@ +package top.ysoft.admin.common.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; +import top.continew.starter.extension.crud.model.entity.BaseIdDO; + +import java.io.Serial; +import java.time.LocalDateTime; + +/** + * 实体类基类 + * + * @author Charles7c + * @since 2025/1/12 23:00 + */ +@Data +public class BaseDO extends BaseIdDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 创建人 + */ + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 修改人 + */ + @TableField(fill = FieldFill.UPDATE) + private Long updateUser; + + /** + * 修改时间 + */ + @TableField(fill = FieldFill.UPDATE) + private LocalDateTime updateTime; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseStrIdDO.java b/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseStrIdDO.java new file mode 100644 index 0000000..c85eca0 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseStrIdDO.java @@ -0,0 +1,51 @@ +package top.ysoft.admin.common.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 实体类基类 + * + * @author Charles7c + * @since 2025/1/12 23:00 + */ +@Data +public class BaseStrIdDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.ASSIGN_ID) + private String id; + + /** + * 创建人 + */ + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 修改人 + */ + @TableField(fill = FieldFill.UPDATE) + private Long updateUser; + + /** + * 修改时间 + */ + @TableField(fill = FieldFill.UPDATE) + private LocalDateTime updateTime; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseUpdateDO.java b/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseUpdateDO.java new file mode 100644 index 0000000..aede3ff --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/model/entity/BaseUpdateDO.java @@ -0,0 +1,39 @@ +package top.ysoft.admin.common.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +import java.io.Serial; +import java.time.LocalDateTime; + +import top.continew.starter.extension.crud.model.entity.BaseIdDO; + +/** + * 实体类基类 + * + *

+ * 通用字段:创建人、创建时间 + *

+ * + * @author Charles7c + * @since 2025/1/12 23:00 + */ +@Data +public class BaseUpdateDO extends BaseIdDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 修改人 + */ + @TableField(fill = FieldFill.UPDATE) + private Long updateUser; + + /** + * 修改时间 + */ + @TableField(fill = FieldFill.UPDATE) + private LocalDateTime updateTime; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/model/req/CommonStatusUpdateReq.java b/wms-common/src/main/java/top/wms/admin/common/model/req/CommonStatusUpdateReq.java new file mode 100644 index 0000000..96d7abd --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/model/req/CommonStatusUpdateReq.java @@ -0,0 +1,26 @@ +package top.ysoft.admin.common.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; + +import java.io.Serializable; + +/** + * 修改状态请求参数 + * + * @author Charles7c + * @since 2025/3/4 20:09 + */ +@Data +@Schema(description = "修改状态请求参数") +public class CommonStatusUpdateReq implements Serializable { + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + @NotNull(message = "状态非法") + private DisEnableStatusEnum status; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/model/resp/BaseDetailResp.java b/wms-common/src/main/java/top/wms/admin/common/model/resp/BaseDetailResp.java new file mode 100644 index 0000000..f5d3ae6 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/model/resp/BaseDetailResp.java @@ -0,0 +1,51 @@ +package top.ysoft.admin.common.model.resp; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.annotation.Mapping; +import cn.crane4j.annotation.condition.ConditionOnPropertyNotNull; +import com.alibaba.excel.annotation.ExcelIgnore; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.constant.ContainerConstants; + +import java.io.Serial; +import java.time.LocalDateTime; + +/** + * 详情响应基类 + * + * @author Charles7c + * @since 2024/12/27 20:32 + */ +@Data +public class BaseDetailResp extends BaseResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 修改人 + */ + @JsonIgnore + @ConditionOnPropertyNotNull + @Assemble(container = ContainerConstants.USER_NICKNAME, props = @Mapping(ref = "updateUserString")) + @ExcelIgnore + private Long updateUser; + + /** + * 修改人 + */ + @Schema(description = "修改人", example = "李四") + // @ExcelProperty(value = "修改人", order = Integer.MAX_VALUE - 2) + @ExcelIgnore + private String updateUserString; + + /** + * 修改时间 + */ + @Schema(description = "修改时间", example = "2023-08-08 08:08:08", type = "string") + // @ExcelProperty(value = "修改时间", order = Integer.MAX_VALUE - 1) + @ExcelIgnore + private LocalDateTime updateTime; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/model/resp/BaseResp.java b/wms-common/src/main/java/top/wms/admin/common/model/resp/BaseResp.java new file mode 100644 index 0000000..6906349 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/model/resp/BaseResp.java @@ -0,0 +1,67 @@ +package top.ysoft.admin.common.model.resp; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.annotation.Mapping; +import com.alibaba.excel.annotation.ExcelIgnore; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.constant.ContainerConstants; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 响应参数基类 + * + * @author Charles7c + * @since 2024/12/27 20:32 + */ +@Data +public class BaseResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + // @ExcelProperty(value = "ID", order = 1) + @ExcelIgnore + private Long id; + + /** + * 创建人 + */ + @JsonIgnore + @Assemble(container = ContainerConstants.USER_NICKNAME, props = @Mapping(ref = "createUserString")) + @ExcelIgnore + private Long createUser; + + /** + * 创建人 + */ + @Schema(description = "创建人", example = "超级管理员") + // @ExcelProperty(value = "创建人", order = Integer.MAX_VALUE - 4) + @ExcelIgnore + private String createUserString; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") + // @ExcelProperty(value = "创建时间", order = Integer.MAX_VALUE - 3) + @ExcelIgnore + private LocalDateTime createTime; + + /** + * 是否禁用修改 + */ + @Schema(description = "是否禁用修改", example = "true") + @JsonInclude(JsonInclude.Include.NON_NULL) + @ExcelIgnore + private Boolean disabled; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/model/resp/BaseStrIdResp.java b/wms-common/src/main/java/top/wms/admin/common/model/resp/BaseStrIdResp.java new file mode 100644 index 0000000..03da063 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/model/resp/BaseStrIdResp.java @@ -0,0 +1,62 @@ +package top.ysoft.admin.common.model.resp; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.annotation.Mapping; +import com.alibaba.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.constant.ContainerConstants; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 响应参数基类 + * + * @author Charles7c + * @since 2024/12/27 20:32 + */ +@Data +public class BaseStrIdResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + @ExcelProperty(value = "ID", order = 1) + private String id; + + /** + * 创建人 + */ + @JsonIgnore + @Assemble(container = ContainerConstants.USER_NICKNAME, props = @Mapping(ref = "createUserString")) + private Long createUser; + + /** + * 创建人 + */ + @Schema(description = "创建人", example = "超级管理员") + @ExcelProperty(value = "创建人", order = Integer.MAX_VALUE - 4) + private String createUserString; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") + @ExcelProperty(value = "创建时间", order = Integer.MAX_VALUE - 3) + private LocalDateTime createTime; + + /** + * 是否禁用修改 + */ + @Schema(description = "是否禁用修改", example = "true") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean disabled; +} diff --git a/wms-common/src/main/java/top/wms/admin/common/service/CommonUserService.java b/wms-common/src/main/java/top/wms/admin/common/service/CommonUserService.java new file mode 100644 index 0000000..4e42a69 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/service/CommonUserService.java @@ -0,0 +1,27 @@ +package top.ysoft.admin.common.service; + +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import top.ysoft.admin.common.constant.ContainerConstants; + +/** + * 公共用户业务接口 + * + * @author Charles7c + * @since 2025/1/9 20:17 + */ +public interface CommonUserService { + + /** + * 根据 ID 查询昵称 + * + *

+ * 数据填充容器 {@link ContainerConstants#USER_NICKNAME} + *

+ * + * @param id ID + * @return 昵称 + */ + @ContainerMethod(namespace = ContainerConstants.USER_NICKNAME, type = MappingType.ORDER_OF_KEYS) + String getNicknameById(Long id); +} diff --git a/wms-common/src/main/java/top/wms/admin/common/util/ImageToBase64Utils.java b/wms-common/src/main/java/top/wms/admin/common/util/ImageToBase64Utils.java new file mode 100644 index 0000000..852d712 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/util/ImageToBase64Utils.java @@ -0,0 +1,148 @@ +package top.ysoft.admin.common.util; + +import org.apache.commons.codec.binary.Base64; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; + +public class ImageToBase64Utils { + /** + * 本地图片转base64 + */ + public static String getImgFileToBase642(String imgFile) { + //将图片文件转化为字节数组字符串,并对其进行Base64编码处理 + byte[] buffer = null; + //读取图片字节数组 + try (InputStream inputStream = new FileInputStream(imgFile);) { + int count = 0; + while (count == 0) { + count = inputStream.available(); + } + buffer = new byte[count]; + inputStream.read(buffer); + } catch (IOException e) { + e.printStackTrace(); + } + // 对字节数组Base64编码 + return new String(Base64.encodeBase64(buffer)); + } + + /** + * 网络图片转base64 + */ + public static String getImgUrlToBase64(String imgUrl) { + byte[] buffer = null; + InputStream inputStream = null; + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) { + // 创建URL + URL url = new URL(imgUrl); + // 创建链接 + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(5000); + inputStream = conn.getInputStream(); + // 将内容读取内存中 + buffer = new byte[1024]; + int len = -1; + while ((len = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + } + buffer = outputStream.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (inputStream != null) { + try { + // 关闭inputStream流 + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + // 对字节数组Base64编码 + return new String(Base64.encodeBase64(buffer)); + } + + /** + * 本地或网络图片转base64 + */ + public static String getImgStrToBase64(String imgStr) { + InputStream inputStream = null; + byte[] buffer = null; + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) { + //判断网络链接图片文件/本地目录图片文件 + if (imgStr.startsWith("http://") || imgStr.startsWith("https://")) { + // 创建URL + URL url = new URL(imgStr); + // 创建链接 + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(5000); + inputStream = conn.getInputStream(); + // 将内容读取内存中 + buffer = new byte[1024]; + int len = -1; + while ((len = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + } + buffer = outputStream.toByteArray(); + } else { + inputStream = new FileInputStream(imgStr); + int count = 0; + while (count == 0) { + count = inputStream.available(); + } + buffer = new byte[count]; + inputStream.read(buffer); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (inputStream != null) { + try { + // 关闭inputStream流 + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + // 对字节数组Base64编码 + + return new String(Base64.encodeBase64(buffer)); + } + + /** + * 对字节数组字符串进行Base64解码并生成图片 + * + * @param imgStr 图片数据 + * @param imgFilePath 保存图片全路径地址 + * @return + */ + public static boolean generateImage(String imgStr, String imgFilePath) { + // + if (imgStr == null) //图像数据为空 + return false; + try { + //Base64解码 + byte[] b = Base64.decodeBase64(imgStr); + for (int i = 0; i < b.length; ++i) { + if (b[i] < 0) {//调整异常数据 + b[i] += 256; + } + } + //生成jpeg图片 + OutputStream out = new FileOutputStream(imgFilePath); + out.write(b); + out.flush(); + out.close(); + return true; + } catch (Exception e) { + return false; + } + } + +} diff --git a/wms-common/src/main/java/top/wms/admin/common/util/PictureUtils.java b/wms-common/src/main/java/top/wms/admin/common/util/PictureUtils.java new file mode 100644 index 0000000..cb23fa6 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/util/PictureUtils.java @@ -0,0 +1,155 @@ +package top.ysoft.admin.common.util; + +import net.coobird.thumbnailator.Thumbnails; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import top.continew.starter.core.exception.BusinessException; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; + +// java将图片的url转换成File,File转换成二进制流byte +public class PictureUtils { + //将Url转换为File + public static File UrltoFile(String url) throws Exception { + HttpURLConnection httpUrl = (HttpURLConnection)new URL(url).openConnection(); + httpUrl.connect(); + InputStream ins = httpUrl.getInputStream(); + File file = new File(System.getProperty("java.io.tmpdir") + File.separator + "xie.jpg"); + if (file.exists()) { + file.delete();//如果缓存中存在该文件就删除 + } + OutputStream os = new FileOutputStream(file); + int bytesRead; + int len = 8192; + byte[] buffer = new byte[len]; + while ((bytesRead = ins.read(buffer, 0, len)) != -1) { + os.write(buffer, 0, bytesRead); + } + os.close(); + ins.close(); + return file; + + } + + //将File对象转换为byte[]的形式 + public static byte[] FileTobyte(File file) { + FileInputStream fileInputStream = null; + byte[] imgData = null; + + try { + + imgData = new byte[(int)file.length()]; + + //read file into bytes[] + fileInputStream = new FileInputStream(file); + fileInputStream.read(imgData); + + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (fileInputStream != null) { + try { + fileInputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + return imgData; + } + + /** + * url转变为 MultipartFile对象 + * + * @param imageUrl 网络地址链接 + * @param fileName 文件名 + * @return + * @throws Exception + */ + public static MultipartFile createMultipartFile(String imageUrl, String fileName) { + try { + // 将在线图片地址转换为URL对象 + URL url = new URL(imageUrl); + // 打开URL连接 + URLConnection connection = url.openConnection(); + // 转换为HttpURLConnection对象 + HttpURLConnection httpURLConnection = (HttpURLConnection)connection; + // 获取输入流 + InputStream inputStream = httpURLConnection.getInputStream(); + // 读取输入流中的数据,并保存到字节数组中 + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + byteArrayOutputStream.write(buffer, 0, bytesRead); + } + // 将字节数组转换为字节数组 + byte[] bytes = byteArrayOutputStream.toByteArray(); + // 创建ByteArrayInputStream对象,将字节数组传递给它 + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); + // 创建MultipartFile对象,将ByteArrayInputStream对象作为构造函数的参数 + return new MockMultipartFile(fileName, fileName + ".jpg", "image/jpg", byteArrayInputStream); + } catch (IOException ex) { + throw new BusinessException("附件无效"); + } + } + + /** + * 根据文件名获取contentType + */ + private static String getContentType(String fileName) { + // 使用Java标准库的方法替代Hutool的StrUtil + String extension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase(); + Map contentTypeMap = new HashMap<>(); + contentTypeMap.put("jpg", "image/jpeg"); + contentTypeMap.put("jpeg", "image/jpeg"); + contentTypeMap.put("png", "image/png"); + contentTypeMap.put("gif", "image/gif"); + contentTypeMap.put("bmp", "image/bmp"); + // 可以根据需要添加更多的文件类型 + return contentTypeMap.getOrDefault(extension, "application/octet-stream"); + } + + /** + * 直接从输入流压缩图片并转换为MultipartFile + * + * @param inputStream 图片输入流 + * @param originalFilename 原始文件名 + * @param quality 压缩质量 (0.0-1.0) + * @return MultipartFile对象 + */ + public static MultipartFile compressImage(InputStream inputStream, + String originalFilename, + float quality) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + // 直接将压缩后的图片写入内存流 + Thumbnails.of(inputStream).scale(1f).outputQuality(quality).toOutputStream(outputStream); + + // 获取文件扩展名和contentType + String extension = originalFilename.substring(originalFilename.lastIndexOf('.') + 1).toLowerCase(); + String contentType = getContentType(originalFilename); + + // 创建MultipartFile对象 + byte[] compressedBytes = outputStream.toByteArray(); + ByteArrayInputStream input = new ByteArrayInputStream(compressedBytes); + return new MockMultipartFile("file", originalFilename, contentType, input); + } finally { + // 确保流关闭 + outputStream.close(); + inputStream.close(); + } + } + +} \ No newline at end of file diff --git a/wms-common/src/main/java/top/wms/admin/common/util/SecureUtils.java b/wms-common/src/main/java/top/wms/admin/common/util/SecureUtils.java new file mode 100644 index 0000000..8060ba9 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/util/SecureUtils.java @@ -0,0 +1,91 @@ +package top.ysoft.admin.common.util; + +import cn.hutool.core.codec.Base64; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.extra.spring.SpringUtil; +import top.ysoft.admin.common.config.properties.RsaProperties; +import top.continew.starter.core.exception.BusinessException; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.security.crypto.autoconfigure.CryptoProperties; +import top.continew.starter.security.crypto.encryptor.AesEncryptor; +import top.continew.starter.security.crypto.encryptor.IEncryptor; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 加密/解密工具类 + * + * @author Charles7c + * @since 2022/12/21 21:41 + */ +public class SecureUtils { + + private SecureUtils() { + } + + /** + * 公钥加密 + * + * @param data 要加密的内容 + * @return 加密后的内容 + */ + public static String encryptByRsaPublicKey(String data) { + String publicKey = RsaProperties.PUBLIC_KEY; + ValidationUtils.throwIfBlank(publicKey, "请配置 RSA 公钥"); + return encryptByRsaPublicKey(data, publicKey); + } + + /** + * 私钥解密 + * + * @param data 要解密的内容(Base64 加密过) + * @return 解密后的内容 + */ + public static String decryptByRsaPrivateKey(String data) { + String privateKey = RsaProperties.PRIVATE_KEY; + ValidationUtils.throwIfBlank(privateKey, "请配置 RSA 私钥"); + return decryptByRsaPrivateKey(data, privateKey); + } + + /** + * 公钥加密 + * + * @param data 要加密的内容 + * @param publicKey 公钥 + * @return 加密后的内容 + */ + public static String encryptByRsaPublicKey(String data, String publicKey) { + return new String(SecureUtil.rsa(null, publicKey).encrypt(data, KeyType.PublicKey)); + } + + /** + * 私钥解密 + * + * @param data 要解密的内容(Base64 加密过) + * @param privateKey 私钥 + * @return 解密后的内容 + */ + public static String decryptByRsaPrivateKey(String data, String privateKey) { + return new String(SecureUtil.rsa(privateKey, null).decrypt(Base64.decode(data), KeyType.PrivateKey)); + } + + /** + * 对普通加密字段列表进行AES加密,优化starter加密模块后优化这个方法 + * + * @param values 待加密内容 + * @return 加密后内容 + */ + public static List encryptFieldByAes(List values) { + IEncryptor encryptor = new AesEncryptor(); + CryptoProperties properties = SpringUtil.getBean(CryptoProperties.class); + return values.stream().map(value -> { + try { + return encryptor.encrypt(value, properties.getPassword(), properties.getPublicKey()); + } catch (Exception e) { + throw new BusinessException("字段加密异常"); + } + }).collect(Collectors.toList()); + } +} diff --git a/wms-extension/.flattened-pom.xml b/wms-extension/.flattened-pom.xml new file mode 100644 index 0000000..398afb9 --- /dev/null +++ b/wms-extension/.flattened-pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + top.ysoft + ysoft-admin + 3.6.0-SNAPSHOT + + ysoft-extension + 3.6.0-SNAPSHOT + pom + 扩展模块(存放其他扩展模块) + + + GNU LESSER GENERAL PUBLIC LICENSE + http://www.gnu.org/licenses/lgpl.html + + + + ysoft-extension-schedule-server + + diff --git a/wms-extension/pom.xml b/wms-extension/pom.xml new file mode 100644 index 0000000..13a984b --- /dev/null +++ b/wms-extension/pom.xml @@ -0,0 +1,17 @@ + + 4.0.0 + + top.ysoft + ysoft-admin + ${revision} + + + ysoft-extension + pom + 扩展模块(存放其他扩展模块) + + + ysoft-extension-schedule-server + + diff --git a/wms-extension/wms-extension-schedule-server/.flattened-pom.xml b/wms-extension/wms-extension-schedule-server/.flattened-pom.xml new file mode 100644 index 0000000..3d69551 --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/.flattened-pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + top.ysoft + ysoft-extension + 3.6.0-SNAPSHOT + + ysoft-extension-schedule-server + 3.6.0-SNAPSHOT + 任务调度服务端 + + + GNU LESSER GENERAL PUBLIC LICENSE + http://www.gnu.org/licenses/lgpl.html + + + + 1.2.0 + + + + com.aizuda + snail-job-server-starter + ${snail-job.version} + + + org.liquibase + liquibase-core + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + diff --git a/wms-extension/wms-extension-schedule-server/pom.xml b/wms-extension/wms-extension-schedule-server/pom.xml new file mode 100644 index 0000000..523aa6d --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + top.ysoft + ysoft-extension + ${revision} + + + ysoft-extension-schedule-server + 任务调度服务端 + + + + 1.2.0 + + + + + + com.aizuda + snail-job-server-starter + ${snail-job.version} + + + + + org.liquibase + liquibase-core + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + \ No newline at end of file diff --git a/wms-extension/wms-extension-schedule-server/src/main/java/top/wms/admin/extension/scheduling/ScheduleServerApplication.java b/wms-extension/wms-extension-schedule-server/src/main/java/top/wms/admin/extension/scheduling/ScheduleServerApplication.java new file mode 100644 index 0000000..71b0761 --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/src/main/java/top/wms/admin/extension/scheduling/ScheduleServerApplication.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.extension.scheduling; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.extra.spring.SpringUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.web.ServerProperties; + +/** + * 任务调度服务端启动程序 + * + * @author KAI + * @since 2024/6/25 22:24 + */ +@Slf4j +@SpringBootApplication +@RequiredArgsConstructor +public class ScheduleServerApplication extends com.aizuda.snailjob.server.SnailJobServerApplication implements ApplicationRunner { + + private final ServerProperties serverProperties; + + public static void main(String[] args) { + SpringApplication.run(ScheduleServerApplication.class, args); + } + + @Override + public void run(ApplicationArguments args) { + String hostAddress = NetUtil.getLocalhostStr(); + Integer port = serverProperties.getPort(); + String contextPath = serverProperties.getServlet().getContextPath(); + String baseUrl = URLUtil.normalize("%s:%s%s".formatted(hostAddress, port, contextPath)); + log.info("----------------------------------------------"); + log.info("{} service started successfully.", SpringUtil.getApplicationName()); + log.info("访问地址:{}", baseUrl); + log.info("在线文档:https://snailjob.opensnail.com"); + log.info("----------------------------------------------"); + } +} diff --git a/wms-extension/wms-extension-schedule-server/src/main/resources/config/application-dev.yml b/wms-extension/wms-extension-schedule-server/src/main/resources/config/application-dev.yml new file mode 100644 index 0000000..5e62648 --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/src/main/resources/config/application-dev.yml @@ -0,0 +1,57 @@ +server: + port: 9203 + +--- ### 数据源配置 +spring.datasource: +# url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_job}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false +# username: ${DB_USER:root} +# password: ${DB_PWD:root} + + url: jdbc:mysql://${DB_HOST:192.168.2.30}:${DB_PORT:3306}/${DB_NAME:continew_job}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false + username: ${DB_USER:root} + password: ${DB_PWD:SQLsql123} + driver-class-name: com.mysql.cj.jdbc.Driver +## Liquibase 配置 +spring.liquibase: + # 是否启用 + enabled: true + # 配置文件路径 + change-log: classpath:/db/changelog/db.changelog-master.yaml + +--- ### Snail Job 服务端配置 +snail-job: + # Netty 端口 + netty-port: 1788 + # 合并日志默认保存天数 + merge-Log-days: 1 + # 合并日志默认的条数 + merge-Log-num: 500 + # 配置日志保存时间(单位:天) + log-storage: 90 + # 配置每批次拉取重试数据的大小 + retry-pull-page-size: 100 + # 配置一个客户端每秒最多接收的重试数量指令 + limiter: 10 + # 配置号段模式下的步长 + step: 100 + # bucket 的总数量 + bucket-total: 128 + # Dashboard 任务容错天数 + summary-day: 7 + # 配置负载均衡周期时间 + load-balance-cycle-time: 10 + ## 回调配置 + callback: + # 回调 uniqueId 前缀 + prefix: CB + # 配置回调的最大执行次数 + max-count: 288 + # 配置回调触发的间隔时间 + trigger-interval: 900 + +--- ### 日志配置 +logging: + level: + com.aizuda.snailjob: DEBUG + file: + path: ./logs \ No newline at end of file diff --git a/wms-extension/wms-extension-schedule-server/src/main/resources/config/application-prod.yml b/wms-extension/wms-extension-schedule-server/src/main/resources/config/application-prod.yml new file mode 100644 index 0000000..07cfc53 --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/src/main/resources/config/application-prod.yml @@ -0,0 +1,58 @@ +server: + port: 18001 + +--- ### 数据源配置 +spring.datasource: + url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:ysoft_admin_job}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false + username: ${DB_USER:root} + password: ${DB_PWD:123456} + driver-class-name: com.mysql.cj.jdbc.Driver +# # PostgreSQL 配置 +# url: jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:ysoft_admin_job}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false +# username: ${DB_USER:root} +# password: ${DB_PWD:123456} +# driver-class-name: org.postgresql.Driver +## Liquibase 配置 +spring.liquibase: + # 是否启用 + enabled: true + # 配置文件路径 + change-log: classpath:/db/changelog/db.changelog-master.yaml + +--- ### Snail Job 服务端配置 +snail-job: + # Netty 端口 + netty-port: 1788 + # 合并日志默认保存天数 + merge-Log-days: 1 + # 合并日志默认的条数 + merge-Log-num: 500 + # 配置日志保存时间(单位:天) + log-storage: 90 + # 配置每批次拉取重试数据的大小 + retry-pull-page-size: 100 + # 配置一个客户端每秒最多接收的重试数量指令 + limiter: 10 + # 配置号段模式下的步长 + step: 100 + # bucket 的总数量 + bucket-total: 128 + # Dashboard 任务容错天数 + summary-day: 7 + # 配置负载均衡周期时间 + load-balance-cycle-time: 10 + ## 回调配置 + callback: + # 回调 uniqueId 前缀 + prefix: CB + # 配置回调的最大执行次数 + max-count: 288 + # 配置回调触发的间隔时间 + trigger-interval: 900 + +--- ### 日志配置 +logging: + level: + com.aizuda.snailjob: INFO + file: + path: ../logs \ No newline at end of file diff --git a/wms-extension/wms-extension-schedule-server/src/main/resources/config/application.yml b/wms-extension/wms-extension-schedule-server/src/main/resources/config/application.yml new file mode 100644 index 0000000..9581a4f --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/src/main/resources/config/application.yml @@ -0,0 +1,12 @@ +--- ### Spring 配置 +spring: + application: + name: ysoft-admin-schedule-server + ## 环境配置 + profiles: + # 启用的环境 + active: dev + +--- ### 日志配置 +logging: + config: classpath:logback-spring.xml \ No newline at end of file diff --git a/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/db.changelog-master.yaml b/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/db.changelog-master.yaml new file mode 100644 index 0000000..286c9bb --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/db.changelog-master.yaml @@ -0,0 +1,5 @@ +databaseChangeLog: + - include: + file: db/changelog/mysql/snail-job_table.sql + - include: + file: db/changelog/mysql/snail-job_data.sql \ No newline at end of file diff --git a/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/mysql/snail-job_data.sql b/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/mysql/snail-job_data.sql new file mode 100644 index 0000000..1ad18aa --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/mysql/snail-job_data.sql @@ -0,0 +1,14 @@ +-- liquibase formatted sql + +-- changeset snail-job-server:1.1.0 +-- 默认命名空间:Default +INSERT INTO `sj_namespace` (`id`, `name`, `unique_id`, `create_dt`, `update_dt`, `deleted`) +VALUES (1, 'Default', '764d604ec6fc45f68cd92514c40e9e1a', NOW(), NOW(), 0); + +-- 默认分组:ysoft-admin +INSERT INTO `sj_group_config` (`id`, `namespace_id`, `group_name`, `description`, `token`, `group_status`, `version`, `group_partition`, `id_generator_mode`, `init_scene`, `bucket_index`, `create_dt`, `update_dt`) +VALUES (1, '764d604ec6fc45f68cd92514c40e9e1a', 'ysoft-admin', '默认分组', 'SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj', 1, 1, 0, 2, 1, 119, NOW(), NOW()); + +-- 默认用户:admin/admin +INSERT INTO `sj_system_user` (username, password, role) +VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2); diff --git a/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/mysql/snail-job_table.sql b/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/mysql/snail-job_table.sql new file mode 100644 index 0000000..c12f812 --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/mysql/snail-job_table.sql @@ -0,0 +1,518 @@ +-- liquibase formatted sql + +-- changeset snail-job-server:1.1.0 +SET NAMES utf8mb4; + +CREATE TABLE `sj_namespace` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(64) NOT NULL COMMENT '名称', + `unique_id` varchar(64) NOT NULL COMMENT '唯一id', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_name` (`name`), + UNIQUE KEY `uk_unique_id` (`unique_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='命名空间'; + +CREATE TABLE `sj_group_config` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL DEFAULT '' COMMENT '组名称', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '组描述', + `token` varchar(64) NOT NULL DEFAULT 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT' COMMENT 'token', + `group_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组状态 0、未启用 1、启用', + `version` int(11) NOT NULL COMMENT '版本号', + `group_partition` int(11) NOT NULL COMMENT '分区', + `id_generator_mode` tinyint(4) NOT NULL DEFAULT 1 COMMENT '唯一id生成模式 默认号段模式', + `init_scene` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否初始化场景 0:否 1:是', + `bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='组配置' +; + +CREATE TABLE `sj_notify_config` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `business_id` varchar(64) NOT NULL COMMENT '业务id (job_id或workflow_id或scene_name)', + `system_task_type` tinyint(4) NOT NULL DEFAULT 3 COMMENT '任务类型 1. 重试任务 2. 重试回调 3、JOB任务 4、WORKFLOW任务', + `notify_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '通知状态 0、未启用 1、启用', + `recipient_ids` varchar(128) NOT NULL COMMENT '接收人id列表', + `notify_threshold` int(11) NOT NULL DEFAULT 0 COMMENT '通知阈值', + `notify_scene` tinyint(4) NOT NULL DEFAULT 0 COMMENT '通知场景', + `rate_limiter_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '限流状态 0、未启用 1、启用', + `rate_limiter_threshold` int(11) NOT NULL DEFAULT 0 COMMENT '每秒限流阈值', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `business_id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='通知配置' +; + +CREATE TABLE `sj_notify_recipient` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `recipient_name` varchar(64) NOT NULL COMMENT '接收人名称', + `notify_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT '通知类型 1、钉钉 2、邮件 3、企业微信 4 飞书 5 webhook', + `notify_attribute` varchar(512) NOT NULL COMMENT '配置属性', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id` (`namespace_id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='告警通知接收人' +; + + +CREATE TABLE `sj_retry_dead_letter_0` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `unique_id` varchar(64) NOT NULL COMMENT '同组下id唯一', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `scene_name` varchar(64) NOT NULL COMMENT '场景名称', + `idempotent_id` varchar(64) NOT NULL COMMENT '幂等id', + `biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '业务编号', + `executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '执行器名称', + `args_str` text NOT NULL COMMENT '执行方法参数', + `ext_attrs` text NOT NULL COMMENT '扩展字段', + `task_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '任务类型 1、重试数据 2、回调数据', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`), + KEY `idx_idempotent_id` (`idempotent_id`), + KEY `idx_biz_no` (`biz_no`), + KEY `idx_create_dt` (`create_dt`), + UNIQUE KEY `uk_namespace_id_group_name_unique_id` (`namespace_id`, `group_name`, `unique_id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='死信队列表' +; + +CREATE TABLE `sj_retry_task_0` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `unique_id` varchar(64) NOT NULL COMMENT '同组下id唯一', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `scene_name` varchar(64) NOT NULL COMMENT '场景名称', + `idempotent_id` varchar(64) NOT NULL COMMENT '幂等id', + `biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '业务编号', + `executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '执行器名称', + `args_str` text NOT NULL COMMENT '执行方法参数', + `ext_attrs` text NOT NULL COMMENT '扩展字段', + `next_trigger_at` datetime NOT NULL COMMENT '下次触发时间', + `retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '重试次数', + `retry_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '重试状态 0、重试中 1、成功 2、最大重试次数', + `task_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '任务类型 1、重试数据 2、回调数据', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`), + KEY `idx_namespace_id_group_name_task_type` (`namespace_id`, `group_name`, `task_type`), + KEY `idx_namespace_id_group_name_retry_status` (`namespace_id`, `group_name`, `retry_status`), + KEY `idx_idempotent_id` (`idempotent_id`), + KEY `idx_biz_no` (`biz_no`), + KEY `idx_create_dt` (`create_dt`), + UNIQUE KEY `uk_name_unique_id` (`namespace_id`, `group_name`, `unique_id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务表' +; + +CREATE TABLE `sj_retry_task_log` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `unique_id` varchar(64) NOT NULL COMMENT '同组下id唯一', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `scene_name` varchar(64) NOT NULL COMMENT '场景名称', + `idempotent_id` varchar(64) NOT NULL COMMENT '幂等id', + `biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '业务编号', + `executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '执行器名称', + `args_str` text NOT NULL COMMENT '执行方法参数', + `ext_attrs` text NOT NULL COMMENT '扩展字段', + `retry_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '重试状态 0、重试中 1、成功 2、最大次数', + `task_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '任务类型 1、重试数据 2、回调数据', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`), + KEY `idx_retry_status` (`retry_status`), + KEY `idx_idempotent_id` (`idempotent_id`), + KEY `idx_unique_id` (`unique_id`), + KEY `idx_biz_no` (`biz_no`), + KEY `idx_create_dt` (`create_dt`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务日志基础信息表' +; + +CREATE TABLE `sj_retry_task_log_message` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `unique_id` varchar(64) NOT NULL COMMENT '同组下id唯一', + `message` longtext NOT NULL COMMENT '异常信息', + `log_num` int(11) NOT NULL DEFAULT 1 COMMENT '日志数量', + `real_time` bigint(13) NOT NULL DEFAULT 0 COMMENT '上报时间', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `unique_id`), + KEY `idx_create_dt` (`create_dt`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务调度日志信息记录表' +; + +CREATE TABLE `sj_retry_scene_config` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `scene_name` varchar(64) NOT NULL COMMENT '场景名称', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `scene_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组状态 0、未启用 1、启用', + `max_retry_count` int(11) NOT NULL DEFAULT 5 COMMENT '最大重试次数', + `back_off` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1、默认等级 2、固定间隔时间 3、CRON 表达式', + `trigger_interval` varchar(16) NOT NULL DEFAULT '' COMMENT '间隔时长', + `deadline_request` bigint(20) unsigned NOT NULL DEFAULT 60000 COMMENT 'Deadline Request 调用链超时 单位毫秒', + `executor_timeout` int(11) unsigned NOT NULL DEFAULT 5 COMMENT '任务执行超时时间,单位秒', + `route_key` tinyint(4) NOT NULL DEFAULT 4 COMMENT '路由策略', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='场景配置' +; + +CREATE TABLE `sj_server_node` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `host_id` varchar(64) NOT NULL COMMENT '主机id', + `host_ip` varchar(64) NOT NULL COMMENT '机器ip', + `host_port` int(16) NOT NULL COMMENT '机器端口', + `expire_at` datetime NOT NULL COMMENT '过期时间', + `node_type` tinyint(4) NOT NULL COMMENT '节点类型 1、客户端 2、是服务端', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`), + KEY `idx_expire_at_node_type` (`expire_at`, `node_type`), + UNIQUE KEY `uk_host_id_host_ip` (`host_id`, `host_ip`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='服务器节点' +; + +CREATE TABLE `sj_distributed_lock` +( + `name` varchar(64) NOT NULL COMMENT '锁名称', + `lock_until` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '锁定时长', + `locked_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '锁定时间', + `locked_by` varchar(255) NOT NULL COMMENT '锁定者', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='锁定表' +; + +CREATE TABLE `sj_system_user` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `username` varchar(64) NOT NULL COMMENT '账号', + `password` varchar(128) NOT NULL COMMENT '密码', + `role` tinyint(4) NOT NULL DEFAULT 0 COMMENT '角色:1-普通用户、2-管理员', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`) USING BTREE +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='系统用户表'; + +CREATE TABLE `sj_system_user_permission` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `system_user_id` bigint(20) NOT NULL COMMENT '系统用户id', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_namespace_id_group_name_system_user_id` (`namespace_id`, `group_name`, `system_user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='系统用户权限表'; + +CREATE TABLE `sj_sequence_alloc` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL DEFAULT '' COMMENT '组名称', + `max_id` bigint(20) NOT NULL DEFAULT 1 COMMENT '最大id', + `step` int(11) NOT NULL DEFAULT 100 COMMENT '步长', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='号段模式序号ID分配表'; + +-- 分布式调度DDL +CREATE TABLE `sj_job` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `job_name` varchar(64) NOT NULL COMMENT '名称', + `args_str` text DEFAULT NULL COMMENT '执行方法参数', + `args_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '参数类型 ', + `next_trigger_at` bigint(13) NOT NULL COMMENT '下次触发时间', + `job_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '任务状态 0、关闭、1、开启', + `task_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '任务类型 1、集群 2、广播 3、切片', + `route_key` tinyint(4) NOT NULL DEFAULT 4 COMMENT '路由策略', + `executor_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '执行器类型', + `executor_info` varchar(255) DEFAULT NULL COMMENT '执行器名称', + `trigger_type` tinyint(4) NOT NULL COMMENT '触发类型 1.CRON 表达式 2. 固定时间', + `trigger_interval` varchar(255) NOT NULL COMMENT '间隔时长', + `block_strategy` tinyint(4) NOT NULL DEFAULT 1 COMMENT '阻塞策略 1、丢弃 2、覆盖 3、并行', + `executor_timeout` int(11) NOT NULL DEFAULT 0 COMMENT '任务执行超时时间,单位秒', + `max_retry_times` int(11) NOT NULL DEFAULT 0 COMMENT '最大重试次数', + `parallel_num` int(11) NOT NULL DEFAULT 1 COMMENT '并行数', + `retry_interval` int(11) NOT NULL DEFAULT 0 COMMENT '重试间隔(s)', + `bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket', + `resident` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否是常驻任务', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`), + KEY `idx_job_status_bucket_index` (`job_status`, `bucket_index`), + KEY `idx_create_dt` (`create_dt`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务信息'; + +CREATE TABLE `sj_job_log_message` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `job_id` bigint(20) NOT NULL COMMENT '任务信息id', + `task_batch_id` bigint(20) NOT NULL COMMENT '任务批次id', + `task_id` bigint(20) NOT NULL COMMENT '调度任务id', + `message` longtext NOT NULL COMMENT '调度信息', + `log_num` int(11) NOT NULL DEFAULT 1 COMMENT '日志数量', + `real_time` bigint(13) NOT NULL DEFAULT 0 COMMENT '上报时间', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_task_batch_id_task_id` (`task_batch_id`, `task_id`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='调度日志'; + +CREATE TABLE `sj_job_task` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `job_id` bigint(20) NOT NULL COMMENT '任务信息id', + `task_batch_id` bigint(20) NOT NULL COMMENT '调度任务id', + `parent_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '父执行器id', + `task_status` tinyint NOT NULL DEFAULT 0 COMMENT '执行的状态 0、失败 1、成功', + `retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '重试次数', + `mr_stage` tinyint DEFAULT NULL COMMENT '动态分片所处阶段 1:map 2:reduce 3:mergeReduce', + `leaf` tinyint NOT NULL DEFAULT '1' COMMENT '叶子节点', + `task_name` varchar(255) NOT NULL DEFAULT '' COMMENT '任务名称', + `client_info` varchar(128) DEFAULT NULL COMMENT '客户端地址 clientId#ip:port', + `wf_context` text DEFAULT NULL COMMENT '工作流全局上下文', + `result_message` text NOT NULL COMMENT '执行结果', + `args_str` text DEFAULT NULL COMMENT '执行方法参数', + `args_type` tinyint NOT NULL DEFAULT 1 COMMENT '参数类型 ', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_task_batch_id_task_status` (`task_batch_id`, `task_status`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务实例'; + +CREATE TABLE `sj_job_task_batch` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `job_id` bigint(20) NOT NULL COMMENT '任务id', + `workflow_node_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '工作流节点id', + `parent_workflow_node_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '工作流任务父批次id', + `workflow_task_batch_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '工作流任务批次id', + `task_batch_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '任务批次状态 0、失败 1、成功', + `operation_reason` tinyint(4) NOT NULL DEFAULT 0 COMMENT '操作原因', + `execution_at` bigint(13) NOT NULL DEFAULT 0 COMMENT '任务执行时间', + `system_task_type` tinyint(4) NOT NULL DEFAULT 3 COMMENT '任务类型 3、JOB任务 4、WORKFLOW任务', + `parent_id` varchar(64) NOT NULL DEFAULT '' COMMENT '父节点', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_job_id_task_batch_status` (`job_id`, `task_batch_status`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`), + KEY `idx_workflow_task_batch_id_workflow_node_id` (`workflow_task_batch_id`, `workflow_node_id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务批次'; + +CREATE TABLE `sj_job_summary` +( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` VARCHAR(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '组名称', + `business_id` bigint NOT NULL COMMENT '业务id (job_id或workflow_id)', + `system_task_type` tinyint(4) NOT NULL DEFAULT 3 COMMENT '任务类型 3、JOB任务 4、WORKFLOW任务', + `trigger_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '统计时间', + `success_num` int NOT NULL DEFAULT 0 COMMENT '执行成功-日志数量', + `fail_num` int NOT NULL DEFAULT 0 COMMENT '执行失败-日志数量', + `fail_reason` varchar(512) NOT NULL DEFAULT '' COMMENT '失败原因', + `stop_num` int NOT NULL DEFAULT 0 COMMENT '执行失败-日志数量', + `stop_reason` varchar(512) NOT NULL DEFAULT '' COMMENT '失败原因', + `cancel_num` int NOT NULL DEFAULT 0 COMMENT '执行失败-日志数量', + `cancel_reason` varchar(512) NOT NULL DEFAULT '' COMMENT '失败原因', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name_business_id` (`namespace_id`, `group_name`, business_id), + UNIQUE KEY `uk_trigger_at_system_task_type_business_id` (`trigger_at`, `system_task_type`, `business_id`) USING BTREE +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 COMMENT ='DashBoard_Job'; + +CREATE TABLE `sj_retry_summary` +( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` VARCHAR(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '组名称', + `scene_name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '场景名称', + `trigger_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '统计时间', + `running_num` int NOT NULL DEFAULT 0 COMMENT '重试中-日志数量', + `finish_num` int NOT NULL DEFAULT 0 COMMENT '重试完成-日志数量', + `max_count_num` int NOT NULL DEFAULT 0 COMMENT '重试到达最大次数-日志数量', + `suspend_num` int NOT NULL DEFAULT 0 COMMENT '暂停重试-日志数量', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_trigger_at` (`trigger_at`), + UNIQUE KEY `uk_scene_name_trigger_at` (`namespace_id`, `group_name`, `scene_name`, `trigger_at`) USING BTREE +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 COMMENT ='DashBoard_Retry'; + +CREATE TABLE `sj_workflow` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `workflow_name` varchar(64) NOT NULL COMMENT '工作流名称', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `workflow_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '工作流状态 0、关闭、1、开启', + `trigger_type` tinyint(4) NOT NULL COMMENT '触发类型 1.CRON 表达式 2. 固定时间', + `trigger_interval` varchar(255) NOT NULL COMMENT '间隔时长', + `next_trigger_at` bigint(13) NOT NULL COMMENT '下次触发时间', + `block_strategy` tinyint(4) NOT NULL DEFAULT 1 COMMENT '阻塞策略 1、丢弃 2、覆盖 3、并行', + `executor_timeout` int(11) NOT NULL DEFAULT 0 COMMENT '任务执行超时时间,单位秒', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `flow_info` text DEFAULT NULL COMMENT '流程信息', + `wf_context` text DEFAULT NULL COMMENT '上下文', + `bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket', + `version` int(11) NOT NULL COMMENT '版本号', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='工作流'; + +CREATE TABLE `sj_workflow_node` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `node_name` varchar(64) NOT NULL COMMENT '节点名称', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `job_id` bigint(20) NOT NULL COMMENT '任务信息id', + `workflow_id` bigint(20) NOT NULL COMMENT '工作流ID', + `node_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1、任务节点 2、条件节点', + `expression_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT '1、SpEl、2、Aviator 3、QL', + `fail_strategy` tinyint(4) NOT NULL DEFAULT 1 COMMENT '失败策略 1、跳过 2、阻塞', + `workflow_node_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '工作流节点状态 0、关闭、1、开启', + `priority_level` int(11) NOT NULL DEFAULT 1 COMMENT '优先级', + `node_info` text DEFAULT NULL COMMENT '节点信息 ', + `version` int(11) NOT NULL COMMENT '版本号', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='工作流节点'; + +CREATE TABLE `sj_workflow_task_batch` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `workflow_id` bigint(20) NOT NULL COMMENT '工作流任务id', + `task_batch_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '任务批次状态 0、失败 1、成功', + `operation_reason` tinyint(4) NOT NULL DEFAULT 0 COMMENT '操作原因', + `flow_info` text DEFAULT NULL COMMENT '流程信息', + `wf_context` text DEFAULT NULL COMMENT '全局上下文', + `execution_at` bigint(13) NOT NULL DEFAULT 0 COMMENT '任务执行时间', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `version` int(11) NOT NULL DEFAULT 1 COMMENT '版本号', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_job_id_task_batch_status` (`workflow_id`, `task_batch_status`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='工作流批次'; \ No newline at end of file diff --git a/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/postgresql/snail-job_data.sql b/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/postgresql/snail-job_data.sql new file mode 100644 index 0000000..ff47729 --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/postgresql/snail-job_data.sql @@ -0,0 +1,14 @@ +-- liquibase formatted sql + +-- changeset snail-job-server:1.1.0 +-- 默认命名空间:Default +INSERT INTO sj_namespace (id, name, unique_id, create_dt, update_dt, deleted) +VALUES (1, 'Default', '764d604ec6fc45f68cd92514c40e9e1a', NOW(), NOW(), 0); + +-- 默认分组:ysoft-admin +INSERT INTO sj_group_config (id, namespace_id, group_name, description, token, group_status, version, group_partition, id_generator_mode, init_scene, bucket_index, create_dt, update_dt) +VALUES (1, '764d604ec6fc45f68cd92514c40e9e1a', 'ysoft-admin', '默认分组', 'SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj', 1, 1, 0, 2, 1, 119, NOW(), NOW()); + +-- 默认用户:admin/admin +INSERT INTO sj_system_user (username, password, role) +VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2); diff --git a/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/postgresql/snail-job_table.sql b/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/postgresql/snail-job_table.sql new file mode 100644 index 0000000..00282ca --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/src/main/resources/db/changelog/postgresql/snail-job_table.sql @@ -0,0 +1,821 @@ +-- liquibase formatted sql + +-- changeset snail-job-server:1.1.0 +-- sj_namespace +CREATE TABLE sj_namespace +( + id bigserial PRIMARY KEY, + name varchar(64) NOT NULL, + unique_id varchar(64) NOT NULL, + description varchar(256) NOT NULL DEFAULT '', + deleted smallint NOT NULL DEFAULT 0, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_namespace_01 ON sj_namespace (name); + +COMMENT ON COLUMN sj_namespace.id IS '主键'; +COMMENT ON COLUMN sj_namespace.name IS '名称'; +COMMENT ON COLUMN sj_namespace.unique_id IS '唯一id'; +COMMENT ON COLUMN sj_namespace.description IS '描述'; +COMMENT ON COLUMN sj_namespace.deleted IS '逻辑删除 1、删除'; +COMMENT ON COLUMN sj_namespace.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_namespace.update_dt IS '修改时间'; +COMMENT ON TABLE sj_namespace IS '命名空间'; + +-- sj_group_config +CREATE TABLE sj_group_config +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL DEFAULT '', + description varchar(256) NOT NULL DEFAULT '', + token varchar(64) NOT NULL DEFAULT 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', + group_status smallint NOT NULL DEFAULT 0, + version int NOT NULL, + group_partition int NOT NULL, + id_generator_mode smallint NOT NULL DEFAULT 1, + init_scene smallint NOT NULL DEFAULT 0, + bucket_index int NOT NULL DEFAULT 0, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX uk_sj_group_config_01 ON sj_group_config (namespace_id, group_name); + +COMMENT ON COLUMN sj_group_config.id IS '主键'; +COMMENT ON COLUMN sj_group_config.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_group_config.group_name IS '组名称'; +COMMENT ON COLUMN sj_group_config.description IS '组描述'; +COMMENT ON COLUMN sj_group_config.token IS 'token'; +COMMENT ON COLUMN sj_group_config.group_status IS '组状态 0、未启用 1、启用'; +COMMENT ON COLUMN sj_group_config.version IS '版本号'; +COMMENT ON COLUMN sj_group_config.group_partition IS '分区'; +COMMENT ON COLUMN sj_group_config.id_generator_mode IS '唯一id生成模式 默认号段模式'; +COMMENT ON COLUMN sj_group_config.init_scene IS '是否初始化场景 0:否 1:是'; +COMMENT ON COLUMN sj_group_config.bucket_index IS 'bucket'; +COMMENT ON COLUMN sj_group_config.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_group_config.update_dt IS '修改时间'; +COMMENT ON TABLE sj_group_config IS '组配置'; + +-- sj_notify_config +CREATE TABLE sj_notify_config +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL, + business_id varchar(64) NOT NULL, + system_task_type smallint NOT NULL DEFAULT 3, + notify_status smallint NOT NULL DEFAULT 0, + recipient_ids varchar(128) NOT NULL, + notify_threshold int NOT NULL DEFAULT 0, + notify_scene smallint NOT NULL DEFAULT 0, + rate_limiter_status smallint NOT NULL DEFAULT 0, + rate_limiter_threshold int NOT NULL DEFAULT 0, + description varchar(256) NOT NULL DEFAULT '', + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_notify_config_01 ON sj_notify_config (namespace_id, group_name, business_id); + +COMMENT ON COLUMN sj_notify_config.id IS '主键'; +COMMENT ON COLUMN sj_notify_config.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_notify_config.group_name IS '组名称'; +COMMENT ON COLUMN sj_notify_config.business_id IS '业务id ( job_id或workflow_id或scene_name ) '; +COMMENT ON COLUMN sj_notify_config.system_task_type IS '任务类型 1. 重试任务 2. 重试回调 3、JOB任务 4、WORKFLOW任务'; +COMMENT ON COLUMN sj_notify_config.notify_status IS '通知状态 0、未启用 1、启用'; +COMMENT ON COLUMN sj_notify_config.recipient_ids IS '接收人id列表'; +COMMENT ON COLUMN sj_notify_config.notify_threshold IS '通知阈值'; +COMMENT ON COLUMN sj_notify_config.notify_scene IS '通知场景'; +COMMENT ON COLUMN sj_notify_config.rate_limiter_status IS '限流状态 0、未启用 1、启用'; +COMMENT ON COLUMN sj_notify_config.rate_limiter_threshold IS '每秒限流阈值'; +COMMENT ON COLUMN sj_notify_config.description IS '描述'; +COMMENT ON COLUMN sj_notify_config.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_notify_config.update_dt IS '修改时间'; +COMMENT ON TABLE sj_notify_config IS '通知配置'; + +-- sj_notify_recipient +CREATE TABLE sj_notify_recipient +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + recipient_name varchar(64) NOT NULL, + notify_type smallint NOT NULL DEFAULT 0, + notify_attribute varchar(512) NOT NULL, + description varchar(256) NOT NULL DEFAULT '', + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_notify_recipient_01 ON sj_notify_recipient (namespace_id); + +COMMENT ON COLUMN sj_notify_recipient.id IS '主键'; +COMMENT ON COLUMN sj_notify_recipient.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_notify_recipient.recipient_name IS '接收人名称'; +COMMENT ON COLUMN sj_notify_recipient.notify_type IS '通知类型 1、钉钉 2、邮件 3、企业微信 4 飞书 5 webhook'; +COMMENT ON COLUMN sj_notify_recipient.notify_attribute IS '配置属性'; +COMMENT ON COLUMN sj_notify_recipient.description IS '描述'; +COMMENT ON COLUMN sj_notify_recipient.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_notify_recipient.update_dt IS '修改时间'; +COMMENT ON TABLE sj_notify_recipient IS '告警通知接收人'; + +-- sj_retry_dead_letter_0 +CREATE TABLE sj_retry_dead_letter_0 +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + unique_id varchar(64) NOT NULL, + group_name varchar(64) NOT NULL, + scene_name varchar(64) NOT NULL, + idempotent_id varchar(64) NOT NULL, + biz_no varchar(64) NOT NULL DEFAULT '', + executor_name varchar(512) NOT NULL DEFAULT '', + args_str text NOT NULL, + ext_attrs text NOT NULL, + task_type smallint NOT NULL DEFAULT 1, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX uk_sj_retry_dead_letter_0_01 ON sj_retry_dead_letter_0 (namespace_id, group_name, unique_id); + +CREATE INDEX idx_sj_retry_dead_letter_0_01 ON sj_retry_dead_letter_0 (namespace_id, group_name, scene_name); +CREATE INDEX idx_sj_retry_dead_letter_0_02 ON sj_retry_dead_letter_0 (idempotent_id); +CREATE INDEX idx_sj_retry_dead_letter_0_03 ON sj_retry_dead_letter_0 (biz_no); +CREATE INDEX idx_sj_retry_dead_letter_0_04 ON sj_retry_dead_letter_0 (create_dt); + +COMMENT ON COLUMN sj_retry_dead_letter_0.id IS '主键'; +COMMENT ON COLUMN sj_retry_dead_letter_0.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_retry_dead_letter_0.unique_id IS '同组下id唯一'; +COMMENT ON COLUMN sj_retry_dead_letter_0.group_name IS '组名称'; +COMMENT ON COLUMN sj_retry_dead_letter_0.scene_name IS '场景名称'; +COMMENT ON COLUMN sj_retry_dead_letter_0.idempotent_id IS '幂等id'; +COMMENT ON COLUMN sj_retry_dead_letter_0.biz_no IS '业务编号'; +COMMENT ON COLUMN sj_retry_dead_letter_0.executor_name IS '执行器名称'; +COMMENT ON COLUMN sj_retry_dead_letter_0.args_str IS '执行方法参数'; +COMMENT ON COLUMN sj_retry_dead_letter_0.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_retry_dead_letter_0.task_type IS '任务类型 1、重试数据 2、回调数据'; +COMMENT ON COLUMN sj_retry_dead_letter_0.create_dt IS '创建时间'; +COMMENT ON TABLE sj_retry_dead_letter_0 IS '死信队列表'; + +-- sj_retry_task_0 +CREATE TABLE sj_retry_task_0 +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + unique_id varchar(64) NOT NULL, + group_name varchar(64) NOT NULL, + scene_name varchar(64) NOT NULL, + idempotent_id varchar(64) NOT NULL, + biz_no varchar(64) NOT NULL DEFAULT '', + executor_name varchar(512) NOT NULL DEFAULT '', + args_str text NOT NULL, + ext_attrs text NOT NULL, + next_trigger_at timestamp NOT NULL, + retry_count int NOT NULL DEFAULT 0, + retry_status smallint NOT NULL DEFAULT 0, + task_type smallint NOT NULL DEFAULT 1, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX uk_sj_retry_task_0_01 ON sj_retry_task_0 (namespace_id, group_name, unique_id); + +CREATE INDEX idx_sj_retry_task_0_01 ON sj_retry_task_0 (namespace_id, group_name, scene_name); +CREATE INDEX idx_sj_retry_task_0_02 ON sj_retry_task_0 (namespace_id, group_name, task_type); +CREATE INDEX idx_sj_retry_task_0_03 ON sj_retry_task_0 (namespace_id, group_name, retry_status); +CREATE INDEX idx_sj_retry_task_0_04 ON sj_retry_task_0 (idempotent_id); +CREATE INDEX idx_sj_retry_task_0_05 ON sj_retry_task_0 (biz_no); +CREATE INDEX idx_sj_retry_task_0_06 ON sj_retry_task_0 (create_dt); + +COMMENT ON COLUMN sj_retry_task_0.id IS '主键'; +COMMENT ON COLUMN sj_retry_task_0.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_retry_task_0.unique_id IS '同组下id唯一'; +COMMENT ON COLUMN sj_retry_task_0.group_name IS '组名称'; +COMMENT ON COLUMN sj_retry_task_0.scene_name IS '场景名称'; +COMMENT ON COLUMN sj_retry_task_0.idempotent_id IS '幂等id'; +COMMENT ON COLUMN sj_retry_task_0.biz_no IS '业务编号'; +COMMENT ON COLUMN sj_retry_task_0.executor_name IS '执行器名称'; +COMMENT ON COLUMN sj_retry_task_0.args_str IS '执行方法参数'; +COMMENT ON COLUMN sj_retry_task_0.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_retry_task_0.next_trigger_at IS '下次触发时间'; +COMMENT ON COLUMN sj_retry_task_0.retry_count IS '重试次数'; +COMMENT ON COLUMN sj_retry_task_0.retry_status IS '重试状态 0、重试中 1、成功 2、最大重试次数'; +COMMENT ON COLUMN sj_retry_task_0.task_type IS '任务类型 1、重试数据 2、回调数据'; +COMMENT ON COLUMN sj_retry_task_0.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_retry_task_0.update_dt IS '修改时间'; +COMMENT ON TABLE sj_retry_task_0 IS '任务表'; + +-- sj_retry_task_log +CREATE TABLE sj_retry_task_log +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + unique_id varchar(64) NOT NULL, + group_name varchar(64) NOT NULL, + scene_name varchar(64) NOT NULL, + idempotent_id varchar(64) NOT NULL, + biz_no varchar(64) NOT NULL DEFAULT '', + executor_name varchar(512) NOT NULL DEFAULT '', + args_str text NOT NULL, + ext_attrs text NOT NULL, + retry_status smallint NOT NULL DEFAULT 0, + task_type smallint NOT NULL DEFAULT 1, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_retry_task_log_01 ON sj_retry_task_log (namespace_id, group_name, scene_name); +CREATE INDEX idx_sj_retry_task_log_02 ON sj_retry_task_log (retry_status); +CREATE INDEX idx_sj_retry_task_log_03 ON sj_retry_task_log (idempotent_id); +CREATE INDEX idx_sj_retry_task_log_04 ON sj_retry_task_log (unique_id); +CREATE INDEX idx_sj_retry_task_log_05 ON sj_retry_task_log (biz_no); +CREATE INDEX idx_sj_retry_task_log_06 ON sj_retry_task_log (create_dt); + +COMMENT ON COLUMN sj_retry_task_log.id IS '主键'; +COMMENT ON COLUMN sj_retry_task_log.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_retry_task_log.unique_id IS '同组下id唯一'; +COMMENT ON COLUMN sj_retry_task_log.group_name IS '组名称'; +COMMENT ON COLUMN sj_retry_task_log.scene_name IS '场景名称'; +COMMENT ON COLUMN sj_retry_task_log.idempotent_id IS '幂等id'; +COMMENT ON COLUMN sj_retry_task_log.biz_no IS '业务编号'; +COMMENT ON COLUMN sj_retry_task_log.executor_name IS '执行器名称'; +COMMENT ON COLUMN sj_retry_task_log.args_str IS '执行方法参数'; +COMMENT ON COLUMN sj_retry_task_log.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_retry_task_log.retry_status IS '重试状态 0、重试中 1、成功 2、最大次数'; +COMMENT ON COLUMN sj_retry_task_log.task_type IS '任务类型 1、重试数据 2、回调数据'; +COMMENT ON COLUMN sj_retry_task_log.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_retry_task_log.update_dt IS '修改时间'; +COMMENT ON TABLE sj_retry_task_log IS '任务日志基础信息表'; + +-- sj_retry_task_log_message +CREATE TABLE sj_retry_task_log_message +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL, + unique_id varchar(64) NOT NULL, + message text NOT NULL, + log_num int NOT NULL DEFAULT 1, + real_time bigint NOT NULL DEFAULT 0, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_retry_task_log_message_01 ON sj_retry_task_log_message (namespace_id, group_name, unique_id); +CREATE INDEX idx_sj_retry_task_log_message_02 ON sj_retry_task_log_message (create_dt); + +COMMENT ON COLUMN sj_retry_task_log_message.id IS '主键'; +COMMENT ON COLUMN sj_retry_task_log_message.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_retry_task_log_message.group_name IS '组名称'; +COMMENT ON COLUMN sj_retry_task_log_message.unique_id IS '同组下id唯一'; +COMMENT ON COLUMN sj_retry_task_log_message.message IS '异常信息'; +COMMENT ON COLUMN sj_retry_task_log_message.log_num IS '日志数量'; +COMMENT ON COLUMN sj_retry_task_log_message.real_time IS '上报时间'; +COMMENT ON COLUMN sj_retry_task_log_message.create_dt IS '创建时间'; +COMMENT ON TABLE sj_retry_task_log_message IS '任务调度日志信息记录表'; + +-- sj_retry_scene_config +CREATE TABLE sj_retry_scene_config +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + scene_name varchar(64) NOT NULL, + group_name varchar(64) NOT NULL, + scene_status smallint NOT NULL DEFAULT 0, + max_retry_count int NOT NULL DEFAULT 5, + back_off smallint NOT NULL DEFAULT 1, + trigger_interval varchar(16) NOT NULL DEFAULT '', + deadline_request bigint NOT NULL DEFAULT 60000, + executor_timeout int NOT NULL DEFAULT 5, + route_key smallint NOT NULL DEFAULT 4, + description varchar(256) NOT NULL DEFAULT '', + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX uk_sj_retry_scene_config_01 ON sj_retry_scene_config (namespace_id, group_name, scene_name); + +COMMENT ON COLUMN sj_retry_scene_config.id IS '主键'; +COMMENT ON COLUMN sj_retry_scene_config.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_retry_scene_config.scene_name IS '场景名称'; +COMMENT ON COLUMN sj_retry_scene_config.group_name IS '组名称'; +COMMENT ON COLUMN sj_retry_scene_config.scene_status IS '组状态 0、未启用 1、启用'; +COMMENT ON COLUMN sj_retry_scene_config.max_retry_count IS '最大重试次数'; +COMMENT ON COLUMN sj_retry_scene_config.back_off IS '1、默认等级 2、固定间隔时间 3、CRON 表达式'; +COMMENT ON COLUMN sj_retry_scene_config.trigger_interval IS '间隔时长'; +COMMENT ON COLUMN sj_retry_scene_config.deadline_request IS 'Deadline Request 调用链超时 单位毫秒'; +COMMENT ON COLUMN sj_retry_scene_config.executor_timeout IS '任务执行超时时间,单位秒'; +COMMENT ON COLUMN sj_retry_scene_config.route_key IS '路由策略'; +COMMENT ON COLUMN sj_retry_scene_config.description IS '描述'; +COMMENT ON COLUMN sj_retry_scene_config.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_retry_scene_config.update_dt IS '修改时间'; +COMMENT ON TABLE sj_retry_scene_config IS '场景配置'; + +-- sj_server_node +CREATE TABLE sj_server_node +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL, + host_id varchar(64) NOT NULL, + host_ip varchar(64) NOT NULL, + host_port int NOT NULL, + expire_at timestamp NOT NULL, + node_type smallint NOT NULL, + ext_attrs varchar(256) NULL DEFAULT '', + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX uk_sj_server_node_01 ON sj_server_node (host_id, host_ip); + +CREATE INDEX idx_sj_server_node_01 ON sj_server_node (namespace_id, group_name); +CREATE INDEX idx_sj_server_node_02 ON sj_server_node (expire_at, node_type); + +COMMENT ON COLUMN sj_server_node.id IS '主键'; +COMMENT ON COLUMN sj_server_node.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_server_node.group_name IS '组名称'; +COMMENT ON COLUMN sj_server_node.host_id IS '主机id'; +COMMENT ON COLUMN sj_server_node.host_ip IS '机器ip'; +COMMENT ON COLUMN sj_server_node.host_port IS '机器端口'; +COMMENT ON COLUMN sj_server_node.expire_at IS '过期时间'; +COMMENT ON COLUMN sj_server_node.node_type IS '节点类型 1、客户端 2、是服务端'; +COMMENT ON COLUMN sj_server_node.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_server_node.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_server_node.update_dt IS '修改时间'; +COMMENT ON TABLE sj_server_node IS '服务器节点'; + +-- sj_distributed_lock +CREATE TABLE sj_distributed_lock +( + name varchar(64) PRIMARY KEY, + lock_until timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + locked_at timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + locked_by varchar(255) NOT NULL, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +COMMENT ON COLUMN sj_distributed_lock.name IS '锁名称'; +COMMENT ON COLUMN sj_distributed_lock.lock_until IS '锁定时长'; +COMMENT ON COLUMN sj_distributed_lock.locked_at IS '锁定时间'; +COMMENT ON COLUMN sj_distributed_lock.locked_by IS '锁定者'; +COMMENT ON COLUMN sj_distributed_lock.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_distributed_lock.update_dt IS '修改时间'; +COMMENT ON TABLE sj_distributed_lock IS '锁定表'; + +-- sj_system_user +CREATE TABLE sj_system_user +( + id bigserial PRIMARY KEY, + username varchar(64) NOT NULL, + password varchar(128) NOT NULL, + role smallint NOT NULL DEFAULT 0, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +COMMENT ON COLUMN sj_system_user.id IS '主键'; +COMMENT ON COLUMN sj_system_user.username IS '账号'; +COMMENT ON COLUMN sj_system_user.password IS '密码'; +COMMENT ON COLUMN sj_system_user.role IS '角色:1-普通用户、2-管理员'; +COMMENT ON COLUMN sj_system_user.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_system_user.update_dt IS '修改时间'; +COMMENT ON TABLE sj_system_user IS '系统用户表'; + +-- sj_system_user_permission +CREATE TABLE sj_system_user_permission +( + id bigserial PRIMARY KEY, + group_name varchar(64) NOT NULL, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + system_user_id bigint NOT NULL, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX uk_sj_system_user_permission_01 ON sj_system_user_permission (namespace_id, group_name, system_user_id); + +COMMENT ON COLUMN sj_system_user_permission.id IS '主键'; +COMMENT ON COLUMN sj_system_user_permission.group_name IS '组名称'; +COMMENT ON COLUMN sj_system_user_permission.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_system_user_permission.system_user_id IS '系统用户id'; +COMMENT ON COLUMN sj_system_user_permission.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_system_user_permission.update_dt IS '修改时间'; +COMMENT ON TABLE sj_system_user_permission IS '系统用户权限表'; + +-- sj_sequence_alloc +CREATE TABLE sj_sequence_alloc +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL DEFAULT '', + max_id bigint NOT NULL DEFAULT 1, + step int NOT NULL DEFAULT 100, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX uk_sj_sequence_alloc_01 ON sj_sequence_alloc (namespace_id, group_name); + +COMMENT ON COLUMN sj_sequence_alloc.id IS '主键'; +COMMENT ON COLUMN sj_sequence_alloc.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_sequence_alloc.group_name IS '组名称'; +COMMENT ON COLUMN sj_sequence_alloc.max_id IS '最大id'; +COMMENT ON COLUMN sj_sequence_alloc.step IS '步长'; +COMMENT ON COLUMN sj_sequence_alloc.update_dt IS '更新时间'; +COMMENT ON TABLE sj_sequence_alloc IS '号段模式序号ID分配表'; + +-- sj_job +CREATE TABLE sj_job +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL, + job_name varchar(64) NOT NULL, + args_str text NULL DEFAULT NULL, + args_type smallint NOT NULL DEFAULT 1, + next_trigger_at bigint NOT NULL, + job_status smallint NOT NULL DEFAULT 1, + task_type smallint NOT NULL DEFAULT 1, + route_key smallint NOT NULL DEFAULT 4, + executor_type smallint NOT NULL DEFAULT 1, + executor_info varchar(255) NULL DEFAULT NULL, + trigger_type smallint NOT NULL, + trigger_interval varchar(255) NOT NULL, + block_strategy smallint NOT NULL DEFAULT 1, + executor_timeout int NOT NULL DEFAULT 0, + max_retry_times int NOT NULL DEFAULT 0, + parallel_num int NOT NULL DEFAULT 1, + retry_interval int NOT NULL DEFAULT 0, + bucket_index int NOT NULL DEFAULT 0, + resident smallint NOT NULL DEFAULT 0, + description varchar(256) NOT NULL DEFAULT '', + ext_attrs varchar(256) NULL DEFAULT '', + deleted smallint NOT NULL DEFAULT 0, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_job_01 ON sj_job (namespace_id, group_name); +CREATE INDEX idx_sj_job_02 ON sj_job (job_status, bucket_index); +CREATE INDEX idx_sj_job_03 ON sj_job (create_dt); + +COMMENT ON COLUMN sj_job.id IS '主键'; +COMMENT ON COLUMN sj_job.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_job.group_name IS '组名称'; +COMMENT ON COLUMN sj_job.job_name IS '名称'; +COMMENT ON COLUMN sj_job.args_str IS '执行方法参数'; +COMMENT ON COLUMN sj_job.args_type IS '参数类型 '; +COMMENT ON COLUMN sj_job.next_trigger_at IS '下次触发时间'; +COMMENT ON COLUMN sj_job.job_status IS '任务状态 0、关闭、1、开启'; +COMMENT ON COLUMN sj_job.task_type IS '任务类型 1、集群 2、广播 3、切片'; +COMMENT ON COLUMN sj_job.route_key IS '路由策略'; +COMMENT ON COLUMN sj_job.executor_type IS '执行器类型'; +COMMENT ON COLUMN sj_job.executor_info IS '执行器名称'; +COMMENT ON COLUMN sj_job.trigger_type IS '触发类型 1.CRON 表达式 2. 固定时间'; +COMMENT ON COLUMN sj_job.trigger_interval IS '间隔时长'; +COMMENT ON COLUMN sj_job.block_strategy IS '阻塞策略 1、丢弃 2、覆盖 3、并行'; +COMMENT ON COLUMN sj_job.executor_timeout IS '任务执行超时时间,单位秒'; +COMMENT ON COLUMN sj_job.max_retry_times IS '最大重试次数'; +COMMENT ON COLUMN sj_job.parallel_num IS '并行数'; +COMMENT ON COLUMN sj_job.retry_interval IS '重试间隔 ( s ) '; +COMMENT ON COLUMN sj_job.bucket_index IS 'bucket'; +COMMENT ON COLUMN sj_job.resident IS '是否是常驻任务'; +COMMENT ON COLUMN sj_job.description IS '描述'; +COMMENT ON COLUMN sj_job.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_job.deleted IS '逻辑删除 1、删除'; +COMMENT ON COLUMN sj_job.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_job.update_dt IS '修改时间'; +COMMENT ON TABLE sj_job IS '任务信息'; + +-- sj_job_log_message +CREATE TABLE sj_job_log_message +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL, + job_id bigint NOT NULL, + task_batch_id bigint NOT NULL, + task_id bigint NOT NULL, + message text NOT NULL, + log_num int NOT NULL DEFAULT 1, + real_time bigint NOT NULL DEFAULT 0, + ext_attrs varchar(256) NULL DEFAULT '', + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_job_log_message_01 ON sj_job_log_message (task_batch_id, task_id); +CREATE INDEX idx_sj_job_log_message_02 ON sj_job_log_message (create_dt); +CREATE INDEX idx_sj_job_log_message_03 ON sj_job_log_message (namespace_id, group_name); + +COMMENT ON COLUMN sj_job_log_message.id IS '主键'; +COMMENT ON COLUMN sj_job_log_message.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_job_log_message.group_name IS '组名称'; +COMMENT ON COLUMN sj_job_log_message.job_id IS '任务信息id'; +COMMENT ON COLUMN sj_job_log_message.task_batch_id IS '任务批次id'; +COMMENT ON COLUMN sj_job_log_message.task_id IS '调度任务id'; +COMMENT ON COLUMN sj_job_log_message.message IS '调度信息'; +COMMENT ON COLUMN sj_job_log_message.log_num IS '日志数量'; +COMMENT ON COLUMN sj_job_log_message.real_time IS '上报时间'; +COMMENT ON COLUMN sj_job_log_message.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_job_log_message.create_dt IS '创建时间'; +COMMENT ON TABLE sj_job_log_message IS '调度日志'; + +-- sj_job_task +CREATE TABLE sj_job_task +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL, + job_id bigint NOT NULL, + task_batch_id bigint NOT NULL, + parent_id bigint NOT NULL DEFAULT 0, + task_status smallint NOT NULL DEFAULT 0, + retry_count int NOT NULL DEFAULT 0, + mr_stage smallint NULL DEFAULT NULL, + leaf smallint NOT NULL DEFAULT '1', + task_name varchar(255) NOT NULL DEFAULT '', + client_info varchar(128) NULL DEFAULT NULL, + wf_context text NULL DEFAULT NULL, + result_message text NOT NULL, + args_str text NULL DEFAULT NULL, + args_type smallint NOT NULL DEFAULT 1, + ext_attrs varchar(256) NULL DEFAULT '', + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_job_task_01 ON sj_job_task (task_batch_id, task_status); +CREATE INDEX idx_sj_job_task_02 ON sj_job_task (create_dt); +CREATE INDEX idx_sj_job_task_03 ON sj_job_task (namespace_id, group_name); + +COMMENT ON COLUMN sj_job_task.id IS '主键'; +COMMENT ON COLUMN sj_job_task.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_job_task.group_name IS '组名称'; +COMMENT ON COLUMN sj_job_task.job_id IS '任务信息id'; +COMMENT ON COLUMN sj_job_task.task_batch_id IS '调度任务id'; +COMMENT ON COLUMN sj_job_task.parent_id IS '父执行器id'; +COMMENT ON COLUMN sj_job_task.task_status IS '执行的状态 0、失败 1、成功'; +COMMENT ON COLUMN sj_job_task.retry_count IS '重试次数'; +COMMENT ON COLUMN sj_job_task.mr_stage IS '动态分片所处阶段 1:map 2:reduce 3:mergeReduce'; +COMMENT ON COLUMN sj_job_task.leaf IS '叶子节点'; +COMMENT ON COLUMN sj_job_task.task_name IS '任务名称'; +COMMENT ON COLUMN sj_job_task.client_info IS '客户端地址 clientId#ip:port'; +COMMENT ON COLUMN sj_job_task.wf_context IS '工作流全局上下文'; +COMMENT ON COLUMN sj_job_task.result_message IS '执行结果'; +COMMENT ON COLUMN sj_job_task.args_str IS '执行方法参数'; +COMMENT ON COLUMN sj_job_task.args_type IS '参数类型 '; +COMMENT ON COLUMN sj_job_task.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_job_task.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_job_task.update_dt IS '修改时间'; +COMMENT ON TABLE sj_job_task IS '任务实例'; + +-- sj_job_task_batch +CREATE TABLE sj_job_task_batch +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL, + job_id bigint NOT NULL, + workflow_node_id bigint NOT NULL DEFAULT 0, + parent_workflow_node_id bigint NOT NULL DEFAULT 0, + workflow_task_batch_id bigint NOT NULL DEFAULT 0, + task_batch_status smallint NOT NULL DEFAULT 0, + operation_reason smallint NOT NULL DEFAULT 0, + execution_at bigint NOT NULL DEFAULT 0, + system_task_type smallint NOT NULL DEFAULT 3, + parent_id varchar(64) NOT NULL DEFAULT '', + ext_attrs varchar(256) NULL DEFAULT '', + deleted smallint NOT NULL DEFAULT 0, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_job_task_batch_01 ON sj_job_task_batch (job_id, task_batch_status); +CREATE INDEX idx_sj_job_task_batch_02 ON sj_job_task_batch (create_dt); +CREATE INDEX idx_sj_job_task_batch_03 ON sj_job_task_batch (namespace_id, group_name); +CREATE INDEX idx_sj_job_task_batch_04 ON sj_job_task_batch (workflow_task_batch_id, workflow_node_id); + +COMMENT ON COLUMN sj_job_task_batch.id IS '主键'; +COMMENT ON COLUMN sj_job_task_batch.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_job_task_batch.group_name IS '组名称'; +COMMENT ON COLUMN sj_job_task_batch.job_id IS '任务id'; +COMMENT ON COLUMN sj_job_task_batch.workflow_node_id IS '工作流节点id'; +COMMENT ON COLUMN sj_job_task_batch.parent_workflow_node_id IS '工作流任务父批次id'; +COMMENT ON COLUMN sj_job_task_batch.workflow_task_batch_id IS '工作流任务批次id'; +COMMENT ON COLUMN sj_job_task_batch.task_batch_status IS '任务批次状态 0、失败 1、成功'; +COMMENT ON COLUMN sj_job_task_batch.operation_reason IS '操作原因'; +COMMENT ON COLUMN sj_job_task_batch.execution_at IS '任务执行时间'; +COMMENT ON COLUMN sj_job_task_batch.system_task_type IS '任务类型 3、JOB任务 4、WORKFLOW任务'; +COMMENT ON COLUMN sj_job_task_batch.parent_id IS '父节点'; +COMMENT ON COLUMN sj_job_task_batch.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_job_task_batch.deleted IS '逻辑删除 1、删除'; +COMMENT ON COLUMN sj_job_task_batch.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_job_task_batch.update_dt IS '修改时间'; +COMMENT ON TABLE sj_job_task_batch IS '任务批次'; + +-- sj_job_summary +CREATE TABLE sj_job_summary +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL DEFAULT '', + business_id bigint NOT NULL, + system_task_type smallint NOT NULL DEFAULT 3, + trigger_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + success_num int NOT NULL DEFAULT 0, + fail_num int NOT NULL DEFAULT 0, + fail_reason varchar(512) NOT NULL DEFAULT '', + stop_num int NOT NULL DEFAULT 0, + stop_reason varchar(512) NOT NULL DEFAULT '', + cancel_num int NOT NULL DEFAULT 0, + cancel_reason varchar(512) NOT NULL DEFAULT '', + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX uk_sj_job_summary_01 ON sj_job_summary (trigger_at, system_task_type, business_id); + +CREATE INDEX idx_sj_job_summary_01 ON sj_job_summary (namespace_id, group_name, business_id); + +COMMENT ON COLUMN sj_job_summary.id IS '主键'; +COMMENT ON COLUMN sj_job_summary.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_job_summary.group_name IS '组名称'; +COMMENT ON COLUMN sj_job_summary.business_id IS '业务id ( job_id或workflow_id ) '; +COMMENT ON COLUMN sj_job_summary.system_task_type IS '任务类型 3、JOB任务 4、WORKFLOW任务'; +COMMENT ON COLUMN sj_job_summary.trigger_at IS '统计时间'; +COMMENT ON COLUMN sj_job_summary.success_num IS '执行成功-日志数量'; +COMMENT ON COLUMN sj_job_summary.fail_num IS '执行失败-日志数量'; +COMMENT ON COLUMN sj_job_summary.fail_reason IS '失败原因'; +COMMENT ON COLUMN sj_job_summary.stop_num IS '执行失败-日志数量'; +COMMENT ON COLUMN sj_job_summary.stop_reason IS '失败原因'; +COMMENT ON COLUMN sj_job_summary.cancel_num IS '执行失败-日志数量'; +COMMENT ON COLUMN sj_job_summary.cancel_reason IS '失败原因'; +COMMENT ON COLUMN sj_job_summary.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_job_summary.update_dt IS '修改时间'; +COMMENT ON TABLE sj_job_summary IS 'DashBoard_Job'; + +-- sj_retry_summary +CREATE TABLE sj_retry_summary +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL DEFAULT '', + scene_name varchar(50) NOT NULL DEFAULT '', + trigger_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + running_num int NOT NULL DEFAULT 0, + finish_num int NOT NULL DEFAULT 0, + max_count_num int NOT NULL DEFAULT 0, + suspend_num int NOT NULL DEFAULT 0, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX uk_sj_retry_summary_01 ON sj_retry_summary (namespace_id, group_name, scene_name, trigger_at); + +CREATE INDEX idx_sj_retry_summary_01 ON sj_retry_summary (trigger_at); + +COMMENT ON COLUMN sj_retry_summary.id IS '主键'; +COMMENT ON COLUMN sj_retry_summary.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_retry_summary.group_name IS '组名称'; +COMMENT ON COLUMN sj_retry_summary.scene_name IS '场景名称'; +COMMENT ON COLUMN sj_retry_summary.trigger_at IS '统计时间'; +COMMENT ON COLUMN sj_retry_summary.running_num IS '重试中-日志数量'; +COMMENT ON COLUMN sj_retry_summary.finish_num IS '重试完成-日志数量'; +COMMENT ON COLUMN sj_retry_summary.max_count_num IS '重试到达最大次数-日志数量'; +COMMENT ON COLUMN sj_retry_summary.suspend_num IS '暂停重试-日志数量'; +COMMENT ON COLUMN sj_retry_summary.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_retry_summary.update_dt IS '修改时间'; +COMMENT ON TABLE sj_retry_summary IS 'DashBoard_Retry'; + +-- sj_workflow +CREATE TABLE sj_workflow +( + id bigserial PRIMARY KEY, + workflow_name varchar(64) NOT NULL, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL, + workflow_status smallint NOT NULL DEFAULT 1, + trigger_type smallint NOT NULL, + trigger_interval varchar(255) NOT NULL, + next_trigger_at bigint NOT NULL, + block_strategy smallint NOT NULL DEFAULT 1, + executor_timeout int NOT NULL DEFAULT 0, + description varchar(256) NOT NULL DEFAULT '', + flow_info text NULL DEFAULT NULL, + wf_context text NULL DEFAULT NULL, + bucket_index int NOT NULL DEFAULT 0, + version int NOT NULL, + ext_attrs varchar(256) NULL DEFAULT '', + deleted smallint NOT NULL DEFAULT 0, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_workflow_01 ON sj_workflow (create_dt); +CREATE INDEX idx_sj_workflow_02 ON sj_workflow (namespace_id, group_name); + +COMMENT ON COLUMN sj_workflow.id IS '主键'; +COMMENT ON COLUMN sj_workflow.workflow_name IS '工作流名称'; +COMMENT ON COLUMN sj_workflow.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_workflow.group_name IS '组名称'; +COMMENT ON COLUMN sj_workflow.workflow_status IS '工作流状态 0、关闭、1、开启'; +COMMENT ON COLUMN sj_workflow.trigger_type IS '触发类型 1.CRON 表达式 2. 固定时间'; +COMMENT ON COLUMN sj_workflow.trigger_interval IS '间隔时长'; +COMMENT ON COLUMN sj_workflow.next_trigger_at IS '下次触发时间'; +COMMENT ON COLUMN sj_workflow.block_strategy IS '阻塞策略 1、丢弃 2、覆盖 3、并行'; +COMMENT ON COLUMN sj_workflow.executor_timeout IS '任务执行超时时间,单位秒'; +COMMENT ON COLUMN sj_workflow.description IS '描述'; +COMMENT ON COLUMN sj_workflow.flow_info IS '流程信息'; +COMMENT ON COLUMN sj_workflow.wf_context IS '上下文'; +COMMENT ON COLUMN sj_workflow.bucket_index IS 'bucket'; +COMMENT ON COLUMN sj_workflow.version IS '版本号'; +COMMENT ON COLUMN sj_workflow.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_workflow.deleted IS '逻辑删除 1、删除'; +COMMENT ON COLUMN sj_workflow.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_workflow.update_dt IS '修改时间'; +COMMENT ON TABLE sj_workflow IS '工作流'; + +-- sj_workflow_node +CREATE TABLE sj_workflow_node +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + node_name varchar(64) NOT NULL, + group_name varchar(64) NOT NULL, + job_id bigint NOT NULL, + workflow_id bigint NOT NULL, + node_type smallint NOT NULL DEFAULT 1, + expression_type smallint NOT NULL DEFAULT 0, + fail_strategy smallint NOT NULL DEFAULT 1, + workflow_node_status smallint NOT NULL DEFAULT 1, + priority_level int NOT NULL DEFAULT 1, + node_info text NULL DEFAULT NULL, + version int NOT NULL, + ext_attrs varchar(256) NULL DEFAULT '', + deleted smallint NOT NULL DEFAULT 0, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_workflow_node_01 ON sj_workflow_node (create_dt); +CREATE INDEX idx_sj_workflow_node_02 ON sj_workflow_node (namespace_id, group_name); + +COMMENT ON COLUMN sj_workflow_node.id IS '主键'; +COMMENT ON COLUMN sj_workflow_node.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_workflow_node.node_name IS '节点名称'; +COMMENT ON COLUMN sj_workflow_node.group_name IS '组名称'; +COMMENT ON COLUMN sj_workflow_node.job_id IS '任务信息id'; +COMMENT ON COLUMN sj_workflow_node.workflow_id IS '工作流ID'; +COMMENT ON COLUMN sj_workflow_node.node_type IS '1、任务节点 2、条件节点'; +COMMENT ON COLUMN sj_workflow_node.expression_type IS '1、SpEl、2、Aviator 3、QL'; +COMMENT ON COLUMN sj_workflow_node.fail_strategy IS '失败策略 1、跳过 2、阻塞'; +COMMENT ON COLUMN sj_workflow_node.workflow_node_status IS '工作流节点状态 0、关闭、1、开启'; +COMMENT ON COLUMN sj_workflow_node.priority_level IS '优先级'; +COMMENT ON COLUMN sj_workflow_node.node_info IS '节点信息 '; +COMMENT ON COLUMN sj_workflow_node.version IS '版本号'; +COMMENT ON COLUMN sj_workflow_node.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_workflow_node.deleted IS '逻辑删除 1、删除'; +COMMENT ON COLUMN sj_workflow_node.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_workflow_node.update_dt IS '修改时间'; +COMMENT ON TABLE sj_workflow_node IS '工作流节点'; + +-- sj_workflow_task_batch +CREATE TABLE sj_workflow_task_batch +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL, + workflow_id bigint NOT NULL, + task_batch_status smallint NOT NULL DEFAULT 0, + operation_reason smallint NOT NULL DEFAULT 0, + flow_info text NULL DEFAULT NULL, + wf_context text NULL DEFAULT NULL, + execution_at bigint NOT NULL DEFAULT 0, + ext_attrs varchar(256) NULL DEFAULT '', + version int NOT NULL DEFAULT 1, + deleted smallint NOT NULL DEFAULT 0, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_workflow_task_batch_01 ON sj_workflow_task_batch (workflow_id, task_batch_status); +CREATE INDEX idx_sj_workflow_task_batch_02 ON sj_workflow_task_batch (create_dt); +CREATE INDEX idx_sj_workflow_task_batch_03 ON sj_workflow_task_batch (namespace_id, group_name); + +COMMENT ON COLUMN sj_workflow_task_batch.id IS '主键'; +COMMENT ON COLUMN sj_workflow_task_batch.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_workflow_task_batch.group_name IS '组名称'; +COMMENT ON COLUMN sj_workflow_task_batch.workflow_id IS '工作流任务id'; +COMMENT ON COLUMN sj_workflow_task_batch.task_batch_status IS '任务批次状态 0、失败 1、成功'; +COMMENT ON COLUMN sj_workflow_task_batch.operation_reason IS '操作原因'; +COMMENT ON COLUMN sj_workflow_task_batch.flow_info IS '流程信息'; +COMMENT ON COLUMN sj_workflow_task_batch.wf_context IS '全局上下文'; +COMMENT ON COLUMN sj_workflow_task_batch.execution_at IS '任务执行时间'; +COMMENT ON COLUMN sj_workflow_task_batch.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_workflow_task_batch.version IS '版本号'; +COMMENT ON COLUMN sj_workflow_task_batch.deleted IS '逻辑删除 1、删除'; +COMMENT ON COLUMN sj_workflow_task_batch.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_workflow_task_batch.update_dt IS '修改时间'; +COMMENT ON TABLE sj_workflow_task_batch IS '工作流批次'; \ No newline at end of file diff --git a/wms-extension/wms-extension-schedule-server/src/main/resources/logback-spring.xml b/wms-extension/wms-extension-schedule-server/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..8a911d2 --- /dev/null +++ b/wms-extension/wms-extension-schedule-server/src/main/resources/logback-spring.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + ${LOG_CHARSET} + + + + + + + ${FILE_LOG_PATTERN} + ${LOG_CHARSET} + + + + + + + ${LOG_PATH}/${APP_NAME}.log + + + + ${LOG_PATH}/%d{yyyy-MM-dd}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log.gz + + ${FILE_MAX_SIZE} + + ${FILE_MAX_HISTORY} + + + ${FILE_LOG_PATTERN} + ${LOG_CHARSET} + + + + + + + 0 + + 512 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wms-module-system/.flattened-pom.xml b/wms-module-system/.flattened-pom.xml new file mode 100644 index 0000000..4c89b7a --- /dev/null +++ b/wms-module-system/.flattened-pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + top.ysoft + ysoft-admin + 3.6.0-SNAPSHOT + + ysoft-module-system + 3.6.0-SNAPSHOT + 系统管理模块(存放系统管理相关业务功能,例如:部门管理、角色管理、用户管理等) + + + GNU LESSER GENERAL PUBLIC LICENSE + http://www.gnu.org/licenses/lgpl.html + + + + + top.ysoft + ysoft-common + + + diff --git a/wms-module-system/pom.xml b/wms-module-system/pom.xml new file mode 100644 index 0000000..6062a0a --- /dev/null +++ b/wms-module-system/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + top.ysoft + ysoft-admin + ${revision} + + + ysoft-module-system + 系统管理模块(存放系统管理相关业务功能,例如:部门管理、角色管理、用户管理等) + + + + + top.ysoft + ysoft-common + + + \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/AbstractLoginHandler.java b/wms-module-system/src/main/java/top/wms/admin/auth/AbstractLoginHandler.java new file mode 100644 index 0000000..504df23 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/AbstractLoginHandler.java @@ -0,0 +1,112 @@ +package top.ysoft.admin.auth; + +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.bean.BeanUtil; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Component; +import top.ysoft.admin.auth.model.req.LoginReq; +import top.ysoft.admin.common.context.RoleContext; +import top.ysoft.admin.common.context.UserContext; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.common.context.UserExtraContext; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.model.entity.DeptDO; +import top.ysoft.admin.system.model.entity.UserDO; +import top.ysoft.admin.system.model.resp.ClientResp; +import top.ysoft.admin.system.service.DeptService; +import top.ysoft.admin.system.service.OptionService; +import top.ysoft.admin.system.service.RoleService; +import top.ysoft.admin.system.service.UserService; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.core.validation.Validator; +import top.continew.starter.web.util.SpringWebUtils; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import static top.ysoft.admin.system.enums.PasswordPolicyEnum.PASSWORD_EXPIRATION_DAYS; + +/** + * 登录处理器基类 + * + * @author KAI + * @author Charles7c + * @since 2024/12/22 14:52 + */ +@Component +public abstract class AbstractLoginHandler implements LoginHandler { + + @Resource + protected OptionService optionService; + @Resource + protected UserService userService; + @Resource + protected RoleService roleService; + @Resource + private DeptService deptService; + @Resource + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + protected static final String CAPTCHA_EXPIRED = "验证码已失效"; + protected static final String CAPTCHA_ERROR = "验证码错误"; + protected static final String CLIENT_ID = "clientId"; + + @Override + public void preLogin(T req, ClientResp client, HttpServletRequest request) { + // 参数校验 + Validator.validate(req); + } + + @Override + public void postLogin(T req, ClientResp client, HttpServletRequest request) { + } + + /** + * 认证 + * + * @param user 用户信息 + * @param client 终端信息 + * @return token 令牌信息 + */ + protected String authenticate(UserDO user, ClientResp client) { + // 获取权限、角色、密码过期天数 + Long userId = user.getId(); + CompletableFuture> permissionFuture = CompletableFuture.supplyAsync(() -> roleService + .listPermissionByUserId(userId), threadPoolTaskExecutor); + CompletableFuture> roleFuture = CompletableFuture.supplyAsync(() -> roleService + .listByUserId(userId), threadPoolTaskExecutor); + CompletableFuture passwordExpirationDaysFuture = CompletableFuture.supplyAsync(() -> optionService + .getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name())); + CompletableFuture.allOf(permissionFuture, roleFuture, passwordExpirationDaysFuture); + UserContext userContext = new UserContext(permissionFuture.join(), roleFuture + .join(), passwordExpirationDaysFuture.join()); + BeanUtil.copyProperties(user, userContext); + // 设置登录配置参数 + SaLoginModel model = new SaLoginModel(); + model.setActiveTimeout(client.getActiveTimeout()); + model.setTimeout(client.getTimeout()); + model.setDevice(client.getClientType()); + userContext.setClientType(client.getClientType()); + model.setExtra(CLIENT_ID, client.getClientId()); + userContext.setClientId(client.getClientId()); + // 登录并缓存用户信息 + StpUtil.login(userContext.getId(), model.setExtraData(BeanUtil.beanToMap(new UserExtraContext(SpringWebUtils + .getRequest())))); + UserContextHolder.setContext(userContext); + return StpUtil.getTokenValue(); + } + + /** + * 检查用户状态 + * + * @param user 用户信息 + */ + protected void checkUserStatus(UserDO user) { + CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, user.getStatus(), "此账号已被禁用,如有疑问,请联系管理员"); + DeptDO dept = deptService.getById(user.getDeptId()); + CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, dept.getStatus(), "此账号所属部门已被禁用,如有疑问,请联系管理员"); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/LoginHandler.java b/wms-module-system/src/main/java/top/wms/admin/auth/LoginHandler.java new file mode 100644 index 0000000..a841fc9 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/LoginHandler.java @@ -0,0 +1,52 @@ +package top.ysoft.admin.auth; + +import jakarta.servlet.http.HttpServletRequest; +import top.ysoft.admin.auth.enums.AuthTypeEnum; +import top.ysoft.admin.auth.model.req.LoginReq; +import top.ysoft.admin.auth.model.resp.LoginResp; +import top.ysoft.admin.system.model.resp.ClientResp; + +/** + * 登录处理器 + * + * @author KAI + * @author Charles7c + * @since 2024/12/22 14:52 + */ +public interface LoginHandler { + + /** + * 登录 + * + * @param req 登录请求参数 + * @param client 终端信息 + * @param request 请求对象 + * @return 登录响应参数 + */ + LoginResp login(T req, ClientResp client, HttpServletRequest request); + + /** + * 登录前置处理 + * + * @param req 登录请求参数 + * @param client 终端信息 + * @param request 请求对象 + */ + void preLogin(T req, ClientResp client, HttpServletRequest request); + + /** + * 登录后置处理 + * + * @param req 登录请求参数 + * @param client 终端信息 + * @param request 请求对象 + */ + void postLogin(T req, ClientResp client, HttpServletRequest request); + + /** + * 获取认证类型 + * + * @return 认证类型 + */ + AuthTypeEnum getAuthType(); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/LoginHandlerFactory.java b/wms-module-system/src/main/java/top/wms/admin/auth/LoginHandlerFactory.java new file mode 100644 index 0000000..b72fbe7 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/LoginHandlerFactory.java @@ -0,0 +1,40 @@ +package top.ysoft.admin.auth; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import top.ysoft.admin.auth.enums.AuthTypeEnum; +import top.ysoft.admin.auth.model.req.LoginReq; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +/** + * 登录处理器工厂 + * + * @author KAI + * @author Charles7c + * @since 2024/12/20 15:16 + */ +@Component +public class LoginHandlerFactory { + + private final Map> handlerMap = new EnumMap<>(AuthTypeEnum.class); + + @Autowired + public LoginHandlerFactory(List> handlers) { + for (LoginHandler handler : handlers) { + handlerMap.put(handler.getAuthType(), handler); + } + } + + /** + * 根据认证类型获取 + * + * @param authType 认证类型 + * @return 认证处理器 + */ + public LoginHandler getHandler(AuthTypeEnum authType) { + return (LoginHandler)handlerMap.get(authType); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/enums/AuthTypeEnum.java b/wms-module-system/src/main/java/top/wms/admin/auth/enums/AuthTypeEnum.java new file mode 100644 index 0000000..b5db9e2 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/enums/AuthTypeEnum.java @@ -0,0 +1,42 @@ +package top.ysoft.admin.auth.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.ysoft.admin.common.constant.UiConstants; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 认证类型枚举 + * + * @author KAI + * @author Charles7c + * @since 2024/12/22 14:52 + */ +@Getter +@RequiredArgsConstructor +public enum AuthTypeEnum implements BaseEnum { + + /** + * 账号 + */ + ACCOUNT("ACCOUNT", "账号", UiConstants.COLOR_SUCCESS), + + /** + * 邮箱 + */ + EMAIL("EMAIL", "邮箱", UiConstants.COLOR_PRIMARY), + + /** + * 手机号 + */ + PHONE("PHONE", "手机号", UiConstants.COLOR_PRIMARY), + + /** + * 第三方账号 + */ + SOCIAL("SOCIAL", "第三方账号", UiConstants.COLOR_ERROR); + + private final String value; + private final String description; + private final String color; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/handler/AccountLoginHandler.java b/wms-module-system/src/main/java/top/wms/admin/auth/handler/AccountLoginHandler.java new file mode 100644 index 0000000..3776bad --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/handler/AccountLoginHandler.java @@ -0,0 +1,110 @@ +package top.ysoft.admin.auth.handler; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.servlet.JakartaServletUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import top.ysoft.admin.auth.AbstractLoginHandler; +import top.ysoft.admin.auth.enums.AuthTypeEnum; +import top.ysoft.admin.auth.model.req.AccountLoginReq; +import top.ysoft.admin.auth.model.resp.LoginResp; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.common.util.SecureUtils; +import top.ysoft.admin.system.enums.PasswordPolicyEnum; +import top.ysoft.admin.system.model.entity.UserDO; +import top.ysoft.admin.system.model.resp.ClientResp; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.util.ExceptionUtils; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.core.validation.ValidationUtils; + +import java.time.Duration; + +/** + * 账号登录处理器 + * + * @author KAI + * @author Charles7c + * @since 2024/12/22 14:58 + */ +@Component +@RequiredArgsConstructor +public class AccountLoginHandler extends AbstractLoginHandler { + + private final PasswordEncoder passwordEncoder; + + @Override + public LoginResp login(AccountLoginReq req, ClientResp client, HttpServletRequest request) { + // 解密密码 + String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getPassword())); + ValidationUtils.throwIfBlank(rawPassword, "密码解密失败"); + // 验证用户名密码 + String username = req.getUsername(); + UserDO user = userService.getByUsername(username); + boolean isError = ObjectUtil.isNull(user) || !passwordEncoder.matches(rawPassword, user.getPassword()); + // 检查账号锁定状态 + this.checkUserLocked(req.getUsername(), request, isError); + ValidationUtils.throwIf(isError, "用户名或密码错误"); + // 检查用户状态 + super.checkUserStatus(user); + // 执行认证 + String token = this.authenticate(user, client); + return LoginResp.builder().token(token).build(); + } + + @Override + public void preLogin(AccountLoginReq req, ClientResp client, HttpServletRequest request) { + super.preLogin(req, client, request); + // 校验验证码 + int loginCaptchaEnabled = optionService.getValueByCode2Int("LOGIN_CAPTCHA_ENABLED"); + if (SysConstants.YES.equals(loginCaptchaEnabled)) { + ValidationUtils.throwIfBlank(req.getCaptcha(), "验证码不能为空"); + ValidationUtils.throwIfBlank(req.getUuid(), "验证码标识不能为空"); + String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + req.getUuid(); + String captcha = RedisUtils.get(captchaKey); + ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); + RedisUtils.delete(captchaKey); + ValidationUtils.throwIfNotEqualIgnoreCase(req.getCaptcha(), captcha, CAPTCHA_ERROR); + } + } + + @Override + public AuthTypeEnum getAuthType() { + return AuthTypeEnum.ACCOUNT; + } + + /** + * 检测用户是否已被锁定 + * + * @param username 用户名 + * @param request 请求对象 + * @param isError 是否登录错误 + */ + private void checkUserLocked(String username, HttpServletRequest request, boolean isError) { + // 不锁定 + int maxErrorCount = optionService.getValueByCode2Int(PasswordPolicyEnum.PASSWORD_ERROR_LOCK_COUNT.name()); + if (maxErrorCount <= SysConstants.NO) { + return; + } + // 检测是否已被锁定 + String key = CacheConstants.USER_PASSWORD_ERROR_KEY_PREFIX + RedisUtils.formatKey(username, JakartaServletUtil + .getClientIP(request)); + int lockMinutes = optionService.getValueByCode2Int(PasswordPolicyEnum.PASSWORD_ERROR_LOCK_MINUTES.name()); + Integer currentErrorCount = ObjectUtil.defaultIfNull(RedisUtils.get(key), 0); + CheckUtils.throwIf(currentErrorCount >= maxErrorCount, PasswordPolicyEnum.PASSWORD_ERROR_LOCK_MINUTES.getMsg() + .formatted(lockMinutes)); + // 登录成功清除计数 + if (!isError) { + RedisUtils.delete(key); + return; + } + // 登录失败递增计数 + currentErrorCount++; + RedisUtils.set(key, currentErrorCount, Duration.ofMinutes(lockMinutes)); + CheckUtils.throwIf(currentErrorCount >= maxErrorCount, PasswordPolicyEnum.PASSWORD_ERROR_LOCK_COUNT.getMsg() + .formatted(maxErrorCount, lockMinutes)); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/handler/EmailLoginHandler.java b/wms-module-system/src/main/java/top/wms/admin/auth/handler/EmailLoginHandler.java new file mode 100644 index 0000000..ac6c11a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/handler/EmailLoginHandler.java @@ -0,0 +1,51 @@ +package top.ysoft.admin.auth.handler; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import top.ysoft.admin.auth.AbstractLoginHandler; +import top.ysoft.admin.auth.enums.AuthTypeEnum; +import top.ysoft.admin.auth.model.req.EmailLoginReq; +import top.ysoft.admin.auth.model.resp.LoginResp; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.system.model.entity.UserDO; +import top.ysoft.admin.system.model.resp.ClientResp; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.validation.ValidationUtils; + +/** + * 邮箱登录处理器 + * + * @author KAI + * @author Charles7c + * @since 2024/12/22 14:58 + */ +@Component +public class EmailLoginHandler extends AbstractLoginHandler { + + @Override + public LoginResp login(EmailLoginReq req, ClientResp client, HttpServletRequest request) { + // 验证邮箱 + UserDO user = userService.getByEmail(req.getEmail()); + ValidationUtils.throwIfNull(user, "此邮箱未绑定本系统账号"); + // 检查用户状态 + super.checkUserStatus(user); + // 执行认证 + String token = super.authenticate(user, client); + return LoginResp.builder().token(token).build(); + } + + @Override + public void preLogin(EmailLoginReq req, ClientResp client, HttpServletRequest request) { + String email = req.getEmail(); + String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + email; + String captcha = RedisUtils.get(captchaKey); + ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); + ValidationUtils.throwIfNotEqualIgnoreCase(req.getCaptcha(), captcha, CAPTCHA_ERROR); + RedisUtils.delete(captchaKey); + } + + @Override + public AuthTypeEnum getAuthType() { + return AuthTypeEnum.EMAIL; + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/handler/PhoneLoginHandler.java b/wms-module-system/src/main/java/top/wms/admin/auth/handler/PhoneLoginHandler.java new file mode 100644 index 0000000..542170a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/handler/PhoneLoginHandler.java @@ -0,0 +1,51 @@ +package top.ysoft.admin.auth.handler; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import top.ysoft.admin.auth.AbstractLoginHandler; +import top.ysoft.admin.auth.enums.AuthTypeEnum; +import top.ysoft.admin.auth.model.req.PhoneLoginReq; +import top.ysoft.admin.auth.model.resp.LoginResp; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.system.model.entity.UserDO; +import top.ysoft.admin.system.model.resp.ClientResp; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.validation.ValidationUtils; + +/** + * 手机号登录处理器 + * + * @author KAI + * @author Charles7c + * @since 2024/12/22 14:59 + */ +@Component +public class PhoneLoginHandler extends AbstractLoginHandler { + + @Override + public LoginResp login(PhoneLoginReq req, ClientResp client, HttpServletRequest request) { + // 验证手机号 + UserDO user = userService.getByPhone(req.getPhone()); + ValidationUtils.throwIfNull(user, "此手机号未绑定本系统账号"); + // 检查用户状态 + super.checkUserStatus(user); + // 执行认证 + String token = super.authenticate(user, client); + return LoginResp.builder().token(token).build(); + } + + @Override + public void preLogin(PhoneLoginReq req, ClientResp client, HttpServletRequest request) { + String phone = req.getPhone(); + String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + phone; + String captcha = RedisUtils.get(captchaKey); + ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); + ValidationUtils.throwIfNotEqualIgnoreCase(req.getCaptcha(), captcha, CAPTCHA_ERROR); + RedisUtils.delete(captchaKey); + } + + @Override + public AuthTypeEnum getAuthType() { + return AuthTypeEnum.PHONE; + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/handler/SocialLoginHandler.java b/wms-module-system/src/main/java/top/wms/admin/auth/handler/SocialLoginHandler.java new file mode 100644 index 0000000..c829d6f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/handler/SocialLoginHandler.java @@ -0,0 +1,161 @@ +package top.ysoft.admin.auth.handler; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.ReUtil; +import cn.hutool.json.JSONUtil; +import com.xkcoding.justauth.AuthRequestFactory; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthRequest; +import org.springframework.stereotype.Component; +import top.ysoft.admin.auth.AbstractLoginHandler; +import top.ysoft.admin.auth.enums.AuthTypeEnum; +import top.ysoft.admin.auth.model.req.SocialLoginReq; +import top.ysoft.admin.auth.model.resp.LoginResp; +import top.ysoft.admin.common.constant.RegexConstants; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.enums.GenderEnum; +import top.ysoft.admin.system.enums.MessageTemplateEnum; +import top.ysoft.admin.system.enums.MessageTypeEnum; +import top.ysoft.admin.system.model.entity.RoleDO; +import top.ysoft.admin.system.model.entity.UserDO; +import top.ysoft.admin.system.model.entity.UserSocialDO; +import top.ysoft.admin.system.model.req.MessageReq; +import top.ysoft.admin.system.model.resp.ClientResp; +import top.ysoft.admin.system.service.MessageService; +import top.ysoft.admin.system.service.UserRoleService; +import top.ysoft.admin.system.service.UserSocialService; +import top.continew.starter.core.autoconfigure.project.ProjectProperties; +import top.continew.starter.core.exception.BadRequestException; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.messaging.websocket.util.WebSocketUtils; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +/** + * 第三方账号登录处理器 + * + * @author KAI + * @author Charles7c + * @since 2024/12/25 14:21 + */ +@Component +@RequiredArgsConstructor +public class SocialLoginHandler extends AbstractLoginHandler { + + private final AuthRequestFactory authRequestFactory; + private final UserSocialService userSocialService; + private final UserRoleService userRoleService; + private final MessageService messageService; + private final ProjectProperties projectProperties; + + @Override + public LoginResp login(SocialLoginReq req, ClientResp client, HttpServletRequest request) { + // 获取第三方登录信息 + AuthRequest authRequest = this.getAuthRequest(req.getSource()); + AuthCallback callback = new AuthCallback(); + callback.setCode(req.getCode()); + callback.setState(req.getState()); + AuthResponse response = authRequest.login(callback); + ValidationUtils.throwIf(!response.ok(), response.getMsg()); + AuthUser authUser = response.getData(); + // 如未绑定则自动注册新用户,保存或更新关联信息 + String source = authUser.getSource(); + String openId = authUser.getUuid(); + UserSocialDO userSocial = userSocialService.getBySourceAndOpenId(source, openId); + UserDO user; + if (null == userSocial) { + String username = authUser.getUsername(); + String nickname = authUser.getNickname(); + UserDO existsUser = userService.getByUsername(username); + String randomStr = RandomUtil.randomString(RandomUtil.BASE_CHAR, 5); + if (null != existsUser || !ReUtil.isMatch(RegexConstants.USERNAME, username)) { + username = randomStr + IdUtil.fastSimpleUUID(); + } + if (!ReUtil.isMatch(RegexConstants.GENERAL_NAME, nickname)) { + nickname = source.toLowerCase() + randomStr; + } + user = new UserDO(); + user.setUsername(username); + user.setNickname(nickname); + user.setGender(GenderEnum.valueOf(authUser.getGender().name())); + user.setAvatar(authUser.getAvatar()); + user.setDeptId(SysConstants.SUPER_DEPT_ID); + user.setStatus(DisEnableStatusEnum.ENABLE); + userService.save(user); + Long userId = user.getId(); + RoleDO role = roleService.getByCode(SysConstants.SUPER_ROLE_CODE); + userRoleService.assignRolesToUser(Collections.singletonList(role.getId()), userId); + userSocial = new UserSocialDO(); + userSocial.setUserId(userId); + userSocial.setSource(source); + userSocial.setOpenId(openId); + this.sendSecurityMsg(user); + } else { + user = BeanUtil.copyProperties(userService.getById(userSocial.getUserId()), UserDO.class); + } + // 检查用户状态 + super.checkUserStatus(user); + userSocial.setMetaJson(JSONUtil.toJsonStr(authUser)); + userSocial.setLastLoginTime(LocalDateTime.now()); + userSocialService.saveOrUpdate(userSocial); + // 执行认证 + String token = super.authenticate(user, client); + return LoginResp.builder().token(token).build(); + } + + @Override + public void preLogin(SocialLoginReq req, ClientResp client, HttpServletRequest request) { + super.preLogin(req, client, request); + if (StpUtil.isLogin()) { + StpUtil.logout(); + } + } + + @Override + public AuthTypeEnum getAuthType() { + return AuthTypeEnum.SOCIAL; + } + + /** + * 获取 AuthRequest + * + * @param source 平台名称 + * @return AuthRequest + */ + private AuthRequest getAuthRequest(String source) { + try { + return authRequestFactory.get(source); + } catch (Exception e) { + throw new BadRequestException("暂不支持 [%s] 平台账号登录".formatted(source)); + } + } + + /** + * 发送安全消息 + * + * @param user 用户信息 + */ + private void sendSecurityMsg(UserDO user) { + MessageReq req = new MessageReq(); + MessageTemplateEnum socialRegister = MessageTemplateEnum.SOCIAL_REGISTER; + req.setTitle(socialRegister.getTitle().formatted(projectProperties.getName())); + req.setContent(socialRegister.getContent().formatted(user.getNickname())); + req.setType(MessageTypeEnum.SECURITY); + messageService.add(req, CollUtil.toList(user.getId())); + List tokenList = StpUtil.getTokenValueListByLoginId(user.getId()); + for (String token : tokenList) { + WebSocketUtils.sendMessage(token, "1"); + } + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/query/OnlineUserQuery.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/query/OnlineUserQuery.java new file mode 100644 index 0000000..ad8b555 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/query/OnlineUserQuery.java @@ -0,0 +1,56 @@ +package top.ysoft.admin.auth.model.query; + +import cn.hutool.core.date.DatePattern; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 在线用户查询条件 + * + * @author Charles7c + * @since 2023/1/20 23:07 + */ +@Data +@Schema(description = "在线用户查询条件") +public class OnlineUserQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户昵称 + */ + @Schema(description = "用户昵称", example = "张三") + private String nickname; + + /** + * 终端 ID + */ + @Schema(description = "终端 ID", example = "ef51c9a3e9046c4f2ea45142c8a8344a") + private String clientId; + + /** + * 登录时间 + */ + @Schema(description = "登录时间", example = "2023-08-08 00:00:00,2023-08-08 23:59:59") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + private List loginTime; + + /** + * 用户 ID + */ + @Schema(hidden = true) + private Long userId; + + /** + * 角色 ID + */ + @Schema(hidden = true) + private Long roleId; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/req/AccountLoginReq.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/req/AccountLoginReq.java new file mode 100644 index 0000000..c93b1e4 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/req/AccountLoginReq.java @@ -0,0 +1,47 @@ +package top.ysoft.admin.auth.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.io.Serial; + +/** + * 账号登录参数 + * + * @author Charles7c + * @since 2022/12/21 20:43 + */ +@Data +@Schema(description = "账号登录参数") +public class AccountLoginReq extends LoginReq { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户名 + */ + @Schema(description = "用户名", example = "zhangsan") + @NotBlank(message = "用户名不能为空") + private String username; + + /** + * 密码(加密) + */ + @Schema(description = "密码(加密)", example = "HHwZoiBwCfh0xLdWOAd0bHOkEZlIMMOQKJyeFUw9T3ArrhL57od2i42s1o0sSXKkeHPJXvQsninhPFH2lArDDQ==") + @NotBlank(message = "密码不能为空") + private String password; + + /** + * 验证码 + */ + @Schema(description = "验证码", example = "ABCD") + private String captcha; + + /** + * 验证码标识 + */ + @Schema(description = "验证码标识", example = "090b9a2c-1691-4fca-99db-e4ed0cff362f") + private String uuid; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/req/EmailLoginReq.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/req/EmailLoginReq.java new file mode 100644 index 0000000..83e25dc --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/req/EmailLoginReq.java @@ -0,0 +1,40 @@ +package top.ysoft.admin.auth.model.req; + +import cn.hutool.core.lang.RegexPool; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; + +/** + * 邮箱登录参数 + * + * @author Charles7c + * @since 2023/10/23 20:15 + */ +@Data +@Schema(description = "邮箱登录参数") +public class EmailLoginReq extends LoginReq { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 邮箱 + */ + @Schema(description = "邮箱", example = "123456789@qq.com") + @NotBlank(message = "邮箱不能为空") + @Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误") + private String email; + + /** + * 验证码 + */ + @Schema(description = "验证码", example = "888888") + @NotBlank(message = "验证码不能为空") + @Length(max = 6, message = "验证码非法") + private String captcha; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/req/LoginReq.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/req/LoginReq.java new file mode 100644 index 0000000..81fe20a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/req/LoginReq.java @@ -0,0 +1,46 @@ +package top.ysoft.admin.auth.model.req; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import top.ysoft.admin.auth.enums.AuthTypeEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 登录参数基类 + * + * @author KAI + * @author Charles7c + * @since 2024/12/22 15:16 + */ +@Data +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "authType", visible = true) +@JsonSubTypes({@JsonSubTypes.Type(value = AccountLoginReq.class, name = "ACCOUNT"), + @JsonSubTypes.Type(value = EmailLoginReq.class, name = "EMAIL"), + @JsonSubTypes.Type(value = PhoneLoginReq.class, name = "PHONE"), + @JsonSubTypes.Type(value = SocialLoginReq.class, name = "SOCIAL")}) +@Schema(description = "基础登录参数") +public class LoginReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 终端 ID + */ + @Schema(description = "终端 ID", example = "ef51c9a3e9046c4f2ea45142c8a8344a") + @NotBlank(message = "终端ID不能为空") + private String clientId; + + /** + * 认证类型 + */ + @Schema(description = "认证类型", example = "ACCOUNT") + @NotNull(message = "认证类型非法") + private AuthTypeEnum authType; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/req/PhoneLoginReq.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/req/PhoneLoginReq.java new file mode 100644 index 0000000..51f8bb8 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/req/PhoneLoginReq.java @@ -0,0 +1,40 @@ +package top.ysoft.admin.auth.model.req; + +import cn.hutool.core.lang.RegexPool; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; + +/** + * 手机号登录参数 + * + * @author Charles7c + * @since 2023/10/26 22:37 + */ +@Data +@Schema(description = "手机号登录参数") +public class PhoneLoginReq extends LoginReq { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 手机号 + */ + @Schema(description = "手机号", example = "13811111111") + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误") + private String phone; + + /** + * 验证码 + */ + @Schema(description = "验证码", example = "8888") + @NotBlank(message = "验证码不能为空") + @Length(max = 4, message = "验证码非法") + private String captcha; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/req/SocialLoginReq.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/req/SocialLoginReq.java new file mode 100644 index 0000000..fbddaa1 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/req/SocialLoginReq.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.auth.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.io.Serial; + +/** + * 第三方账号登录参数 + * + * @author KAI + * @author Charles7c + * @since 2024/12/25 15:43 + */ +@Data +@Schema(description = "第三方账号登录参数") +public class SocialLoginReq extends LoginReq { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 第三方登录平台 + */ + @Schema(description = "第三方登录平台", example = "gitee") + @NotBlank(message = "第三方登录平台不能为空") + private String source; + + /** + * 授权码 + */ + @Schema(description = "授权码", example = "a08d33e9e577fb339de027499784ed4e871d6f62ae65b459153e906ab546bd56") + @NotBlank(message = "授权码不能为空") + private String code; + + /** + * 状态码 + */ + @Schema(description = "状态码", example = "2ca8d8baf437eb374efaa1191a3d") + @NotBlank(message = "状态码不能为空") + private String state; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/CaptchaResp.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/CaptchaResp.java new file mode 100644 index 0000000..6ccb149 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/CaptchaResp.java @@ -0,0 +1,59 @@ +package top.ysoft.admin.auth.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 验证码信息 + * + * @author Charles7c + * @since 2022/12/11 13:55 + */ +@Data +@Builder +@Schema(description = "验证码信息") +public class CaptchaResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 验证码标识 + */ + @Schema(description = "验证码标识", example = "090b9a2c-1691-4fca-99db-e4ed0cff362f") + private String uuid; + + /** + * 验证码图片(Base64编码,带图片格式:data:image/gif;base64) + */ + @Schema(description = "验证码图片(Base64编码,带图片格式:data:image/gif;base64)", example = "data:image/png;base64,iVBORw0KGgoAAAAN...") + private String img; + + /** + * 过期时间戳 + */ + @Schema(description = "过期时间戳", example = "1714376969409") + private Long expireTime; + + /** + * 是否启用 + */ + @Schema(description = "是否启用", example = "true") + private Boolean isEnabled; + + /** + * 构建验证码信息 + * + * @param uuid 验证码标识 + * @param img 验证码图片(Base64编码,带图片格式:data:image/gif;base64) + * @param expireTime 过期时间戳 + * @return 验证码信息 + */ + public static CaptchaResp of(String uuid, String img, Long expireTime) { + return CaptchaResp.builder().uuid(uuid).img(img).expireTime(expireTime).isEnabled(true).build(); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/LoginResp.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/LoginResp.java new file mode 100644 index 0000000..fbed5f2 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/LoginResp.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.auth.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 登录响应参数 + * + * @author Charles7c + * @since 2022/12/21 20:42 + */ +@Data +@Builder +@Schema(description = "登录响应参数") +public class LoginResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 令牌 + */ + @Schema(description = "令牌", example = "eyJ0eXAiOiJlV1QiLCJhbGciqiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb29pbiIsImxvZ2luSWQiOjEsInJuU3RyIjoiSjd4SUljYnU5cmNwU09vQ3Uyc1ND1BYYTYycFRjcjAifQ.KUPOYm-2wfuLUSfEEAbpGE527fzmkAJG7sMNcQ0pUZ8") + private String token; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/OnlineUserResp.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/OnlineUserResp.java new file mode 100644 index 0000000..b605115 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/OnlineUserResp.java @@ -0,0 +1,102 @@ +package top.ysoft.admin.auth.model.resp; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.annotation.AssembleMethod; +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.auth.service.OnlineUserService; +import top.ysoft.admin.common.constant.ContainerConstants; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 在线用户信息 + * + * @author Charles7c + * @since 2023/1/20 21:54 + */ +@Data +@Schema(description = "在线用户信息") +public class OnlineUserResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + @Assemble(prop = ":nickname", container = ContainerConstants.USER_NICKNAME) + private Long id; + + /** + * 令牌 + */ + @Schema(description = "令牌", example = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjEsInJuU3RyIjoiTUd6djdyOVFoeHEwdVFqdFAzV3M5YjVJRzh4YjZPSEUifQ.7q7U3ouoN7WPhH2kUEM7vPe5KF3G_qavSG-vRgIxKvE") + @AssembleMethod(prop = ":lastActiveTime", targetType = OnlineUserService.class, method = @ContainerMethod(bindMethod = "getLastActiveTime", type = MappingType.ORDER_OF_KEYS)) + private String token; + + /** + * 用户名 + */ + @Schema(description = "用户名", example = "zhangsan") + private String username; + + /** + * 昵称 + */ + @Schema(description = "昵称", example = "张三") + private String nickname; + + /** + * 终端类型 + */ + @Schema(description = "终端类型", example = "PC") + private String clientType; + + /** + * 终端 ID + */ + @Schema(description = "终端 ID", example = "ef51c9a3e9046c4f2ea45142c8a8344a") + private String clientId; + + /** + * 登录 IP + */ + @Schema(description = "登录 IP", example = "") + private String ip; + + /** + * 登录地点 + */ + @Schema(description = "登录地点", example = "中国北京北京市") + private String address; + + /** + * 浏览器 + */ + @Schema(description = "浏览器", example = "Chrome 115.0.0.0") + private String browser; + + /** + * 操作系统 + */ + @Schema(description = "操作系统", example = "Windows 10") + private String os; + + /** + * 登录时间 + */ + @Schema(description = "登录时间", example = "2023-08-08 08:08:08", type = "string") + private LocalDateTime loginTime; + + /** + * 最后活跃时间 + */ + @Schema(description = "最后活跃时间", example = "2023-08-08 08:08:08", type = "string") + private LocalDateTime lastActiveTime; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/RouteResp.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/RouteResp.java new file mode 100644 index 0000000..84ddeba --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/RouteResp.java @@ -0,0 +1,114 @@ +package top.ysoft.admin.auth.model.resp; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 路由信息 + * + * @author Charles7c + * @since 2023/2/26 22:51 + */ +@Data +@Schema(description = "路由信息") +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class RouteResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1010") + private Long id; + + /** + * 上级菜单 ID + */ + @Schema(description = "上级菜单ID", example = "1000") + private Long parentId; + + /** + * 标题 + */ + @Schema(description = "标题", example = "用户管理") + private String title; + + /** + * 类型 + */ + @Schema(description = "类型", example = "2") + private Integer type; + + /** + * 路由地址 + */ + @Schema(description = "路由地址", example = "/system/user") + private String path; + + /** + * 组件名称 + */ + @Schema(description = "组件名称", example = "User") + private String name; + + /** + * 组件路径 + */ + @Schema(description = "组件路径", example = "/system/user/index") + private String component; + + /** + * 重定向地址 + */ + @Schema(description = "重定向地址") + private String redirect; + + /** + * 图标 + */ + @Schema(description = "图标", example = "user") + private String icon; + + /** + * 是否外链 + */ + @Schema(description = "是否外链", example = "false") + private Boolean isExternal; + + /** + * 是否缓存 + */ + @Schema(description = "是否缓存", example = "false") + private Boolean isCache; + + /** + * 是否隐藏 + */ + @Schema(description = "是否隐藏", example = "false") + private Boolean isHidden; + + /** + * 权限标识 + */ + @Schema(description = "权限标识", example = "system:user:list") + private String permission; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + private Integer sort; + + /** + * 子路由列表 + */ + @Schema(description = "子路由列表") + private List children; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/SocialAuthAuthorizeResp.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/SocialAuthAuthorizeResp.java new file mode 100644 index 0000000..d3aa18f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/SocialAuthAuthorizeResp.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.auth.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 三方账号授权认证响应信息 + * + * @author Charles7c + * @since 2024/3/6 22:26 + */ +@Data +@Builder +@Schema(description = "三方账号授权认证响应信息") +public class SocialAuthAuthorizeResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 授权 URL + */ + @Schema(description = "授权 URL", example = "https://gitee.com/oauth/authorize?response_type=code&client_id=5d271b7f638941812aaf8bfc2e2f08f06d6235ef934e0e39537e2364eb8452c4&redirect_uri=http://localhost:6609/social/callback?source=gitee&state=d4ea7129e2531050210e9c918cc007d7&scope=user_info") + private String authorizeUrl; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/UserInfoResp.java b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/UserInfoResp.java new file mode 100644 index 0000000..aa2fdd5 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/model/resp/UserInfoResp.java @@ -0,0 +1,130 @@ +package top.ysoft.admin.auth.model.resp; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.enums.GenderEnum; +import top.continew.starter.security.mask.annotation.JsonMask; +import top.continew.starter.security.mask.enums.MaskType; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Set; + +/** + * 用户信息 + * + * @author Charles7c + * @since 2022/12/29 20:15 + */ +@Data +@Schema(description = "用户信息") +public class UserInfoResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 用户名 + */ + @Schema(description = "用户名", example = "zhangsan") + private String username; + + /** + * 昵称 + */ + @Schema(description = "昵称", example = "张三") + private String nickname; + + /** + * 性别 + */ + @Schema(description = "性别", example = "1") + private GenderEnum gender; + + /** + * 邮箱 + */ + @Schema(description = "邮箱", example = "c*******@126.com") + @JsonMask(MaskType.EMAIL) + private String email; + + /** + * 手机号码 + */ + @Schema(description = "手机号码", example = "188****8888") + @JsonMask(MaskType.MOBILE_PHONE) + private String phone; + + /** + * 头像地址 + */ + @Schema(description = "头像地址", example = "https://himg.bdimg.com/sys/portrait/item/public.1.81ac9a9e.rf1ix17UfughLQjNo7XQ_w.jpg") + private String avatar; + + /** + * 描述 + */ + @Schema(description = "描述", example = "张三描述信息") + private String description; + + /** + * 最后一次修改密码时间 + */ + @Schema(description = "最后一次修改密码时间", example = "2023-08-08 08:08:08", type = "string") + private LocalDateTime pwdResetTime; + + /** + * 密码是否已过期 + */ + @Schema(description = "密码是否已过期", example = "true") + private Boolean pwdExpired; + + /** + * 创建时间 + */ + @JsonIgnore + private LocalDateTime createTime; + + /** + * 注册日期 + */ + @Schema(description = "注册日期", example = "2023-08-08") + private LocalDate registrationDate; + + /** + * 部门 ID + */ + @Schema(description = "部门 ID", example = "1") + private Long deptId; + + /** + * 所属部门 + */ + @Schema(description = "所属部门", example = "测试部") + private String deptName; + + /** + * 权限码集合 + */ + @Schema(description = "权限码集合", example = "[\"system:user:list\",\"system:user:add\"]") + private Set permissions; + + /** + * 角色编码集合 + */ + @Schema(description = "角色编码集合", example = "[\"test\"]") + private Set roles; + + public LocalDate getRegistrationDate() { + return createTime.toLocalDate(); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/service/AuthService.java b/wms-module-system/src/main/java/top/wms/admin/auth/service/AuthService.java new file mode 100644 index 0000000..a4bdc84 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/service/AuthService.java @@ -0,0 +1,34 @@ +package top.ysoft.admin.auth.service; + +import jakarta.servlet.http.HttpServletRequest; +import top.ysoft.admin.auth.model.req.LoginReq; +import top.ysoft.admin.auth.model.resp.LoginResp; +import top.ysoft.admin.auth.model.resp.RouteResp; + +import java.util.List; + +/** + * 认证业务接口 + * + * @author Charles7c + * @since 2022/12/21 21:48 + */ +public interface AuthService { + + /** + * 登录 + * + * @param req 登录请求参数 + * @param request 请求对象 + * @return 登录响应参数 + */ + LoginResp login(LoginReq req, HttpServletRequest request); + + /** + * 构建路由树 + * + * @param userId 用户 ID + * @return 路由树 + */ + List buildRouteTree(Long userId); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/service/OnlineUserService.java b/wms-module-system/src/main/java/top/wms/admin/auth/service/OnlineUserService.java new file mode 100644 index 0000000..b7c2e1f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/service/OnlineUserService.java @@ -0,0 +1,50 @@ +package top.ysoft.admin.auth.service; + +import top.ysoft.admin.auth.model.query.OnlineUserQuery; +import top.ysoft.admin.auth.model.resp.OnlineUserResp; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 在线用户业务接口 + * + * @author Charles7c + * @since 2023/3/25 22:48 + */ +public interface OnlineUserService { + + /** + * 分页查询列表 + * + * @param query 查询条件 + * @param pageQuery 分页查询条件 + * @return 分页列表信息 + */ + PageResp page(OnlineUserQuery query, PageQuery pageQuery); + + /** + * 查询列表 + * + * @param query 查询条件 + * @return 列表信息 + */ + List list(OnlineUserQuery query); + + /** + * 查询 Token 最后活跃时间 + * + * @param token Token + * @return 最后活跃时间 + */ + LocalDateTime getLastActiveTime(String token); + + /** + * 踢出用户 + * + * @param userId 用户 ID + */ + void kickOut(Long userId); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/service/impl/AuthServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/auth/service/impl/AuthServiceImpl.java new file mode 100644 index 0000000..d2c80d9 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/service/impl/AuthServiceImpl.java @@ -0,0 +1,110 @@ +package top.ysoft.admin.auth.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNodeConfig; +import cn.hutool.core.lang.tree.TreeUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.auth.LoginHandler; +import top.ysoft.admin.auth.LoginHandlerFactory; +import top.ysoft.admin.auth.enums.AuthTypeEnum; +import top.ysoft.admin.auth.model.req.LoginReq; +import top.ysoft.admin.auth.model.resp.LoginResp; +import top.ysoft.admin.auth.model.resp.RouteResp; +import top.ysoft.admin.auth.service.AuthService; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.common.context.RoleContext; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.MenuTypeEnum; +import top.ysoft.admin.system.model.resp.ClientResp; +import top.ysoft.admin.system.model.resp.MenuResp; +import top.ysoft.admin.system.service.ClientService; +import top.ysoft.admin.system.service.MenuService; +import top.ysoft.admin.system.service.RoleService; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.extension.crud.annotation.TreeField; +import top.continew.starter.extension.crud.autoconfigure.CrudProperties; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * 认证业务实现 + * + * @author Charles7c + * @since 2022/12/21 21:49 + */ +@Service +@RequiredArgsConstructor +public class AuthServiceImpl implements AuthService { + + private final LoginHandlerFactory loginHandlerFactory; + private final ClientService clientService; + private final RoleService roleService; + private final MenuService menuService; + private final CrudProperties crudProperties; + + @Override + public LoginResp login(LoginReq req, HttpServletRequest request) { + AuthTypeEnum authType = req.getAuthType(); + // 校验终端 + ClientResp client = clientService.getByClientId(req.getClientId()); + ValidationUtils.throwIfNull(client, "终端不存在"); + ValidationUtils.throwIf(DisEnableStatusEnum.DISABLE.equals(client.getStatus()), "终端已禁用"); + ValidationUtils.throwIf(!client.getAuthType().contains(authType.getValue()), "该终端暂未授权 [{}] 认证", authType + .getDescription()); + // 获取处理器 + LoginHandler loginHandler = loginHandlerFactory.getHandler(authType); + // 登录前置处理 + loginHandler.preLogin(req, client, request); + // 登录 + LoginResp loginResp = loginHandler.login(req, client, request); + // 登录后置处理 + loginHandler.postLogin(req, client, request); + return loginResp; + } + + @Override + public List buildRouteTree(Long userId) { + Set roleSet = roleService.listByUserId(userId); + if (CollUtil.isEmpty(roleSet)) { + return new ArrayList<>(0); + } + // 查询菜单列表 + Set menuSet = new LinkedHashSet<>(); + if (roleSet.stream().anyMatch(r -> SysConstants.SUPER_ROLE_ID.equals(r.getId()))) { + menuSet.addAll(menuService.listByRoleId(SysConstants.SUPER_ROLE_ID)); + } else { + roleSet.forEach(r -> menuSet.addAll(menuService.listByRoleId(r.getId()))); + } + List menuList = menuSet.stream().filter(m -> !MenuTypeEnum.BUTTON.equals(m.getType())).toList(); + if (CollUtil.isEmpty(menuList)) { + return new ArrayList<>(0); + } + // 构建路由树 + TreeField treeField = MenuResp.class.getDeclaredAnnotation(TreeField.class); + TreeNodeConfig treeNodeConfig = crudProperties.getTree().genTreeNodeConfig(treeField); + List> treeList = TreeUtil.build(menuList, treeField.rootId(), treeNodeConfig, (m, tree) -> { + tree.setId(m.getId()); + tree.setParentId(m.getParentId()); + tree.setName(m.getTitle()); + tree.setWeight(m.getSort()); + tree.putExtra("type", m.getType().getValue()); + tree.putExtra("path", m.getPath()); + tree.putExtra("name", m.getName()); + tree.putExtra("component", m.getComponent()); + tree.putExtra("redirect", m.getRedirect()); + tree.putExtra("icon", m.getIcon()); + tree.putExtra("isExternal", m.getIsExternal()); + tree.putExtra("isCache", m.getIsCache()); + tree.putExtra("isHidden", m.getIsHidden()); + tree.putExtra("permission", m.getPermission()); + }); + return BeanUtil.copyToList(treeList, RouteResp.class); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/auth/service/impl/OnlineUserServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/auth/service/impl/OnlineUserServiceImpl.java new file mode 100644 index 0000000..2d70ecc --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/auth/service/impl/OnlineUserServiceImpl.java @@ -0,0 +1,137 @@ +package top.ysoft.admin.auth.service.impl; + +import cn.crane4j.annotation.AutoOperate; +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.auth.model.query.OnlineUserQuery; +import top.ysoft.admin.auth.model.resp.OnlineUserResp; +import top.ysoft.admin.auth.service.OnlineUserService; +import top.ysoft.admin.common.context.UserContext; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.common.context.UserExtraContext; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 在线用户业务实现 + * + * @author Charles7c + * @since 2023/3/25 22:49 + */ +@Service +@RequiredArgsConstructor +public class OnlineUserServiceImpl implements OnlineUserService { + + @Override + @AutoOperate(type = OnlineUserResp.class, on = "list") + public PageResp page(OnlineUserQuery query, PageQuery pageQuery) { + List list = this.list(query); + return PageResp.build(pageQuery.getPage(), pageQuery.getSize(), list); + } + + @Override + public List list(OnlineUserQuery query) { + List list = new ArrayList<>(); + // 查询所有在线 Token + List tokenKeyList = StpUtil.searchTokenValue(StringConstants.EMPTY, 0, -1, false); + Map> tokenMap = tokenKeyList.stream().filter(tokenKey -> { + String token = StrUtil.subAfter(tokenKey, StringConstants.COLON, true); + // 忽略已过期或失效 Token + return StpUtil.getStpLogic().getTokenActiveTimeoutByToken(token) >= SaTokenDao.NEVER_EXPIRE; + }) + .map(tokenKey -> StrUtil.subAfter(tokenKey, StringConstants.COLON, true)) + .collect(Collectors.groupingBy(token -> Convert.toLong(StpUtil.getLoginIdByToken(token)))); + // 筛选数据 + for (Map.Entry> entry : tokenMap.entrySet()) { + Long userId = entry.getKey(); + UserContext userContext = UserContextHolder.getContext(userId); + if (null == userContext || !this.isMatchNickname(query.getNickname(), userContext) || !this + .isMatchClientId(query.getClientId(), userContext)) { + continue; + } + List loginTimeList = query.getLoginTime(); + entry.getValue().parallelStream().forEach(token -> { + UserExtraContext extraContext = UserContextHolder.getExtraContext(token); + if (!this.isMatchLoginTime(loginTimeList, extraContext.getLoginTime())) { + return; + } + OnlineUserResp resp = BeanUtil.copyProperties(userContext, OnlineUserResp.class); + BeanUtil.copyProperties(extraContext, resp); + resp.setToken(token); + list.add(resp); + }); + } + // 设置排序 + CollUtil.sort(list, Comparator.comparing(OnlineUserResp::getLoginTime).reversed()); + return list; + } + + @Override + public LocalDateTime getLastActiveTime(String token) { + long lastActiveTime = StpUtil.getStpLogic().getTokenLastActiveTime(token); + return lastActiveTime == SaTokenDao.NOT_VALUE_EXPIRE ? null : DateUtil.date(lastActiveTime).toLocalDateTime(); + } + + @Override + public void kickOut(Long userId) { + if (!StpUtil.isLogin(userId)) { + return; + } + StpUtil.logout(userId); + } + + /** + * 是否匹配昵称 + * + * @param nickname 昵称 + * @param userContext 用户上下文信息 + * @return 是否匹配昵称 + */ + private boolean isMatchNickname(String nickname, UserContext userContext) { + if (StrUtil.isBlank(nickname)) { + return true; + } + return StrUtil.contains(userContext.getUsername(), nickname) || StrUtil.contains(UserContextHolder + .getNickname(userContext.getId()), nickname); + } + + /** + * 是否匹配终端 ID + * + * @param clientId 终端 ID + * @param userContext 用户上下文信息 + * @return 是否匹配终端 ID + */ + private boolean isMatchClientId(String clientId, UserContext userContext) { + if (StrUtil.isBlank(clientId)) { + return true; + } + return Objects.equals(userContext.getClientId(), clientId); + } + + /** + * 是否匹配登录时间 + * + * @param loginTimeList 登录时间列表 + * @param loginTime 登录时间 + * @return 是否匹配登录时间 + */ + private boolean isMatchLoginTime(List loginTimeList, LocalDateTime loginTime) { + if (CollUtil.isEmpty(loginTimeList)) { + return true; + } + return DateUtil.isIn(DateUtil.date(loginTime).toJdkDate(), loginTimeList.get(0), loginTimeList.get(1)); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/mapper/DownRecordMapper.java b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/DownRecordMapper.java new file mode 100644 index 0000000..934b018 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/DownRecordMapper.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.consume.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.consume.model.entity.DownRecordDO; +import top.ysoft.admin.consume.model.resp.DownRecordResp; + +/** +* 消费下发记录 Mapper +* +* @author zc +* @since 2026/02/25 11:01 +*/ +@Repository +public interface DownRecordMapper extends BaseMapper { + + IPage selectRecordsPage(@Param("page") IPage page, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/mapper/GroupMapper.java b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/GroupMapper.java new file mode 100644 index 0000000..7f93f6f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/GroupMapper.java @@ -0,0 +1,14 @@ +package top.ysoft.admin.consume.mapper; + +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.consume.model.entity.GroupDO; +import org.springframework.stereotype.Repository; + +/** + * 消费组 Mapper + * + * @author zc + * @since 2026/01/27 13:48 + */ +@Repository +public interface GroupMapper extends BaseMapper {} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/mapper/GroupUserMapper.java b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/GroupUserMapper.java new file mode 100644 index 0000000..a48e88d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/GroupUserMapper.java @@ -0,0 +1,16 @@ +package top.ysoft.admin.consume.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springframework.stereotype.Repository; +import top.ysoft.admin.consume.model.entity.GroupUserDO; + +/** + * 消费组用户映射器 + * + * @author zc + * @since 2026/01/27 13:48 + */ +@Repository +public interface GroupUserMapper extends BaseMapper { + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/mapper/RechargeRecordMapper.java b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/RechargeRecordMapper.java new file mode 100644 index 0000000..a38a9e3 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/RechargeRecordMapper.java @@ -0,0 +1,40 @@ +package top.ysoft.admin.consume.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.consume.model.entity.RechargeRecordDO; +import org.springframework.stereotype.Repository; +import top.ysoft.admin.consume.model.resp.RechargeRecordResp; + +import java.util.List; + +/** + * 充值记录 Mapper + * + * @author zc + * @since 2026/01/29 16:11 + */ +@Repository +public interface RechargeRecordMapper extends BaseMapper { + + /** + * 查询充值记录分页 + * + * @param page 分页查询条件 + * @param queryWrapper 查询条件 + * @return 分页信息 + */ + IPage selectRechargePage(@Param("page") IPage page, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); + + /** + * 导出充值记录 + * + * @param queryWrapper 查询条件 + * @return 充值记录列表 + */ + List selectExport(@Param(Constants.WRAPPER) QueryWrapper queryWrapper); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/mapper/RecordMapper.java b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/RecordMapper.java new file mode 100644 index 0000000..6a82d98 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/RecordMapper.java @@ -0,0 +1,40 @@ +package top.ysoft.admin.consume.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.consume.model.entity.RecordDO; +import top.ysoft.admin.consume.model.resp.RecordResp; + +import java.util.List; + +/** + * 消费记录 Mapper + * + * @author zc + * @since 2026/01/21 17:53 + */ +@Repository +public interface RecordMapper extends BaseMapper { + + /** + * 分页查询列表 + * + * @param page 分页查询条件 + * @param queryWrapper 查询条件 + * @return 分页信息 + */ + IPage selectRecordsPage(@Param("page") IPage page, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); + + /** + * 导出列表 + * + * @param queryWrapper 查询条件 + * @return 列表信息 + */ + List selectRecordsExport(@Param(Constants.WRAPPER) QueryWrapper queryWrapper); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/mapper/ReportMapper.java b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/ReportMapper.java new file mode 100644 index 0000000..59a663d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/ReportMapper.java @@ -0,0 +1,69 @@ +package top.ysoft.admin.consume.mapper; + +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; +import top.ysoft.admin.consume.model.query.ReportQuery; +import top.ysoft.admin.consume.model.resp.report.*; + +import java.util.List; + +@Repository +public interface ReportMapper { + /** + * 查询商户报表 + * + * @param query 查询参数 + * @return 商户报表列表 + */ + List queryMerchantReport(ReportQuery query); + + /** + * 查询报表总价 + * + * @param query 查询参数 + * @return 报表列表总价 + */ + BaseReport totalReport(ReportQuery query); + + /** + * 查询餐段报表 + * + * @param query 查询参数 + * @return 餐段报表列表 + */ + List queryMealReport(ReportQuery query); + + /** + * 查询店铺报表 + * + * @param query 查询参数 + * @return 分店报表列表 + */ + List queryBranchReport(ReportQuery query); + + /** + * 查询人员报表 + * + * @param query 查询参数 + * @return 人员报表列表 + */ + List queryPeopleReport(@Param("query") ReportQuery query, + @Param("size") Integer size, + @Param("offset") Integer offset); + + /** + * 查询人员报表总数 + * + * @param query 查询参数 + * @return 人员报表总数 + */ + long selectCount(ReportQuery query); + + /** + * 查询人员报表导出数据 + * + * @param query 查询参数 + * @return 人员报表导出数据列表 + */ + List queryPeopleExport(ReportQuery query); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/mapper/TenantMapper.java b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/TenantMapper.java new file mode 100644 index 0000000..423aae9 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/TenantMapper.java @@ -0,0 +1,16 @@ +package top.ysoft.admin.consume.mapper; + +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.consume.model.entity.TenantDO; +import org.springframework.stereotype.Repository; + +/** + * 商户信息 Mapper + * + * @author zc + * @since 2026/01/30 14:55 + */ +@Repository +public interface TenantMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/mapper/TimeIntervalMapper.java b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/TimeIntervalMapper.java new file mode 100644 index 0000000..568ef20 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/mapper/TimeIntervalMapper.java @@ -0,0 +1,16 @@ +package top.ysoft.admin.consume.mapper; + +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.consume.model.entity.TimeIntervalDO; +import org.springframework.stereotype.Repository; + +/** + * 消费时段 Mapper + * + * @author zc + * @since 2026/01/30 14:56 + */ +@Repository +public interface TimeIntervalMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/mapstruct/RechargeRecordConvert.java b/wms-module-system/src/main/java/top/wms/admin/consume/mapstruct/RechargeRecordConvert.java new file mode 100644 index 0000000..4441755 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/mapstruct/RechargeRecordConvert.java @@ -0,0 +1,58 @@ +package top.ysoft.admin.consume.mapstruct; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import top.ysoft.admin.common.enums.ConsumeRechargeModeEnum; +import top.ysoft.admin.common.enums.ConsumeRechargeTypeEnum; +import top.ysoft.admin.common.enums.ConsumeRechargeWayEnum; +import top.ysoft.admin.consume.model.entity.RechargeRecordDO; +import top.ysoft.admin.consume.model.resp.RechargeRecordResp; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface RechargeRecordConvert { + + @Mapping(target = "rechargeWay", expression = "java(convertRechargeWay(recordDO.getRechargeWay()))") + @Mapping(target = "rechargeMode", expression = "java(convertRechargeMode(recordDO.getRechargeMode()))") + @Mapping(target = "rechargeType", expression = "java(convertRechargeType(recordDO.getRechargeType()))") + RechargeRecordResp toRecordResp(RechargeRecordDO recordDO); + + List ListToRecordResp(List recordDOS); + + default ConsumeRechargeWayEnum convertRechargeWay(Integer rechargeWay) { + if (rechargeWay == null) { + return null; + } + for (ConsumeRechargeWayEnum way : ConsumeRechargeWayEnum.values()) { + if (way.getValue().equals(rechargeWay)) { + return way; + } + } + return null; + } + + default ConsumeRechargeModeEnum convertRechargeMode(Integer rechargeMode) { + if (rechargeMode == null) { + return null; + } + for (ConsumeRechargeModeEnum way : ConsumeRechargeModeEnum.values()) { + if (way.getValue().equals(rechargeMode)) { + return way; + } + } + return null; + } + + default ConsumeRechargeTypeEnum convertRechargeType(Integer rechargeType) { + if (rechargeType == null) { + return null; + } + for (ConsumeRechargeTypeEnum way : ConsumeRechargeTypeEnum.values()) { + if (way.getValue().equals(rechargeType)) { + return way; + } + } + return null; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/mapstruct/RecordConvert.java b/wms-module-system/src/main/java/top/wms/admin/consume/mapstruct/RecordConvert.java new file mode 100644 index 0000000..f1a8588 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/mapstruct/RecordConvert.java @@ -0,0 +1,8 @@ +package top.ysoft.admin.consume.mapstruct; + +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface RecordConvert { + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/DownRecordDO.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/DownRecordDO.java new file mode 100644 index 0000000..1145537 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/DownRecordDO.java @@ -0,0 +1,54 @@ +package top.ysoft.admin.consume.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; +import java.time.*; + +/** + * 消费下发记录实体 + * + * @author zc + * @since 2026/02/25 11:01 + */ +@Data +@TableName("consume_down_record") +public class DownRecordDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + private Long equipmentId; + + /** + * 人员ID + */ + private Long peopleId; + + /** + * 下发时间 + */ + private LocalDateTime downTime; + + /** + * 下发结果:0-成功,1-失败 + */ + private Integer downResult; + + /** + * 下发结果报文 + */ + private String msg; + + /** + * 0:新增 1:修改 2:下发 3:删除 + */ + private Integer operType; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/GroupDO.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/GroupDO.java new file mode 100644 index 0000000..377d097 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/GroupDO.java @@ -0,0 +1,47 @@ +package top.ysoft.admin.consume.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; +import java.math.BigDecimal; + +/** + * 消费组实体 + * + * @author zc + * @since 2026/01/27 13:48 + */ +@Data +@TableName("consume_group") +public class GroupDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 消费组名称 + */ + private String groupName; + + /** + * 补贴金额 + */ + private BigDecimal money; + + /** + * 部门ID + */ + private String branchId; + + /** + * 补贴日期(每月几号) + */ + private String subsidyDate; + + /** + * 是否补贴:0否,1是 + */ + private Integer timedConsumption; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/GroupUserDO.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/GroupUserDO.java new file mode 100644 index 0000000..71e14a4 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/GroupUserDO.java @@ -0,0 +1,25 @@ +package top.ysoft.admin.consume.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 消费组用户关联表 + * + * @author zc + * @since 2026/01/27 13:48 + */ +@Data +@TableName("consume_group_user") +public class GroupUserDO { + + /** + * 消费组ID + */ + private Long groupId; + + /** + * 用户ID + */ + private Long userId; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/RechargeRecordDO.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/RechargeRecordDO.java new file mode 100644 index 0000000..7f10a05 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/RechargeRecordDO.java @@ -0,0 +1,114 @@ +package top.ysoft.admin.consume.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; +import java.time.*; +import java.math.BigDecimal; + +/** + * 充值记录实体 + * + * @author zc + * @since 2026/01/29 16:11 + */ +@Data +@TableName("consume_recharge_record") +public class RechargeRecordDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 帐号people_id + */ + private Long accountId; + + /** + * 后面A表示工号,如果是B表示卡号 + */ + private String adminRecharge; + + /** + * 内部图片路径 + */ + private String base64; + + /** + * 消费金额,分 + */ + private BigDecimal consumeMoney; + + /** + * 折扣金额,分 + */ + private BigDecimal discountMoney; + + /** + * 工号 + */ + private String empId; + + /** + * 交易发起时间 + */ + private LocalDateTime startTime; + + /** + * 交易结束时间 + */ + private LocalDateTime endTime; + + /** + * 订单类型 + */ + private Integer orderType; + + /** + * 单号,支付宝和微信使用,平台单号 + */ + private String outTradeId; + + /** + * 支付类型:0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码 + */ + private Integer payMode; + + /** + * 交易状态 0在线消费 1离线消费 2超时 3消费异常,4异常消费 + */ + private Integer resultCode; + + /** + * 记录版本1考勤,2门禁,22人脸机消费,25人脸机充值 + */ + private Integer ver; + + /** + * 充值方式 0现金 1支付宝 2微信 3网银 + */ + private Integer rechargeMode; + + /** + * 充值来源 0平台 1手机 + */ + private Integer rechargeWay; + + /** + * 充值类型 0补贴 1充值 2退款 + */ + private Integer rechargeType; + /** + * 清除补贴金额,分 + */ + private BigDecimal clearBtje; + + /** + * 清除充值金额,分 + */ + private BigDecimal clearCzje; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/RecordDO.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/RecordDO.java new file mode 100644 index 0000000..891cc64 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/RecordDO.java @@ -0,0 +1,155 @@ +package top.ysoft.admin.consume.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; +import java.math.BigDecimal; +import java.time.*; + +/** + * 消费记录实体 + * + * @author zc + * @since 2026/01/21 17:53 + */ +@Data +@TableName("consume_record") +public class RecordDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 记录版本1考勤,2门禁,22人脸机消费,25人脸机充值 + */ + private Integer ver; + + /** + * 工号 + */ + private String empId; + + /** + * 帐号 + */ + private Long accountId; + + /** + * 内部图片路径 + */ + private String base64; + + /** + * 消费方式:0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式 + */ + private Integer consumeType; + + /** + * 消费金额,分 + */ + private BigDecimal consumeMoney; + + /** + * 补贴消费金额,分 + */ + private BigDecimal subsidyMoney; + + /** + * 充值消费金额,分 + */ + private BigDecimal rechargeMoney; + + /** + * 折扣金额,分 + */ + private BigDecimal discountMoney; + + /** + * 单号,支付宝和微信使用,平台单号 + */ + private String outTradeId; + + /** + * 交易发起时间 + */ + private String startTime; + + /** + * 交易结束时间 + */ + private String endTime; + + /** + * 交易状态 0在线消费 1离线消费 2超时 3消费异常,4异常消费 + */ + private Integer resultCode; + + /** + * 支付类型:0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码 + */ + private Integer payMode; + + /** + * 订单类型 + */ + private Integer orderType; + + /** + * 钱包扣款模式:0先消费补贴再个人,1仅现金,2仅补贴 + */ + private Integer walletConsumeMode; + + /** + * 多个相似的人脸出现时返回工号,最多3个,分号隔开 + */ + private String likeId; + + /** + * 卡号 + */ + private String cardSn; + + /** + * 交易发起时间 + */ + private LocalDateTime startDate; + + /** + * 交易结束时间 + */ + private LocalDateTime endDate; + + /** + * 交易结果 + */ + private String consumeResult; + + /** + * 备注 + */ + private String remark; + + /** + * 设备ID + */ + private Long devId; + + /** + * 餐段ID + */ + private String timeId; + + /** + * 支付宝买家ID + */ + private String buyerId; + + /** + * 微信买家ID + */ + private String openid; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/TenantDO.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/TenantDO.java new file mode 100644 index 0000000..ba53773 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/TenantDO.java @@ -0,0 +1,93 @@ +package top.ysoft.admin.consume.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 商户信息实体 + * + * @author zc + * @since 2026/01/30 14:55 + */ +@Data +@TableName("consume_tenant") +public class TenantDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 商户名称 + */ + private String name; + + /** + * 商户类型 0支付宝 1微信 2支付宝+微信 3银行 + */ + private Integer type; + + /** + * 客户的appid + */ + private String zfbAppId; + + /** + * 服务商PID + */ + private String zfbSysServiceProviderId; + + /** + * 私有密钥 + */ + private String zfbRsaPrivateKey; + + /** + * 是否停用 + */ + private Long zfbStop; + + /** + * 是否测试模式 + */ + private Integer zfbMode; + + /** + * 公众号appid + */ + private String wxAppid; + + /** + * 服务商商户号 + */ + private String wxMchId; + + /** + * 特约商户号 + */ + private String wxSubMchId; + + /** + * 支付key + */ + private String wxKey; + + /** + * 是否停用 + */ + private Integer wxStop; + + /** + * 是否测试模式 + */ + private Integer wxMode; + + /** + * 平台参数ID + */ + private String paramId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/TimeIntervalDO.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/TimeIntervalDO.java new file mode 100644 index 0000000..c2fa451 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/entity/TimeIntervalDO.java @@ -0,0 +1,74 @@ +package top.ysoft.admin.consume.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; +import java.math.BigDecimal; + +/** + * 消费时段实体 + * + * @author zc + * @since 2026/01/30 14:56 + */ +@Data +@TableName("consume_time_interval") +public class TimeIntervalDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 开始时间 + */ + private String startTime; + + /** + * 结束时间 + */ + private String endTime; + + /** + * 刷卡方式 00刷卡 01刷卡+密码 + */ + private String useMode; + + /** + * 消费餐段 0早餐 1中餐 2午餐 3夜餐 + */ + private Integer tmrtype; + + /** + * 预留 + */ + private String mark; + + /** + * 卡级别 + */ + private Integer level; + + /** + * 最大刷卡次数 + */ + private Integer maxcount; + + /** + * 定额方式下要扣的金额 + */ + private BigDecimal fixmoney; + + /** + * 消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式 + */ + private Integer consumeType; + + /** + * 平台参数ID + */ + private String paramId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/query/DownRecordQuery.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/DownRecordQuery.java new file mode 100644 index 0000000..22c24b3 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/DownRecordQuery.java @@ -0,0 +1,70 @@ +package top.ysoft.admin.consume.model.query; + +import cn.hutool.core.date.DatePattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.springframework.format.annotation.DateTimeFormat; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; +import java.util.Date; +import java.util.List; + +/** + * 消费下发记录查询条件 + * + * @author zc + * @since 2026/02/25 11:01 + */ +@Data +@Schema(description = "消费下发记录查询条件") +public class DownRecordQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + private Long equipmentId; + + /** + * 人员名称 + */ + @Schema(description = "人员名称") + private String peopleName; + + /** + * 下发时间开始 + */ + @Schema(description = "下发时间开始") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + private Date startTime; + + /** + * 下发时间结束 + */ + @Schema(description = "下发时间结束") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + private Date endTime; + + /** + * 下发结果:0-成功,1-失败 + */ + @Schema(description = "下发结果:0-成功,1-失败") + private Integer downResult; + + /** + * 0:新增 1:修改 2:下发 3:删除 + */ + @Schema(description = "0:新增 1:修改 2:下发 3:删除") + private Integer operType; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/query/GroupQuery.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/GroupQuery.java new file mode 100644 index 0000000..58a4730 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/GroupQuery.java @@ -0,0 +1,33 @@ +package top.ysoft.admin.consume.model.query; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 消费组查询条件 + * + * @author zc + * @since 2026/01/27 13:48 + */ +@Data +@Schema(description = "消费组查询条件") +public class GroupQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 消费组名称 + */ + @Query(type = QueryType.LIKE) + private String groupName; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/query/RechargeRecordQuery.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/RechargeRecordQuery.java new file mode 100644 index 0000000..86a4355 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/RechargeRecordQuery.java @@ -0,0 +1,58 @@ +package top.ysoft.admin.consume.model.query; + +import cn.hutool.core.date.DatePattern; +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 充值记录查询条件 + * + * @author zc + * @since 2026/01/29 16:11 + */ +@Data +@Schema(description = "充值记录查询条件") +public class RechargeRecordQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 帐号名称 + */ + @Schema(description = "帐号名称") + private String accountName; + + /** + * 工号 + */ + @Schema(description = "工号") + private String empId; + + /** + * 充值类型:0补贴 1充值 + */ + @Schema(description = "充值类型:0补贴 1充值 2退款") + private Integer rechargeType; + /** + * 开始时间 + */ + @Schema(description = "开始时间") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + private Date startTime; + + /** + * 结束时间 + */ + @Schema(description = "结束时间") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + private Date endTime; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/query/RecordQuery.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/RecordQuery.java new file mode 100644 index 0000000..fa583c8 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/RecordQuery.java @@ -0,0 +1,84 @@ +package top.ysoft.admin.consume.model.query; + +import cn.hutool.core.date.DatePattern; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 消费记录查询条件 + * + * @author zc + * @since 2026/01/21 17:53 + */ +@Data +@Schema(description = "消费记录查询条件") +public class RecordQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 工号 + */ + @Schema(description = "工号") + @Query(type = QueryType.EQ) + private String empId; + + /** + * 单号,支付宝和微信使用,平台单号 + */ + @Schema(description = "单号,支付宝和微信使用,平台单号") + @Query(type = QueryType.EQ) + private String outTradeId; + + /** + * 人员名称 + */ + @Schema(description = "人员名称") + private String accountName; + + /** + * 交易发起时间 + */ + @Schema(description = "交易发起时间") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + @Query(type = QueryType.GT) + private Date startDate; + + /** + * 交易结束时间 + */ + @Schema(description = "交易结束时间") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + @Query(type = QueryType.LT) + private Date endDate; + + /** + * 交易状态 0在线消费 1离线消费 2超时 3消费异常,4异常消费 + */ + @Schema(description = "交易状态 0在线消费 1离线消费 2超时 3消费异常,4异常消费") + @Query(type = QueryType.EQ) + private Integer resultCode; + + /** + * 支付类型:0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码 + */ + @Schema(description = "支付类型:0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码") + @Query(type = QueryType.EQ) + private Integer payMode; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + @Query(type = QueryType.EQ) + private Long devId; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/query/ReportQuery.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/ReportQuery.java new file mode 100644 index 0000000..fff5998 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/ReportQuery.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.consume.model.query; + +import cn.hutool.core.date.DatePattern; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; +import java.util.List; + +@Data +@Schema(description = "报表查询条件") +public class ReportQuery { + + @Schema(description = "创建时间", example = "2023-08-08 00:00:00,2023-08-08 23:59:59") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + @Size(max = 2, message = "创建时间必须是一个范围") + private List createTime; + + private String branchId; + + private String peopleName; + + private String gh; + + private Integer tmrtype; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/query/TenantQuery.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/TenantQuery.java new file mode 100644 index 0000000..997ab35 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/TenantQuery.java @@ -0,0 +1,40 @@ +package top.ysoft.admin.consume.model.query; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 商户信息查询条件 + * + * @author zc + * @since 2026/01/30 14:55 + */ +@Data +@Schema(description = "商户信息查询条件") +public class TenantQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 商户类型 0支付宝 1微信 2支付宝+微信 3银行 + */ + @Schema(description = "商户类型 0支付宝 1微信 2支付宝+微信 3银行") + @Query(type = QueryType.EQ) + private Integer type; + + /** + * 商户名称 + */ + @Schema(description = "商户名称") + @Query(type = QueryType.LIKE) + private String name; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/query/TimeIntervalQuery.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/TimeIntervalQuery.java new file mode 100644 index 0000000..9f62bd4 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/query/TimeIntervalQuery.java @@ -0,0 +1,41 @@ +package top.ysoft.admin.consume.model.query; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 消费时段查询条件 + * + * @author zc + * @since 2026/01/30 14:56 + */ +@Data +@Schema(description = "消费时段查询条件") +public class TimeIntervalQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 刷卡方式 00刷卡 01刷卡+密码 + */ + @Schema(description = "刷卡方式 00刷卡 01刷卡+密码") + @Query(type = QueryType.EQ) + private String useMode; + + /** + * 消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式 + */ + @Schema(description = "消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式") + @Query(type = QueryType.EQ) + private Integer consumeType; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/req/DownRecordReq.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/DownRecordReq.java new file mode 100644 index 0000000..bd91635 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/DownRecordReq.java @@ -0,0 +1,76 @@ +package top.ysoft.admin.consume.model.req; + +import jakarta.validation.constraints.*; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 创建或修改消费下发记录参数 + * + * @author zc + * @since 2026/02/25 11:01 + */ +@Data +@Schema(description = "创建或修改消费下发记录参数") +public class DownRecordReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + @NotNull(message = "设备ID不能为空") + private Long equipmentId; + + /** + * 人员ID + */ + @Schema(description = "人员ID") + @NotNull(message = "人员ID不能为空") + private Long peopleId; + + /** + * 下发时间 + */ + @Schema(description = "下发时间") + @NotNull(message = "下发时间不能为空") + private LocalDateTime downTime; + + /** + * 下发结果:0-成功,1-失败 + */ + @Schema(description = "下发结果:0-成功,1-失败") + @NotNull(message = "下发结果:0-成功,1-失败不能为空") + private Integer downResult; + + /** + * 0:新增 1:修改 2:下发 3:删除 + */ + @Schema(description = "0:新增 1:修改 2:下发 3:删除") + @NotNull(message = "0:新增 1:修改 2:下发 3:删除不能为空") + private Integer operType; + + /** + * 创建人 + */ + @Schema(description = "创建人") + @NotNull(message = "创建人不能为空") + private Long createUser; + + /** + * 创建时间 + */ + @Schema(description = "创建时间") + @NotNull(message = "创建时间不能为空") + private LocalDateTime createTime; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/req/GroupReq.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/GroupReq.java new file mode 100644 index 0000000..d744342 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/GroupReq.java @@ -0,0 +1,62 @@ +package top.ysoft.admin.consume.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 创建或修改消费组参数 + * + * @author zc + * @since 2026/01/27 13:48 + */ +@Data +@Schema(description = "创建或修改消费组参数") +public class GroupReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 消费组名称 + */ + @Schema(description = "消费组名称") + @Length(max = 64, message = "消费组名称长度不能超过64个字符") + private String groupName; + + /** + * 补贴金额 + */ + @Schema(description = "补贴金额") + private BigDecimal money; + + /** + * 补贴日期(每月几号) + */ + @Schema(description = "补贴日期(每月几号)") + private String subsidyDate; + + /** + * 是否补贴:0否,1是 + */ + @Schema(description = "是否补贴:0否,1是") + private Integer timedConsumption; + + /** + * 人员ids + */ + @Schema(description = "人员ids") + private List peopleIds; + + /** + * 部门id + */ + @Schema(description = "部门id") + private String branchId; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/req/PeopleDepositImportReq.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/PeopleDepositImportReq.java new file mode 100644 index 0000000..9b3baf6 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/PeopleDepositImportReq.java @@ -0,0 +1,47 @@ +package top.ysoft.admin.consume.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.ImportPolicyEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 人员充值导入参数 + * + * @author zc + * @since 2024-6-17 16:42 + */ +@Data +@Schema(description = "人员充值导入参数") +public class PeopleDepositImportReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 导入会话KEY + */ + @Schema(description = "导入会话KEY", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed") + @NotBlank(message = "导入已过期,请重新上传") + private String importKey; + + /** + * 用户重复策略 + */ + @Schema(description = "人员不存在策略", example = "1") + @NotNull(message = "人员不存在策略不能为空") + private ImportPolicyEnum peopleNotExist; + + /** + * 消费未启用策略 + */ + @Schema(description = "消费未启用策略", example = "1") + @NotNull(message = "消费未启用策略不能为空") + private ImportPolicyEnum consumeNotEnable; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/req/PeopleDepositImportRowReq.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/PeopleDepositImportRowReq.java new file mode 100644 index 0000000..9b865b6 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/PeopleDepositImportRowReq.java @@ -0,0 +1,63 @@ +package top.ysoft.admin.consume.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import top.ysoft.admin.common.constant.RegexConstants; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 人员充值导入行数据 + * + * @author Kils + * @since 2024-6-17 16:42 + */ +@Data +@Schema(description = "人员充值导入行数据") +public class PeopleDepositImportRowReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户名 + */ + private String peopleName; + + /** + * 工号 + */ + @NotBlank(message = "工号不能为空") + private String gh; + + /** + * 充值金额 + */ + @NotBlank(message = "充值金额不能为空") + @Pattern(regexp = RegexConstants.MONEY, message = "充值金额必须为数字") + private String czje; + + /** + * 补贴金额 + */ + @NotBlank(message = "补贴金额不能为空") + @Pattern(regexp = RegexConstants.MONEY, message = "补贴金额必须为数字") + private String btje; + + /** + * 是否启用消费 + */ + @NotBlank(message = "是否启用消费不能为空") + private String isConsume; + + /** + * 是否冻结 + */ + @NotBlank(message = "是否冻结不能为空") + private String freeze; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/req/RechargeRecordReq.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/RechargeRecordReq.java new file mode 100644 index 0000000..0a5d7da --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/RechargeRecordReq.java @@ -0,0 +1,100 @@ +package top.ysoft.admin.consume.model.req; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 创建或修改充值记录参数 + * + * @author zc + * @since 2026/01/29 16:11 + */ +@Data +@Schema(description = "创建或修改充值记录参数") +public class RechargeRecordReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 帐号people_id + */ + @Schema(description = "帐号people_id") + private Long accountId; + + /** + * 后面A表示工号,如果是B表示卡号 + */ + @Schema(description = "后面A表示工号,如果是B表示卡号") + @Length(max = 50, message = "后面A表示工号,如果是B表示卡号长度不能超过 {max} 个字符") + private String adminRecharge; + + /** + * 消费金额,分 + */ + @Schema(description = "消费金额,分") + private BigDecimal consumeMoney; + + /** + * 折扣金额,分 + */ + @Schema(description = "折扣金额,分") + private BigDecimal discountMoney; + + /** + * 工号 + */ + @Schema(description = "工号") + @Length(max = 50, message = "工号长度不能超过 {max} 个字符") + private String empId; + + /** + * 订单类型 + */ + @Schema(description = "订单类型") + private Integer orderType; + + /** + * 单号,支付宝和微信使用,平台单号 + */ + @Schema(description = "单号,支付宝和微信使用,平台单号") + @Length(max = 50, message = "单号,支付宝和微信使用,平台单号长度不能超过 {max} 个字符") + private String outTradeId; + + /** + * 支付类型:0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码 + */ + @Schema(description = "支付类型:0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码") + private Integer payMode; + + /** + * 交易状态 0在线消费 1离线消费 2超时 3消费异常,4异常消费 + */ + @Schema(description = "交易状态 0在线消费 1离线消费 2超时 3消费异常,4异常消费") + private Integer resultCode; + + /** + * 充值方式 + */ + @Schema(description = "充值方式") + private Integer rechargeMode; + + /** + * 充值来源 + */ + @Schema(description = "充值来源") + private Integer rechargeWay; + + /** + * 充值类型 + */ + @Schema(description = "充值类型") + private Integer rechargeType; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/req/RecordReq.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/RecordReq.java new file mode 100644 index 0000000..bfc2acd --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/RecordReq.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.consume.model.req; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 创建或修改消费记录参数 + * + * @author zc + * @since 2026/01/21 17:53 + */ +@Data +@Schema(description = "创建或修改消费记录参数") +public class RecordReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/req/TenantReq.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/TenantReq.java new file mode 100644 index 0000000..6ccf51d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/TenantReq.java @@ -0,0 +1,112 @@ +package top.ysoft.admin.consume.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 创建或修改商户信息参数 + * + * @author zc + * @since 2026/01/30 14:55 + */ +@Data +@Schema(description = "创建或修改商户信息参数") +public class TenantReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + /** + * 商户名称 + */ + @Schema(description = "商户名称") + @Length(max = 50, message = "商户名称长度不能超过50个字符") + private String name; + + /** + * 商户类型 0支付宝 1微信 2支付宝+微信 3银行 + */ + @Schema(description = "商户类型 0支付宝 1微信 2支付宝+微信 3银行") + private Integer type; + + /** + * 客户的appid + */ + @Schema(description = "客户的appid") + @Length(max = 50, message = "客户的appid长度不能超过50个字符") + private String zfbAppId; + + /** + * 服务商PID + */ + @Schema(description = "服务商PID") + @Length(max = 50, message = "服务商PID长度不能超过50个字符") + private String zfbSysServiceProviderId; + + /** + * 私有密钥 + */ + @Schema(description = "私有密钥") + private String zfbRsaPrivateKey; + + /** + * 是否停用 + */ + @Schema(description = "是否停用") + private Long zfbStop; + + /** + * 是否测试模式 + */ + @Schema(description = "是否测试模式") + private Integer zfbMode; + + /** + * 公众号appid + */ + @Schema(description = "公众号appid") + @Length(max = 50, message = "公众号appid长度不能超过50个字符") + private String wxAppid; + + /** + * 服务商商户号 + */ + @Schema(description = "服务商商户号") + @Length(max = 50, message = "服务商商户号长度不能超过50个字符") + private String wxMchId; + + /** + * 特约商户号 + */ + @Schema(description = "特约商户号") + @Length(max = 50, message = "特约商户号长度不能超过50个字符") + private String wxSubMchId; + + /** + * 支付key + */ + @Schema(description = "支付key") + @Length(max = 50, message = "支付key长度不能超过50个字符") + private String wxKey; + + /** + * 是否停用 + */ + @Schema(description = "是否停用") + private Integer wxStop; + + /** + * 是否测试模式 + */ + @Schema(description = "是否测试模式") + private Integer wxMode; + + /** + * 平台参数ID + */ + @Schema(description = "平台参数ID") + private String paramId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/req/TimeIntervalReq.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/TimeIntervalReq.java new file mode 100644 index 0000000..24b704a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/req/TimeIntervalReq.java @@ -0,0 +1,58 @@ +package top.ysoft.admin.consume.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 创建或修改消费时段参数 + * + * @author zc + * @since 2026/01/30 14:56 + */ +@Data +@Schema(description = "创建或修改消费时段参数") +public class TimeIntervalReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 开始时间 + */ + private String startTime; + + /** + * 结束时间 + */ + private String endTime; + + /** + * 刷卡方式 00刷卡 01刷卡+密码 + */ + private String useMode; + + /** + * 最大刷卡次数 + */ + private Integer maxcount; + + /** + * 定额方式下要扣的金额 + */ + private BigDecimal fixmoney; + + /** + * 消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式 + */ + private Integer consumeType; + + /** + * 消费餐段 0早餐 1中餐 2午餐 3夜餐 + */ + private Integer tmrtype; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/DownRecordResp.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/DownRecordResp.java new file mode 100644 index 0000000..b7025bd --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/DownRecordResp.java @@ -0,0 +1,78 @@ +package top.ysoft.admin.consume.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; + +/** + * 消费下发记录信息 + * + * @author zc + * @since 2026/02/25 11:01 + */ +@Data +@Schema(description = "消费下发记录信息") +public class DownRecordResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + private Long equipmentId; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + private String equipmentName; + + /** + * 人员ID + */ + @Schema(description = "人员ID") + private Long peopleId; + + /** + * 人员名称 + */ + @Schema(description = "人员名称") + private String peopleName; + + /** + * 工号 + */ + @Schema(description = "工号") + private String gh; + + /** + * 下发时间 + */ + @Schema(description = "下发时间") + private LocalDateTime downTime; + + /** + * 下发结果:0-成功,1-失败 + */ + @Schema(description = "下发结果:0-成功,1-失败") + private Integer downResult; + + /** + * 下发结果报文 + */ + @Schema(description = "下发结果报文") + private String msg; + + /** + * 0:新增 1:修改 2:下发 3:删除 + */ + @Schema(description = "0:新增 1:修改 2:下发 3:删除") + private Integer operType; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/GroupResp.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/GroupResp.java new file mode 100644 index 0000000..ae2d07d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/GroupResp.java @@ -0,0 +1,64 @@ +package top.ysoft.admin.consume.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.math.BigDecimal; +import java.time.*; +import java.util.List; + +/** + * 消费组信息 + * + * @author zc + * @since 2026/01/27 13:48 + */ +@Data +@Schema(description = "消费组信息") +public class GroupResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 消费组名称 + */ + @Schema(description = "消费组名称") + private String groupName; + + /** + * 补贴金额 + */ + @Schema(description = "补贴金额") + private BigDecimal money; + + /** + * 补贴日期 + */ + @Schema(description = "补贴日期(每月几号)") + private String subsidyDate; + + /** + * 是否补贴 + */ + @Schema(description = "是否补贴") + private Integer timedConsumption; + + /** + * 人员ids + */ + @Schema(description = "人员ids") + private List peopleIds; + + /** + * 人员下拉数据 + */ + @Schema(description = "人员下拉数据") + private List peopleSelect; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/PeopleDepositImportParseResp.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/PeopleDepositImportParseResp.java new file mode 100644 index 0000000..965cf76 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/PeopleDepositImportParseResp.java @@ -0,0 +1,53 @@ +package top.ysoft.admin.consume.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 人员充值导入解析结果 + * + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "人员充值导入解析结果") +public class PeopleDepositImportParseResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 导入会话 Key + */ + @Schema(description = "导入会话Key", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed") + private String importKey; + + /** + * 总计行数 + */ + @Schema(description = "总计行数", example = "100") + private Integer totalRows; + + /** + * 有效行数 + */ + @Schema(description = "有效行数", example = "100") + private Integer validRows; + + /** + * 重复行数 + */ + @Schema(description = "重复行数", example = "100") + private Integer duplicateUserRows; + + /** + * 消费未启用行数 + */ + @Schema(description = "消费未启用行数", example = "100") + private Integer unEnabledRows; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/RechargeRecordResp.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/RechargeRecordResp.java new file mode 100644 index 0000000..d153fac --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/RechargeRecordResp.java @@ -0,0 +1,171 @@ +package top.ysoft.admin.consume.model.resp; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; +import top.ysoft.admin.common.enums.ConsumeRechargeModeEnum; +import top.ysoft.admin.common.enums.ConsumeRechargeTypeEnum; +import top.ysoft.admin.common.enums.ConsumeRechargeWayEnum; +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 充值记录信息 + * + * @author zc + * @since 2026/01/29 16:11 + */ +@Data +@Schema(description = "充值记录信息") +public class RechargeRecordResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 帐号people_id + */ + @Schema(description = "帐号people_id") + @ExcelIgnore + private Long accountId; + + /** + * 人员名称 + */ + @Schema(description = "人员名称") + @ExcelProperty(value = "人员名称", order = 2) + private String accountName; + + /** + * 后面A表示工号,如果是B表示卡号 + */ + @Schema(description = "后面A表示工号,如果是B表示卡号") + @ExcelIgnore + private String adminRecharge; + + /** + * 内部图片路径 + */ + @Schema(description = "内部图片路径") + @ExcelIgnore + private String base64; + + /** + * 消费金额,分 + */ + @Schema(description = "充值金额,分") + @ExcelProperty(value = "操作金额(分)", order = 3) + private BigDecimal consumeMoney; + + /** + * 折扣金额,分 + */ + @Schema(description = "折扣金额,分") + @ExcelIgnore + private BigDecimal discountMoney; + + /** + * 工号 + */ + @Schema(description = "工号") + @ExcelProperty(value = "工号", order = 1) + private String empId; + + /** + * 交易发起时间 + */ + @Schema(description = "交易发起时间") + @ExcelIgnore + private LocalDateTime startTime; + + /** + * 交易结束时间 + */ + @Schema(description = "交易结束时间") + @ExcelIgnore + private LocalDateTime endTime; + + /** + * 订单类型 + */ + @Schema(description = "订单类型") + @ExcelIgnore + private Integer orderType; + + /** + * 单号,支付宝和微信使用,平台单号 + */ + @Schema(description = "单号,支付宝和微信使用,平台单号") + @ExcelIgnore + private String outTradeId; + + /** + * 支付类型:0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码 + */ + @Schema(description = "支付类型:0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码") + @ExcelIgnore + private Integer payMode; + + /** + * 交易状态 0在线消费 1离线消费 2超时 3消费异常,4异常消费 + */ + @Schema(description = "交易状态 0在线消费 1离线消费 2超时 3消费异常,4异常消费") + @ExcelIgnore + private Integer resultCode; + + /** + * 记录版本1考勤,2门禁,22人脸机消费,25人脸机充值 + */ + @Schema(description = "记录版本1考勤,2门禁,22人脸机消费,25人脸机充值") + @ExcelIgnore + private Integer ver; + + /** + * 充值方式 + */ + @Schema(description = "充值方式 0现金 1支付宝 2微信 3网银") + @ExcelProperty(value = "充值方式", converter = ExcelBaseEnumConverter.class, order = 5) + private ConsumeRechargeModeEnum rechargeMode; + + /** + * 充值来源 + */ + @Schema(description = "充值来源 0平台 1手机") + @ExcelProperty(value = "充值来源", converter = ExcelBaseEnumConverter.class, order = 6) + private ConsumeRechargeWayEnum rechargeWay; + + /** + * 充值类型 + */ + @Schema(description = "充值类型 0补贴 1充值") + @ExcelProperty(value = "充值类型", converter = ExcelBaseEnumConverter.class, order = 4) + private ConsumeRechargeTypeEnum rechargeType; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") + @ExcelProperty(value = "创建时间", order = 7) + @ColumnWidth(30) + private LocalDateTime createTime; + + /** + * 清除补贴金额,分 + */ + @Schema(description = "清除补贴金额,分") + @ExcelIgnore + private BigDecimal clearBtje; + + /** + * 清除充值金额,分 + */ + @Schema(description = "清除充值金额,分") + @ExcelIgnore + private BigDecimal clearCzje; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/RecordResp.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/RecordResp.java new file mode 100644 index 0000000..567971b --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/RecordResp.java @@ -0,0 +1,247 @@ +package top.ysoft.admin.consume.model.resp; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; +import top.ysoft.admin.common.enums.*; +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 消费记录信息 + * + * @author zc + * @since 2026/01/21 17:53 + */ +@Data +@Schema(description = "消费记录信息") +public class RecordResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 记录版本1考勤,2门禁,22人脸机消费,25人脸机充值 + */ + @Schema(description = "记录版本1考勤,2门禁,22人脸机消费,25人脸机充值") + @ExcelIgnore + private Integer ver; + + /** + * 工号 + */ + @Schema(description = "工号") + @ExcelProperty(value = "工号", order = 1) + private String empId; + + /** + * 帐号 + */ + @Schema(description = "帐号") + @ExcelIgnore + private Long accountId; + + /** + * 人员名称 + */ + @Schema(description = "人员名称") + @ExcelProperty(value = "人员", order = 2) + private String accountName; + + /** + * 内部图片路径 + */ + @Schema(description = "内部图片路径") + @ExcelIgnore + private String base64; + + /** + * 消费方式:0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式 + */ + @Schema(description = "消费方式:0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式") + @ExcelProperty(value = "消费方式", converter = ExcelBaseEnumConverter.class, order = 4) + private ConsumeTypeConverter consumeType; + + /** + * 消费金额,分 + */ + @Schema(description = "消费总金额,分") + @ExcelProperty(value = "消费总金额", order = 5) + private BigDecimal consumeMoney = BigDecimal.ZERO; + + /** + * 补贴消费金额,分 + */ + @Schema(description = "补贴消费金额,分") + @ExcelProperty(value = "补贴消费金额", order = 6) + private BigDecimal subsidyMoney = BigDecimal.ZERO; + + /** + * 充值消费金额,分 + */ + @Schema(description = "充值消费金额,分") + @ExcelProperty(value = "充值消费金额", order = 7) + private BigDecimal rechargeMoney = BigDecimal.ZERO; + + /** + * 折扣金额,分 + */ + @Schema(description = "折扣金额,分") + @ExcelIgnore + private BigDecimal discountMoney = BigDecimal.ZERO; + + /** + * 单号,支付宝和微信使用,平台单号 + */ + @Schema(description = "单号,支付宝和微信使用,平台单号") + @ExcelProperty(value = "单号", order = 10) + private String outTradeId; + + /** + * 交易发起时间 + */ + @Schema(description = "交易发起时间") + @ExcelIgnore + private String startTime; + + /** + * 交易结束时间 + */ + @Schema(description = "交易结束时间") + @ExcelIgnore + private String endTime; + + /** + * 交易状态 0在线消费 1离线消费 2超时 3消费异常,4异常消费 + */ + @Schema(description = "交易状态 0在线消费 1离线消费 2超时 3消费异常,4异常消费") + @ExcelProperty(value = "交易状态", converter = ExcelBaseEnumConverter.class, order = 11) + private ConsumeResultEnum resultCode; + + /** + * 支付类型:0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码 + */ + @Schema(description = "支付类型:0人脸 1云卡 2刷卡 3支付宝 4微信 5取餐码") + @ExcelProperty(value = "支付类型", converter = ExcelBaseEnumConverter.class, order = 12) + private ConsumePayModeEnum payMode; + + /** + * 订单类型 + */ + @Schema(description = "订单类型") + @ExcelProperty(value = "订单类型", converter = ExcelBaseEnumConverter.class, order = 13) + private ConsumeOrderTypeEnum orderType; + + /** + * 钱包扣款模式:0先消费补贴再个人,1仅现金,2仅补贴 + */ + @Schema(description = "钱包扣款模式:0先消费补贴再个人,1仅现金,2仅补贴") + @ExcelIgnore + private ConsumeWalletModeEnum walletConsumeMode; + + /** + * 多个相似的人脸出现时返回工号,最多3个,分号隔开 + */ + @Schema(description = "多个相似的人脸出现时返回工号,最多3个,分号隔开") + @ExcelIgnore + private String likeId; + + /** + * 卡号 + */ + @Schema(description = "卡号") + @ExcelIgnore + private String cardSn; + + /** + * 交易发起时间 + */ + @Schema(description = "交易发起时间") + @ExcelIgnore + private LocalDateTime startDate; + + /** + * 交易结束时间 + */ + @Schema(description = "交易结束时间") + @ExcelIgnore + private LocalDateTime endDate; + + /** + * 交易结果 + */ + @Schema(description = "交易结果") + @ExcelIgnore + private String consumeResult; + + /** + * 备注 + */ + @Schema(description = "备注") + @ExcelIgnore + private String remark; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + @ExcelIgnore + private Long devId; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + @ExcelProperty(value = "设备名称", order = 3) + private String equipmentName; + + /** + * 餐段ID + */ + @Schema(description = "餐段ID") + @ExcelIgnore + private String timeId; + + /** + * 支付宝买家ID + */ + @Schema(description = "支付宝买家ID") + @ExcelIgnore + private String buyerId; + + /** + * 微信买家ID + */ + @Schema(description = "微信买家ID") + @ExcelIgnore + private String openid; + + /** + * 充值金额 + */ + @Schema(description = "充值金额") + @ExcelProperty(value = "充值金额", order = 9) + private BigDecimal czje = BigDecimal.ZERO; + + /** + * 补贴金额 + */ + @Schema(description = "补贴金额") + @ExcelProperty(value = "补贴金额", order = 8) + private BigDecimal btje = BigDecimal.ZERO; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") + @ExcelProperty(value = "创建时间", order = 14) + @ColumnWidth(30) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/TenantResp.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/TenantResp.java new file mode 100644 index 0000000..878af69 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/TenantResp.java @@ -0,0 +1,120 @@ +package top.ysoft.admin.consume.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; + +/** + * 商户信息信息 + * + * @author zc + * @since 2026/01/30 14:55 + */ +@Data +@Schema(description = "商户信息信息") +public class TenantResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 商户名称 + */ + @Schema(description = "商户名称") + private String name; + + /** + * 商户类型 + */ + @Schema(description = "商户类型") + private String type; + + /** + * 客户的appid + */ + @Schema(description = "客户的appid") + private String zfbAppId; + + /** + * 服务商PID + */ + @Schema(description = "服务商PID") + private String zfbSysServiceProviderId; + + /** + * 私有密钥 + */ + @Schema(description = "私有密钥") + private String zfbRsaPrivateKey; + + /** + * 是否停用 + */ + @Schema(description = "是否停用") + private String zfbStop; + + /** + * 是否测试模式 + */ + @Schema(description = "是否测试模式") + private String zfbMode; + + /** + * 公众号appid + */ + @Schema(description = "公众号appid") + private String wxAppid; + + /** + * 服务商商户号 + */ + @Schema(description = "服务商商户号") + private String wxMchId; + + /** + * 特约商户号 + */ + @Schema(description = "特约商户号") + private String wxSubMchId; + + /** + * 支付key + */ + @Schema(description = "支付key") + private String wxKey; + + /** + * 是否停用 + */ + @Schema(description = "是否停用") + private String wxStop; + + /** + * 是否测试模式 + */ + @Schema(description = "是否测试模式") + private String wxMode; + + /** + * 平台参数ID + */ + @Schema(description = "平台参数ID") + private String paramId; + + /** + * 更新者 + */ + @Schema(description = "更新者") + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/TimeIntervalResp.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/TimeIntervalResp.java new file mode 100644 index 0000000..7395315 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/TimeIntervalResp.java @@ -0,0 +1,97 @@ +package top.ysoft.admin.consume.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; +import java.math.BigDecimal; + +/** + * 消费时段信息 + * + * @author zc + * @since 2026/01/30 14:56 + */ +@Data +@Schema(description = "消费时段信息") +public class TimeIntervalResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 开始时间 + */ + @Schema(description = "开始时间") + private String startTime; + + /** + * 结束时间 + */ + @Schema(description = "结束时间") + private String endTime; + + /** + * 刷卡方式 00刷卡 01刷卡+密码 + */ + @Schema(description = "刷卡方式 00刷卡 01刷卡+密码") + private String useMode; + + /** + * 消费餐段 0早餐 1中餐 2午餐 3夜餐 + */ + @Schema(description = "消费餐段 0早餐 1中餐 2午餐 3夜餐") + private String tmrtype; + + /** + * 预留 + */ + @Schema(description = "预留") + private String mark; + + /** + * 卡级别 + */ + @Schema(description = "卡级别") + private Integer level; + + /** + * 最大刷卡次数 + */ + @Schema(description = "最大刷卡次数") + private Integer maxcount; + + /** + * 定额方式下要扣的金额 + */ + @Schema(description = "定额方式下要扣的金额") + private BigDecimal fixmoney; + + /** + * 消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式 + */ + @Schema(description = "消费方式 0单价 1定额 2时段模式 3计次 5点餐机模式 9身份模式") + private String consumeType; + + /** + * 平台参数ID + */ + @Schema(description = "平台参数ID") + private String paramId; + + /** + * 更新者 + */ + @Schema(description = "更新者") + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/BaseReport.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/BaseReport.java new file mode 100644 index 0000000..b2d6cc1 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/BaseReport.java @@ -0,0 +1,73 @@ +package top.ysoft.admin.consume.model.resp.report; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@Schema(description = "基础报表") +public class BaseReport { + + /** + * 合计次数 + */ + @ExcelProperty(value = "合计次数", order = Integer.MAX_VALUE - 10) + private Integer totalCount = 0; + + /** + * 合计金额 + */ + @ExcelProperty(value = "合计金额", order = Integer.MAX_VALUE - 9) + private BigDecimal totalAmount = BigDecimal.ZERO; + + /** + * 单价次数 + */ + @ExcelProperty(value = "单价次数", order = Integer.MAX_VALUE - 8) + private Integer unitPriceCount = 0; + + /** + * 单价金额 + */ + @ExcelProperty(value = "单价金额", order = Integer.MAX_VALUE - 7) + private BigDecimal unitPriceAmount = BigDecimal.ZERO; + + /** + * 定值次数 + */ + @ExcelProperty(value = "定值次数", order = Integer.MAX_VALUE - 6) + private Integer fixedValueCount = 0; + + /** + * 定值金额 + */ + @ExcelProperty(value = "定值金额", order = Integer.MAX_VALUE - 5) + private BigDecimal fixedValueAmount = BigDecimal.ZERO; + + /** + * 计次次数 + */ + @ExcelProperty(value = "计次次数", order = Integer.MAX_VALUE - 4) + private Integer countingCount = 0; + + /** + * 计次金额 + */ + @ExcelProperty(value = "计次金额", order = Integer.MAX_VALUE - 3) + private BigDecimal countingAmount = BigDecimal.ZERO; + + /** + * 二维码次数 + */ + @ExcelProperty(value = "二维码次数", order = Integer.MAX_VALUE - 2) + private Integer qrcodeCount = 0; + + /** + * 二维码金额 + */ + @ExcelProperty(value = "二维码金额", order = Integer.MAX_VALUE - 1) + private BigDecimal qrcodeAmount = BigDecimal.ZERO; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/BranchReport.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/BranchReport.java new file mode 100644 index 0000000..3464bf9 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/BranchReport.java @@ -0,0 +1,20 @@ +package top.ysoft.admin.consume.model.resp.report; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 部门报表 + */ +@Data +@Schema(description = "部门报表") +public class BranchReport extends BaseReport { + + /** + * 部门名称 + */ + @ExcelProperty(value = "部门名称", order = Integer.MAX_VALUE - 11) + private String branchName; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/MealReport.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/MealReport.java new file mode 100644 index 0000000..d1619cb --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/MealReport.java @@ -0,0 +1,35 @@ +package top.ysoft.admin.consume.model.resp.report; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; +import top.ysoft.admin.common.enums.ConsumeTmrtypeEnum; + +/** + * 餐段报表 + */ +@Data +@Schema(description = "餐段报表") +public class MealReport extends BaseReport { + + /** + * 商户名称 + */ + @ExcelProperty(value = "商户名称", order = Integer.MAX_VALUE - 13) + private String merchant; + + /** + * 设备名称 + */ + + @ExcelProperty(value = "设备名称", order = Integer.MAX_VALUE - 12) + private String equipmentName; + + /** + * 消费餐段 0早餐 1中餐 2午餐 3夜餐 + */ + @ExcelProperty(value = "消费餐段", converter = ExcelBaseEnumConverter.class, order = Integer.MAX_VALUE - 11) + private ConsumeTmrtypeEnum tmrtype; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/MerchantReport.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/MerchantReport.java new file mode 100644 index 0000000..83e9a98 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/MerchantReport.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.consume.model.resp.report; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 商户报表 + */ +@Data +@Schema(description = "商户报表") +public class MerchantReport extends BaseReport { + + /** + * 商户名称 + */ + @Schema(description = "商户名称") + @ExcelProperty(value = "商户名称", order = Integer.MAX_VALUE - 12) + private String merchant; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + @ExcelProperty(value = "设备名称", order = Integer.MAX_VALUE - 11) + private String equipmentName; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/PeopleReport.java b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/PeopleReport.java new file mode 100644 index 0000000..04c64b3 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/model/resp/report/PeopleReport.java @@ -0,0 +1,32 @@ +package top.ysoft.admin.consume.model.resp.report; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 人员报表 + */ +@Data +@Schema(description = "人员报表") +public class PeopleReport extends BaseReport { + + /** + * 人员名称 + */ + @ExcelProperty(value = "人员名称", order = Integer.MAX_VALUE - 13) + private String peopleName; + + /** + * 人员工号 + */ + @ExcelProperty(value = "人员工号", order = Integer.MAX_VALUE - 12) + private String gh; + + /** + * 部门名称 + */ + @ExcelProperty(value = "部门名称", order = Integer.MAX_VALUE - 11) + private String branchName; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/DownRecordService.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/DownRecordService.java new file mode 100644 index 0000000..264b655 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/DownRecordService.java @@ -0,0 +1,17 @@ +package top.ysoft.admin.consume.service; + +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.consume.model.query.DownRecordQuery; +import top.ysoft.admin.consume.model.req.DownRecordReq; +import top.ysoft.admin.consume.model.resp.DownRecordResp; + +/** + * 消费下发记录业务接口 + * + * @author zc + * @since 2026/02/25 11:01 + */ +public interface DownRecordService extends BaseService { + + void down(Long equipmentId, Long peopleId); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/GroupService.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/GroupService.java new file mode 100644 index 0000000..ff7fce7 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/GroupService.java @@ -0,0 +1,14 @@ +package top.ysoft.admin.consume.service; + +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.consume.model.query.GroupQuery; +import top.ysoft.admin.consume.model.req.GroupReq; +import top.ysoft.admin.consume.model.resp.GroupResp; + +/** + * 消费组业务接口 + * + * @author zc + * @since 2026/01/27 13:48 + */ +public interface GroupService extends BaseService {} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/GroupUserService.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/GroupUserService.java new file mode 100644 index 0000000..a8bca2f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/GroupUserService.java @@ -0,0 +1,8 @@ +package top.ysoft.admin.consume.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import top.ysoft.admin.consume.model.entity.GroupUserDO; + +public interface GroupUserService extends IService { + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/RechargeRecordService.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/RechargeRecordService.java new file mode 100644 index 0000000..1463ee5 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/RechargeRecordService.java @@ -0,0 +1,16 @@ +package top.ysoft.admin.consume.service; + +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.consume.model.query.RechargeRecordQuery; +import top.ysoft.admin.consume.model.req.RechargeRecordReq; +import top.ysoft.admin.consume.model.resp.RechargeRecordResp; + +/** + * 充值记录业务接口 + * + * @author zc + * @since 2026/01/29 16:11 + */ +public interface RechargeRecordService extends BaseService { + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/RecordService.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/RecordService.java new file mode 100644 index 0000000..32e07c2 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/RecordService.java @@ -0,0 +1,14 @@ +package top.ysoft.admin.consume.service; + +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.consume.model.query.RecordQuery; +import top.ysoft.admin.consume.model.req.RecordReq; +import top.ysoft.admin.consume.model.resp.RecordResp; + +/** + * 消费记录业务接口 + * + * @author zc + * @since 2026/01/21 17:53 + */ +public interface RecordService extends BaseService {} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/ReportService.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/ReportService.java new file mode 100644 index 0000000..c612304 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/ReportService.java @@ -0,0 +1,77 @@ +package top.ysoft.admin.consume.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.apache.ibatis.annotations.Param; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.ysoft.admin.consume.model.query.ReportQuery; +import top.ysoft.admin.consume.model.resp.report.BranchReport; +import top.ysoft.admin.consume.model.resp.report.MealReport; +import top.ysoft.admin.consume.model.resp.report.MerchantReport; +import top.ysoft.admin.consume.model.resp.report.PeopleReport; + +import java.util.List; + +public interface ReportService { + + /** + * 查询商户报表 + * + * @param query 查询参数 + * @return 商户报表列表 + */ + List queryMerchantReport(ReportQuery query); + + /** + * 查询餐段报表 + * + * @param query 查询参数 + * @return 餐段报表列表 + */ + List queryMealReport(ReportQuery query); + + /** + * 查询部门报表 + * + * @param query 查询参数 + * @return 部门报表列表 + */ + List queryBranchReport(ReportQuery query); + + /** + * 查询人员报表 + * + * @param query 查询参数 + * @param pageQuery 分页查询参数 + * @return 人员报表分页列表 + */ + PageResp page(@Param("query") ReportQuery query, @Param("pageQuery") PageQuery pageQuery); + + /** + * 导出商户报表 + * + * @param query 查询参数 + */ + void exportMerchant(ReportQuery query, HttpServletResponse response); + + /** + * 导出餐段报表 + * + * @param query 查询参数 + */ + void exportMeal(ReportQuery query, HttpServletResponse response); + + /** + * 导出部门报表 + * + * @param query 查询参数 + */ + void exportBranch(ReportQuery query, HttpServletResponse response); + + /** + * 导出人员报表 + * + * @param query 查询参数 + */ + void exportPeople(ReportQuery query, HttpServletResponse response); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/TenantService.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/TenantService.java new file mode 100644 index 0000000..b8d5cbf --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/TenantService.java @@ -0,0 +1,16 @@ +package top.ysoft.admin.consume.service; + +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.consume.model.query.TenantQuery; +import top.ysoft.admin.consume.model.req.TenantReq; +import top.ysoft.admin.consume.model.resp.TenantResp; + +/** + * 商户信息业务接口 + * + * @author zc + * @since 2026/01/30 14:55 + */ +public interface TenantService extends BaseService { + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/TimeIntervalService.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/TimeIntervalService.java new file mode 100644 index 0000000..1610be9 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/TimeIntervalService.java @@ -0,0 +1,16 @@ +package top.ysoft.admin.consume.service; + +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.consume.model.query.TimeIntervalQuery; +import top.ysoft.admin.consume.model.req.TimeIntervalReq; +import top.ysoft.admin.consume.model.resp.TimeIntervalResp; + +/** + * 消费时段业务接口 + * + * @author zc + * @since 2026/01/30 14:56 + */ +public interface TimeIntervalService extends BaseService { + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/DownRecordServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/DownRecordServiceImpl.java new file mode 100644 index 0000000..1523e7a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/DownRecordServiceImpl.java @@ -0,0 +1,62 @@ +package top.ysoft.admin.consume.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; + +import org.springframework.stereotype.Service; + +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.consume.mapper.DownRecordMapper; +import top.ysoft.admin.consume.model.entity.DownRecordDO; +import top.ysoft.admin.consume.model.entity.RecordDO; +import top.ysoft.admin.consume.model.query.DownRecordQuery; +import top.ysoft.admin.consume.model.query.RecordQuery; +import top.ysoft.admin.consume.model.req.DownRecordReq; +import top.ysoft.admin.consume.model.resp.DownRecordResp; +import top.ysoft.admin.consume.model.resp.RecordResp; +import top.ysoft.admin.consume.service.DownRecordService; +import top.ysoft.admin.system.mapper.PeopleEquipmentMapper; +import top.ysoft.admin.system.model.entity.PeopleEquipmentDO; + +/** + * 消费下发记录业务实现 + * + * @author zc + * @since 2026/02/25 11:01 + */ +@Service +@RequiredArgsConstructor +public class DownRecordServiceImpl extends BaseServiceImpl implements DownRecordService { + + private final PeopleEquipmentMapper peopleEquipmentMapper; + + @Override + public PageResp page(DownRecordQuery query, PageQuery pageQuery) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(StrUtil.isNotBlank(query.getPeopleName()), "p.name", query.getPeopleName()); + queryWrapper.eq(null != query.getDownResult(), "r.down_result", query.getDownResult()); + queryWrapper.eq(null != query.getOperType(), "r.oper_type", query.getOperType()); + queryWrapper.eq(null != query.getEquipmentId(), "r.equipment_id", query.getEquipmentId()); + queryWrapper.ge(null != query.getStartTime(), "r.down_time", query.getStartTime()); + queryWrapper.le(null != query.getEndTime(), "r.down_time", query.getEndTime()); + this.sort(queryWrapper, pageQuery); + + IPage page = baseMapper.selectRecordsPage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + + return PageResp.build(page); + } + + + @Override + public void down(Long equipmentId, Long peopleId) { + peopleEquipmentMapper.delete(new QueryWrapper() + .eq("equipment_id", equipmentId) + .eq("people_id", peopleId)); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/GroupServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/GroupServiceImpl.java new file mode 100644 index 0000000..aa967ad --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/GroupServiceImpl.java @@ -0,0 +1,96 @@ +package top.ysoft.admin.consume.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.consume.mapper.GroupMapper; +import top.ysoft.admin.consume.model.entity.GroupDO; +import top.ysoft.admin.consume.model.entity.GroupUserDO; +import top.ysoft.admin.consume.model.query.GroupQuery; +import top.ysoft.admin.consume.model.req.GroupReq; +import top.ysoft.admin.consume.model.resp.GroupResp; +import top.ysoft.admin.consume.service.GroupService; +import top.ysoft.admin.consume.service.GroupUserService; +import top.ysoft.admin.peopleBranch.mapper.PeopleMapper; +import top.ysoft.admin.peopleBranch.mapstruct.PeopleConvert; +import top.ysoft.admin.peopleBranch.model.entity.PeopleDO; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 消费组业务实现 + * + * @author zc + * @since 2026/01/27 13:48 + */ +@Service +@RequiredArgsConstructor +public class GroupServiceImpl extends BaseServiceImpl implements GroupService { + + private final GroupUserService groupUserService; + + private final PeopleMapper peopleMapper; + + private final PeopleConvert peopleConvert; + + @Override + public GroupResp get(Long id) { + GroupDO entity = baseMapper.selectById(id); + GroupResp detail = BeanUtil.toBean(entity, GroupResp.class); + + List groupUserDOS = groupUserService.list(new LambdaQueryWrapper() + .eq(GroupUserDO::getGroupId, id)); + if (CollUtil.isNotEmpty(groupUserDOS)) { + detail.setPeopleIds(groupUserDOS.stream().map(GroupUserDO::getUserId).toList()); + + //返回人员下拉数据 + List peopleDOS = peopleMapper.selectByIds(detail.getPeopleIds()); + if (CollUtil.isNotEmpty(peopleDOS)) { + detail.setPeopleSelect(peopleConvert.labelValueList(peopleDOS)); + } + } + + this.fill(detail); + return detail; + } + + @Override + public void afterAdd(GroupReq req, GroupDO groupDO) { + if (CollUtil.isNotEmpty(req.getPeopleIds())) { + List groupUserDOS = req.getPeopleIds().stream().map(userId -> { + GroupUserDO groupUserDO = new GroupUserDO(); + groupUserDO.setGroupId(groupDO.getId()); + groupUserDO.setUserId(userId); + return groupUserDO; + }).collect(Collectors.toList()); + + groupUserService.saveBatch(groupUserDOS); + } + } + + @Override + public void afterUpdate(GroupReq req, GroupDO groupDO) { + if (CollUtil.isNotEmpty(req.getPeopleIds())) { + List groupUserDOS = req.getPeopleIds().stream().map(userId -> { + GroupUserDO groupUserDO = new GroupUserDO(); + groupUserDO.setGroupId(groupDO.getId()); + groupUserDO.setUserId(userId); + return groupUserDO; + }).collect(Collectors.toList()); + groupUserService.remove(new LambdaQueryWrapper().eq(GroupUserDO::getGroupId, groupDO.getId())); + groupUserService.saveBatch(groupUserDOS); + } + } + + @Override + public void afterDelete(List ids) { + if (CollUtil.isNotEmpty(ids)) { + groupUserService.remove(new LambdaQueryWrapper().in(GroupUserDO::getGroupId, ids)); + } + } + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/GroupUserServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/GroupUserServiceImpl.java new file mode 100644 index 0000000..0eaf928 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/GroupUserServiceImpl.java @@ -0,0 +1,20 @@ +package top.ysoft.admin.consume.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.consume.mapper.GroupUserMapper; +import top.ysoft.admin.consume.model.entity.GroupUserDO; +import top.ysoft.admin.consume.service.GroupUserService; + +/** + * 消费组用户业务实现 + * + * @author zc + * @since 2026/01/27 13:48 + */ +@Service +@RequiredArgsConstructor +public class GroupUserServiceImpl extends ServiceImpl implements GroupUserService { + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/RechargeRecordServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/RechargeRecordServiceImpl.java new file mode 100644 index 0000000..83b7ca8 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/RechargeRecordServiceImpl.java @@ -0,0 +1,72 @@ +package top.ysoft.admin.consume.service.impl; + +import cn.crane4j.annotation.AutoOperate; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.query.SortQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.continew.starter.file.excel.util.ExcelUtils; +import top.ysoft.admin.consume.mapper.RechargeRecordMapper; +import top.ysoft.admin.consume.model.entity.RechargeRecordDO; +import top.ysoft.admin.consume.model.query.RechargeRecordQuery; +import top.ysoft.admin.consume.model.req.RechargeRecordReq; +import top.ysoft.admin.consume.model.resp.RechargeRecordResp; +import top.ysoft.admin.consume.service.RechargeRecordService; + +import java.util.List; + +/** + * 充值记录业务实现 + * + * @author zc + * @since 2026/01/29 16:11 + */ +@Service +@RequiredArgsConstructor +public class RechargeRecordServiceImpl extends BaseServiceImpl implements RechargeRecordService { + + @Override + @AutoOperate(type = RechargeRecordResp.class, on = "list") + public PageResp page(RechargeRecordQuery query, PageQuery pageQuery) { + QueryWrapper queryWrapper = buildQueryWrapper(query); + this.sort(queryWrapper, pageQuery); + + IPage page = baseMapper.selectRechargePage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + + return PageResp.build(page); + } + + @Override + public void export(RechargeRecordQuery query, SortQuery sortQuery, HttpServletResponse response) { + QueryWrapper queryWrapper = buildQueryWrapper(query); + this.sort(queryWrapper, sortQuery); + List rechargeRecordResps = baseMapper.selectExport(queryWrapper); + + ExcelUtils.export(rechargeRecordResps, "充值记录_" + DateUtil.today(), RechargeRecordResp.class, response); + } + + public QueryWrapper buildQueryWrapper(RechargeRecordQuery query) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + //根据人员名称查询 + queryWrapper.like(StrUtil.isNotBlank(query.getAccountName()), "p.name", query.getAccountName()); + //根据工号查询 + queryWrapper.like(StrUtil.isNotBlank(query.getEmpId()), "r.emp_id", query.getEmpId()); + //根据充值类型查询 + queryWrapper.eq(query.getRechargeType() != null, "r.recharge_type", query.getRechargeType()); + //根据开始时间查询 + queryWrapper.ge(query.getStartTime() != null, "r.create_time", query.getStartTime()); + //根据结束时间查询 + queryWrapper.le(query.getEndTime() != null, "r.create_time", query.getEndTime()); + return queryWrapper; + } + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/RecordServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/RecordServiceImpl.java new file mode 100644 index 0000000..afc0489 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/RecordServiceImpl.java @@ -0,0 +1,76 @@ +package top.ysoft.admin.consume.service.impl; + +import cn.crane4j.annotation.AutoOperate; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +import org.springframework.stereotype.Service; + +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.query.SortQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.continew.starter.file.excel.util.ExcelUtils; +import top.ysoft.admin.consume.mapper.RecordMapper; +import top.ysoft.admin.consume.model.entity.RecordDO; +import top.ysoft.admin.consume.model.query.RecordQuery; +import top.ysoft.admin.consume.model.req.RecordReq; +import top.ysoft.admin.consume.model.resp.RecordResp; +import top.ysoft.admin.consume.service.RecordService; +import top.ysoft.admin.peopleBranch.mapper.PeopleMapper; + +import java.util.List; + +/** + * 消费记录业务实现 + * + * @author zc + * @since 2026/01/21 17:53 + */ +@Service +@RequiredArgsConstructor +public class RecordServiceImpl extends BaseServiceImpl implements RecordService { + + @Override + public PageResp page(RecordQuery query, PageQuery pageQuery) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(StrUtil.isNotBlank(query.getEmpId()), "r.emp_id", query.getEmpId()); + queryWrapper.eq(StrUtil.isNotBlank(query.getOutTradeId()), "r.out_trade_id", query.getOutTradeId()); + queryWrapper.eq(null != query.getPayMode(), "r.pay_mode", query.getPayMode()); + queryWrapper.eq(null != query.getResultCode(), "r.result_code", query.getResultCode()); + queryWrapper.ge(null != query.getStartDate(), "r.start_date", query.getStartDate()); + queryWrapper.le(null != query.getEndDate(), "r.end_date", query.getEndDate()); + queryWrapper.le(null != query.getDevId(), "r.dev_id", query.getDevId()); + queryWrapper.like(StrUtil.isNotBlank(query.getAccountName()), "p.name", query.getAccountName()); + this.sort(queryWrapper, pageQuery); + + IPage page = baseMapper.selectRecordsPage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + + return PageResp.build(page); + } + + @Override + public void export(RecordQuery query, SortQuery sortQuery, HttpServletResponse response) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(StrUtil.isNotBlank(query.getEmpId()), "r.emp_id", query.getEmpId()); + queryWrapper.eq(StrUtil.isNotBlank(query.getOutTradeId()), "r.out_trade_id", query.getOutTradeId()); + queryWrapper.eq(null != query.getPayMode(), "r.pay_mode", query.getPayMode()); + queryWrapper.eq(null != query.getResultCode(), "r.result_code", query.getResultCode()); + queryWrapper.ge(null != query.getStartDate(), "r.start_date", query.getStartDate()); + queryWrapper.le(null != query.getEndDate(), "r.end_date", query.getEndDate()); + queryWrapper.le(null != query.getDevId(), "r.dev_id", query.getDevId()); + queryWrapper.like(StrUtil.isNotBlank(query.getAccountName()), "p.name", query.getAccountName()); + this.sort(queryWrapper, sortQuery); + + List list = baseMapper.selectRecordsExport(queryWrapper); + + ExcelUtils.export(list, "消费记录_" + DateUtil.today(), this.getDetailClass(), response); + } + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/ReportServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/ReportServiceImpl.java new file mode 100644 index 0000000..6edd689 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/ReportServiceImpl.java @@ -0,0 +1,129 @@ +package top.ysoft.admin.consume.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.file.excel.util.ExcelUtils; +import top.ysoft.admin.consume.mapper.ReportMapper; +import top.ysoft.admin.consume.model.query.ReportQuery; +import top.ysoft.admin.consume.model.resp.report.*; +import top.ysoft.admin.consume.service.ReportService; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ReportServiceImpl implements ReportService { + + private final ReportMapper reportMapper; + + /** + * 查询商户报表 + * + * @param query 查询参数 + * @return 商户报表列表 + */ + @Override + public List queryMerchantReport(ReportQuery query) { + List list = reportMapper.queryMerchantReport(query); + BaseReport totalReport = reportMapper.totalReport(query); + MerchantReport merchantReport = new MerchantReport(); + merchantReport.setMerchant("总价"); + BeanUtil.copyProperties(totalReport, merchantReport); + list.add(merchantReport); + return list; + } + + /** + * 查询餐段报表 + * + * @param query 查询参数 + * @return 餐段报表列表 + */ + @Override + public List queryMealReport(ReportQuery query) { + List list = reportMapper.queryMealReport(query); + BaseReport totalReport = reportMapper.totalReport(query); + MealReport mealReport = new MealReport(); + mealReport.setMerchant("总价"); + BeanUtil.copyProperties(totalReport, mealReport); + list.add(mealReport); + return list; + } + + /** + * 查询部门报表 + * + * @param query 查询参数 + * @return 部门报表列表 + */ + @Override + public List queryBranchReport(ReportQuery query) { + List list = reportMapper.queryBranchReport(query); + BaseReport totalReport = reportMapper.totalReport(query); + BranchReport branchReport = new BranchReport(); + branchReport.setBranchName("总价"); + BeanUtil.copyProperties(totalReport, branchReport); + list.add(branchReport); + return list; + } + + /** + * 查询人员报表 + * + * @param query 查询参数 + * @param pageQuery 分页查询参数 + * @return 人员报表分页列表 + */ + @Override + public PageResp page(ReportQuery query, PageQuery pageQuery) { + int offset = (pageQuery.getPage() - 1) * pageQuery.getSize(); + List list = reportMapper.queryPeopleReport(query, pageQuery.getSize(), offset); + long total = reportMapper.selectCount(query); + return new PageResp<>(list, total); + } + + @Override + public void exportMerchant(ReportQuery query, HttpServletResponse response) { + List list = reportMapper.queryMerchantReport(query); + BaseReport totalReport = reportMapper.totalReport(query); + MerchantReport merchantReport = new MerchantReport(); + merchantReport.setMerchant("总价"); + BeanUtil.copyProperties(totalReport, merchantReport); + list.add(merchantReport); + ExcelUtils.export(list, "商户营业报表_" + DateUtil.today(), MerchantReport.class, response); + } + + @Override + public void exportMeal(ReportQuery query, HttpServletResponse response) { + List list = reportMapper.queryMealReport(query); + BaseReport totalReport = reportMapper.totalReport(query); + MealReport mealReport = new MealReport(); + mealReport.setMerchant("总价"); + BeanUtil.copyProperties(totalReport, mealReport); + list.add(mealReport); + ExcelUtils.export(list, "餐段消费报表_" + DateUtil.today(), MealReport.class, response); + } + + @Override + public void exportBranch(ReportQuery query, HttpServletResponse response) { + List list = reportMapper.queryBranchReport(query); + BaseReport totalReport = reportMapper.totalReport(query); + BranchReport branchReport = new BranchReport(); + branchReport.setBranchName("总价"); + BeanUtil.copyProperties(totalReport, branchReport); + list.add(branchReport); + ExcelUtils.export(list, "部门消费报表_" + DateUtil.today(), BranchReport.class, response); + } + + @Override + public void exportPeople(ReportQuery query, HttpServletResponse response) { + List list = reportMapper.queryPeopleExport(query); + ExcelUtils.export(list, "个人消费报表_" + DateUtil.today(), PeopleReport.class, response); + } + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/TenantServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/TenantServiceImpl.java new file mode 100644 index 0000000..5b00365 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/TenantServiceImpl.java @@ -0,0 +1,25 @@ +package top.ysoft.admin.consume.service.impl; + +import lombok.RequiredArgsConstructor; + +import org.springframework.stereotype.Service; + +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.consume.mapper.TenantMapper; +import top.ysoft.admin.consume.model.entity.TenantDO; +import top.ysoft.admin.consume.model.query.TenantQuery; +import top.ysoft.admin.consume.model.req.TenantReq; +import top.ysoft.admin.consume.model.resp.TenantResp; +import top.ysoft.admin.consume.service.TenantService; + +/** + * 商户信息业务实现 + * + * @author zc + * @since 2026/01/30 14:55 + */ +@Service +@RequiredArgsConstructor +public class TenantServiceImpl extends BaseServiceImpl implements TenantService { + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/TimeIntervalServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/TimeIntervalServiceImpl.java new file mode 100644 index 0000000..c7dd056 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/consume/service/impl/TimeIntervalServiceImpl.java @@ -0,0 +1,25 @@ +package top.ysoft.admin.consume.service.impl; + +import lombok.RequiredArgsConstructor; + +import org.springframework.stereotype.Service; + +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.consume.mapper.TimeIntervalMapper; +import top.ysoft.admin.consume.model.entity.TimeIntervalDO; +import top.ysoft.admin.consume.model.query.TimeIntervalQuery; +import top.ysoft.admin.consume.model.req.TimeIntervalReq; +import top.ysoft.admin.consume.model.resp.TimeIntervalResp; +import top.ysoft.admin.consume.service.TimeIntervalService; + +/** + * 消费时段业务实现 + * + * @author zc + * @since 2026/01/30 14:56 + */ +@Service +@RequiredArgsConstructor +public class TimeIntervalServiceImpl extends BaseServiceImpl implements TimeIntervalService { + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/mapper/EquipmentMapper.java b/wms-module-system/src/main/java/top/wms/admin/equipment/mapper/EquipmentMapper.java new file mode 100644 index 0000000..f8babfd --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/mapper/EquipmentMapper.java @@ -0,0 +1,16 @@ +package top.ysoft.admin.equipment.mapper; + +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.equipment.model.entity.EquipmentDO; + +/** + * 设备信息 Mapper + * + * @author zc + * @since 2025/03/26 17:14 + */ +@Repository +public interface EquipmentMapper extends BaseMapper { + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/mapper/ProductMapper.java b/wms-module-system/src/main/java/top/wms/admin/equipment/mapper/ProductMapper.java new file mode 100644 index 0000000..b0c0292 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/mapper/ProductMapper.java @@ -0,0 +1,12 @@ +package top.ysoft.admin.equipment.mapper; + +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.equipment.model.entity.ProductDO; + +/** + * 产品信息 Mapper + * + * @author zc + * @since 2025/05/26 15:18 + */ +public interface ProductMapper extends BaseMapper {} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/mapstruct/EquipmentConvert.java b/wms-module-system/src/main/java/top/wms/admin/equipment/mapstruct/EquipmentConvert.java new file mode 100644 index 0000000..e10b7ef --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/mapstruct/EquipmentConvert.java @@ -0,0 +1,24 @@ +package top.ysoft.admin.equipment.mapstruct; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.ysoft.admin.equipment.model.entity.EquipmentDO; +import top.ysoft.admin.equipment.model.req.EquipmentReq; +import top.ysoft.admin.equipment.model.resp.EquipmentResp; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface EquipmentConvert { + + EquipmentResp DOToResp(EquipmentDO equipmentDO); + + EquipmentReq RespToReq(EquipmentResp equipmentDO); + + @Mapping(source = "id", target = "value") + @Mapping(source = "name", target = "label") + LabelValueResp labelValue(EquipmentDO equipmentDOS); + + List labelValueList(List equipmentDOS); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/mapstruct/ProductConvert.java b/wms-module-system/src/main/java/top/wms/admin/equipment/mapstruct/ProductConvert.java new file mode 100644 index 0000000..287502a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/mapstruct/ProductConvert.java @@ -0,0 +1,20 @@ +package top.ysoft.admin.equipment.mapstruct; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.ysoft.admin.equipment.model.entity.ProductDO; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface ProductConvert { + + @Mapping(source = "id", target = "value") + @Mapping(source = "name", target = "label") + @Mapping(source = "avatar", target = "extra") + LabelValueResp labelValue(ProductDO productDO); + + List labelValueList(List productDOS); + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/model/entity/EquipmentDO.java b/wms-module-system/src/main/java/top/wms/admin/equipment/model/entity/EquipmentDO.java new file mode 100644 index 0000000..3a6aa64 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/model/entity/EquipmentDO.java @@ -0,0 +1,91 @@ +package top.ysoft.admin.equipment.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 设备信息实体 + * + * @author zc + * @since 2025/03/26 17:10 + */ +@Data +@TableName("sys_equipment") +public class EquipmentDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 所属产品Id + */ + private Long productId; + + /** + * 设备名称 + */ + private String name; + + /** + * 设备序列号 + */ + private String sequence; + + /** + * 设备Ip + */ + private String ip; + + /** + * 设备密码 + */ + private String password; + + /** + * 设备区域 + */ + private Long spaceId; + + /** + * 设备位置 + */ + private Long pointId; + + /** + * 备注 + */ + private String remark; + + /** + * 对接状态(0未对接 1对接成功) + */ + private Integer state; + + /** + * 设备状态(0在线 1离线) + */ + private String flag; + + /** + * 设备回调地址 + */ + private String backUrl; + + /** + * 是否采集设备 + */ + private String isCj; + + /** + * 门禁位置 1.进;2.出 + */ + private String entryExitType; + + /** + * 文件类型 1.图片;2.视频 + */ + private String fileType; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/model/entity/ProductDO.java b/wms-module-system/src/main/java/top/wms/admin/equipment/model/entity/ProductDO.java new file mode 100644 index 0000000..21278cb --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/model/entity/ProductDO.java @@ -0,0 +1,78 @@ +package top.ysoft.admin.equipment.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 产品信息实体 + * + * @author zc + * @since 2025/05/26 15:18 + */ +@Data +@TableName("sys_product") +public class ProductDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 产品图片地址 + */ + private String avatar; + + /** + * 产品名称 + */ + private String name; + + /** + * 产品型号 + */ + private String version; + + /** + * 产品品类(大类) + */ + private String bigtype; + + /** + * 产品品类(小类) + */ + private String subtype; + + /** + * 节点类型 + */ + private String genre; + + /** + * 产品描述 + */ + private String describes; + + /** + * 联网方式 + */ + private String network; + + /** + * 数据格式 + */ + private String dataFormat; + + /** + * 数据校验级别 + */ + private String scale; + + /** + * 认证方式 + */ + private String attestation; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/model/query/EquipmentQuery.java b/wms-module-system/src/main/java/top/wms/admin/equipment/model/query/EquipmentQuery.java new file mode 100644 index 0000000..3401ba7 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/model/query/EquipmentQuery.java @@ -0,0 +1,53 @@ +package top.ysoft.admin.equipment.model.query; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设备信息查询条件 + * + * @author zc + * @since 2025/03/26 17:14 + */ +@Data +@Schema(description = "设备信息查询条件") +public class EquipmentQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + @Query(type = QueryType.LIKE) + private String name; + + /** + * 设备序列号 + */ + @Schema(description = "设备序列号") + @Query(type = QueryType.EQ) + private String sequence; + + /** + * 所属产品 + */ + @Schema(description = "所属产品") + @Query(type = QueryType.EQ) + private Long productId; + + /** + * 设备状态(0在线 1离线) + */ + @Schema(description = "设备状态(0在线 1离线)") + @Query(type = QueryType.EQ) + private String flag; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/model/query/ProductQuery.java b/wms-module-system/src/main/java/top/wms/admin/equipment/model/query/ProductQuery.java new file mode 100644 index 0000000..580996f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/model/query/ProductQuery.java @@ -0,0 +1,47 @@ +package top.ysoft.admin.equipment.model.query; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 产品信息查询条件 + * + * @author zc + * @since 2025/05/26 15:18 + */ +@Data +@Schema(description = "产品信息查询条件") +public class ProductQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 产品名称 + */ + @Schema(description = "产品名称") + @Query(type = QueryType.LIKE) + private String name; + + /** + * 产品名称 + */ + @Schema(description = "产品型号") + @Query(type = QueryType.LIKE) + private String version; + + /** + * 产品名称 + */ + @Schema(description = "节点类型") + @Query(type = QueryType.EQ) + private String genre; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/model/req/EquipmentReq.java b/wms-module-system/src/main/java/top/wms/admin/equipment/model/req/EquipmentReq.java new file mode 100644 index 0000000..a31c7ba --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/model/req/EquipmentReq.java @@ -0,0 +1,142 @@ +package top.ysoft.admin.equipment.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 创建或修改设备信息参数 + * + * @author zc + * @since 2025/03/26 16:40 + */ +@Data +@Schema(description = "创建或修改设备信息参数") +public class EquipmentReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备Id + */ + @Schema(description = "设备Id") + private Long id; + + /** + * 所属产品Id + */ + @Schema(description = "所属产品Id") + private Long productId; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + @NotBlank(message = "设备名称不能为空") + @Length(max = 50, message = "设备名称长度不能超过 {max} 个字符") + private String name; + + /** + * 设备序列号 + */ + @Schema(description = "设备序列号") + @Length(max = 50, message = "设备序列号长度不能超过 {max} 个字符") + private String sequence; + + /** + * 设备Ip + */ + @Schema(description = "设备Ip") + @Length(max = 50, message = "设备Ip长度不能超过 {max} 个字符") + private String ip; + + /** + * 设备密码 + */ + @Schema(description = "设备密码") + @Length(max = 50, message = "设备密码长度不能超过 {max} 个字符") + private String password; + + /** + * 设备区域 + */ + @Schema(description = "设备区域") + private Long spaceId; + + /** + * 设备位置 + */ + @Schema(description = "设备位置") + private Long pointId; + + /** + * 备注 + */ + @Schema(description = "备注") + @Length(max = 200, message = "备注长度不能超过 {max} 个字符") + private String remark; + + /** + * + */ + @Schema(description = "") + private Long createUser; + + /** + * 创建时间 + */ + @Schema(description = "创建时间") + private LocalDateTime createTime; + + /** + * + */ + @Schema(description = "") + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + /** + * 对接状态(0未对接 1对接成功) + */ + @Schema(description = "对接状态(0未对接 1对接成功)") + private Integer state; + + /** + * 设备状态(0在线 1离线) + */ + @Schema(description = "设备状态(0在线 1离线)") + @Length(max = 50, message = "设备状态(0在线 1离线)长度不能超过 {max} 个字符") + private String flag; + + /*** 设备回调地址 ***/ + private String backUrl; + + /** + * 是否采集设备 + */ + @Schema(description = "是否采集设备") + private String isCj; + + /** + * 门卡号码 + */ + private String doorNo; + + /** + * 门禁位置 1.进;2.出 + */ + @Schema(description = "门禁位置 1.进;2.出") + private String entryExitType; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/model/req/ProductReq.java b/wms-module-system/src/main/java/top/wms/admin/equipment/model/req/ProductReq.java new file mode 100644 index 0000000..33e559f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/model/req/ProductReq.java @@ -0,0 +1,105 @@ +package top.ysoft.admin.equipment.model.req; + +import jakarta.validation.constraints.*; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 创建或修改产品信息参数 + * + * @author zc + * @since 2025/05/26 15:18 + */ +@Data +@Schema(description = "创建或修改产品信息参数") +public class ProductReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 产品图片地址 + */ + @Schema(description = "产品图片地址") + @Length(max = 500, message = "产品图片地址长度不能超过 {max} 个字符") + private String avatar; + + /** + * 产品名称 + */ + @Schema(description = "产品名称") + @NotBlank(message = "产品名称不能为空") + @Length(max = 50, message = "产品名称长度不能超过 {max} 个字符") + private String name; + + /** + * 产品型号 + */ + @Schema(description = "产品型号") + @Length(max = 500, message = "产品型号长度不能超过 {max} 个字符") + private String version; + + /** + * 产品品类(大类) + */ + @Schema(description = "产品品类(大类)") + @Length(max = 1, message = "产品品类(大类)长度不能超过 {max} 个字符") + private String bigtype; + + /** + * 产品品类(小类) + */ + @Schema(description = "产品品类(小类)") + @Length(max = 1, message = "产品品类(小类)长度不能超过 {max} 个字符") + private String subtype; + + /** + * 节点类型 + */ + @Schema(description = "节点类型") + @Length(max = 1, message = "节点类型长度不能超过 {max} 个字符") + private String genre; + + /** + * 产品描述 + */ + @Schema(description = "产品描述") + @Length(max = 200, message = "产品描述长度不能超过 {max} 个字符") + private String describes; + + /** + * 联网方式 + */ + @Schema(description = "联网方式") + @Length(max = 1, message = "联网方式长度不能超过 {max} 个字符") + private String network; + + /** + * 数据格式 + */ + @Schema(description = "数据格式") + @Length(max = 50, message = "数据格式长度不能超过 {max} 个字符") + private String dataFormat; + + /** + * 数据校验级别 + */ + @Schema(description = "数据校验级别") + @Length(max = 50, message = "数据校验级别长度不能超过 {max} 个字符") + private String scale; + + /** + * 认证方式 + */ + @Schema(description = "认证方式") + @Length(max = 1, message = "认证方式长度不能超过 {max} 个字符") + private String attestation; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/EquipmentDetailResp.java b/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/EquipmentDetailResp.java new file mode 100644 index 0000000..a35b2ea --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/EquipmentDetailResp.java @@ -0,0 +1,125 @@ +package top.ysoft.admin.equipment.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; + +/** + * 设备信息详情信息 + * + * @author zc + * @since 2025/03/26 17:11 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "设备信息详情信息") +public class EquipmentDetailResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 所属产品Id + */ + @Schema(description = "所属产品Id") + @ExcelProperty(value = "所属产品Id") + private Long productId; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + @ExcelProperty(value = "设备名称") + private String name; + + /** + * 设备序列号 + */ + @Schema(description = "设备序列号") + @ExcelProperty(value = "设备序列号") + private String sequence; + + /** + * 设备Ip + */ + @Schema(description = "设备Ip") + @ExcelProperty(value = "设备Ip") + private String ip; + + /** + * 设备密码 + */ + @Schema(description = "设备密码") + @ExcelProperty(value = "设备密码") + private String password; + + /** + * 设备区域 + */ + @Schema(description = "设备区域") + @ExcelProperty(value = "设备区域") + private Long spaceId; + + /** + * 设备位置 + */ + @Schema(description = "设备位置") + @ExcelProperty(value = "设备位置") + private Long pointId; + + /** + * 备注 + */ + @Schema(description = "备注") + @ExcelProperty(value = "备注") + private String remark; + + /** + * 对接状态(0未对接 1对接成功) + */ + @Schema(description = "对接状态(0未对接 1对接成功)") + @ExcelProperty(value = "对接状态(0未对接 1对接成功)") + private Integer state; + + /** + * 设备状态(0在线 1离线) + */ + @Schema(description = "设备状态(0在线 1离线)") + @ExcelProperty(value = "设备状态(0在线 1离线)") + private String flag; + + /** + * 设备回调地址 + */ + @Schema(description = "设备回调地址") + @ExcelProperty(value = "设备回调地址") + private String backUrl; + + /** + * 是否采集设备 + */ + @Schema(description = "是否采集设备") + @ExcelProperty(value = "是否采集设备") + private String isCj; + + /** + * 门禁位置 1.进;2.出 + */ + @Schema(description = "门禁位置 1.进;2.出") + @ExcelProperty(value = "门禁位置 1.进;2.出") + private String entryExitType; + + /** + * 文件类型 1.图片;2.视频 + */ + @Schema(description = "文件类型 1.图片;2.视频") + @ExcelProperty(value = "文件类型 1.图片;2.视频") + private String fileType; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/EquipmentResp.java b/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/EquipmentResp.java new file mode 100644 index 0000000..6c48d2b --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/EquipmentResp.java @@ -0,0 +1,141 @@ +package top.ysoft.admin.equipment.model.resp; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.annotation.Mapping; +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.common.model.resp.BaseResp; + +import java.io.Serial; +import java.time.*; + +/** + * 设备信息信息 + * + * @author zc + * @since 2025/03/26 17:11 + */ +@Data +@Schema(description = "设备信息信息") +public class EquipmentResp extends BaseResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 所属产品Id + */ + @Schema(description = "所属产品Id") + @Assemble(props = @Mapping(src = "name", ref = "productName"), container = ContainerConstants.PRODUCT_NAME_AVATAR) + @Assemble(props = @Mapping(src = "avatar", ref = "avatar"), container = ContainerConstants.PRODUCT_NAME_AVATAR) + private Long productId; + + /** + * 产品名称 + */ + @Schema(description = "产品名称") + private String productName; + + /** + * 产品图片 + */ + @Schema(description = "产品图片") + private String avatar; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + private String name; + + /** + * 设备序列号 + */ + @Schema(description = "设备序列号") + private String sequence; + + /** + * 设备Ip + */ + @Schema(description = "设备Ip") + private String ip; + + /** + * 设备密码 + */ + @Schema(description = "设备密码") + private String password; + + /** + * 设备区域 + */ + @Schema(description = "设备区域") + private Long spaceId; + + /** + * 设备位置 + */ + @Schema(description = "设备位置") + private Long pointId; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; + + /** + * + */ + @Schema(description = "") + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + /** + * 对接状态(0未对接 1对接成功) + */ + @Schema(description = "对接状态(0未对接 1对接成功)") + private Integer state; + + /** + * 设备状态(0在线 1离线) + */ + @Schema(description = "设备状态(0在线 1离线)") + private String flag; + + /** + * 设备回调地址 + */ + @Schema(description = "设备回调地址") + private String backUrl; + + /** + * 是否采集设备 + */ + @Schema(description = "是否采集设备") + private String isCj; + + /** + * 门禁位置 1.进;2.出 + */ + @Schema(description = "门禁位置 1.进;2.出") + private String entryExitType; + + /** + * 文件类型 1.图片;2.视频 + */ + @Schema(description = "文件类型 1.图片;2.视频") + private String fileType; + + /** 门卡号码 */ + @Schema(name = "卡号") + private String doorNo; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/ProductDetailResp.java b/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/ProductDetailResp.java new file mode 100644 index 0000000..bf837bf --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/ProductDetailResp.java @@ -0,0 +1,105 @@ +package top.ysoft.admin.equipment.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; + +/** + * 产品信息详情信息 + * + * @author zc + * @since 2025/05/26 15:18 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "产品信息详情信息") +public class ProductDetailResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 产品图片地址 + */ + @Schema(description = "产品图片地址") + @ExcelProperty(value = "产品图片地址") + private String avatar; + + /** + * 产品名称 + */ + @Schema(description = "产品名称") + @ExcelProperty(value = "产品名称") + private String name; + + /** + * 产品型号 + */ + @Schema(description = "产品型号") + @ExcelProperty(value = "产品型号") + private String version; + + /** + * 产品品类(大类) + */ + @Schema(description = "产品品类(大类)") + @ExcelProperty(value = "产品品类(大类)") + private String bigtype; + + /** + * 产品品类(小类) + */ + @Schema(description = "产品品类(小类)") + @ExcelProperty(value = "产品品类(小类)") + private String subtype; + + /** + * 节点类型 + */ + @Schema(description = "节点类型") + @ExcelProperty(value = "节点类型") + private String genre; + + /** + * 产品描述 + */ + @Schema(description = "产品描述") + @ExcelProperty(value = "产品描述") + private String describes; + + /** + * 联网方式 + */ + @Schema(description = "联网方式") + @ExcelProperty(value = "联网方式") + private String network; + + /** + * 数据格式 + */ + @Schema(description = "数据格式") + @ExcelProperty(value = "数据格式") + private String dataFormat; + + /** + * 数据校验级别 + */ + @Schema(description = "数据校验级别") + @ExcelProperty(value = "数据校验级别") + private String scale; + + /** + * 认证方式 + */ + @Schema(description = "认证方式") + @ExcelProperty(value = "认证方式") + private String attestation; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/ProductResp.java b/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/ProductResp.java new file mode 100644 index 0000000..d708157 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/model/resp/ProductResp.java @@ -0,0 +1,102 @@ +package top.ysoft.admin.equipment.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; + +/** + * 产品信息信息 + * + * @author zc + * @since 2025/05/26 15:18 + */ +@Data +@Schema(description = "产品信息信息") +public class ProductResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 产品图片地址 + */ + @Schema(description = "产品图片地址") + private String avatar; + + /** + * 产品名称 + */ + @Schema(description = "产品名称") + private String name; + + /** + * 产品型号 + */ + @Schema(description = "产品型号") + private String version; + + /** + * 产品品类(大类) + */ + @Schema(description = "产品品类(大类)") + private String bigtype; + + /** + * 产品品类(小类) + */ + @Schema(description = "产品品类(小类)") + private String subtype; + + /** + * 节点类型 + */ + @Schema(description = "节点类型") + private String genre; + + /** + * 产品描述 + */ + @Schema(description = "产品描述") + private String describes; + + /** + * 联网方式 + */ + @Schema(description = "联网方式") + private String network; + + /** + * 数据格式 + */ + @Schema(description = "数据格式") + private String dataFormat; + + /** + * 数据校验级别 + */ + @Schema(description = "数据校验级别") + private String scale; + + /** + * 认证方式 + */ + @Schema(description = "认证方式") + private String attestation; + + /** + * 更新者 + */ + @Schema(description = "更新者") + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/service/EquipmentService.java b/wms-module-system/src/main/java/top/wms/admin/equipment/service/EquipmentService.java new file mode 100644 index 0000000..bd99c29 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/service/EquipmentService.java @@ -0,0 +1,27 @@ +package top.ysoft.admin.equipment.service; + +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.equipment.model.query.EquipmentQuery; +import top.ysoft.admin.equipment.model.req.EquipmentReq; +import top.ysoft.admin.equipment.model.resp.EquipmentDetailResp; +import top.ysoft.admin.equipment.model.resp.EquipmentResp; + +import java.util.List; + +/** + * 设备信息业务接口 + * + * @author zc + * @since 2025/03/26 17:14 + */ +public interface EquipmentService extends BaseService { + + EquipmentResp selectSysEquipment(EquipmentReq equipment); + + List getEquipmentNameList(EquipmentReq equipmentReq); + + EquipmentResp selectEquipmentById(Long id); + + String getEquipmentName(Long id); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/service/ProductService.java b/wms-module-system/src/main/java/top/wms/admin/equipment/service/ProductService.java new file mode 100644 index 0000000..ecffd26 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/service/ProductService.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.equipment.service; + +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.equipment.model.entity.ProductDO; +import top.ysoft.admin.equipment.model.query.ProductQuery; +import top.ysoft.admin.equipment.model.req.ProductReq; +import top.ysoft.admin.equipment.model.resp.ProductResp; + +import java.util.List; + +/** + * 产品信息业务接口 + * + * @author zc + * @since 2025/05/26 15:18 + */ +public interface ProductService extends BaseService { + + List getProductNameAvatarList(); + + List getProductNameList(); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/service/impl/EquipmentServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/equipment/service/impl/EquipmentServiceImpl.java new file mode 100644 index 0000000..75d19b7 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/service/impl/EquipmentServiceImpl.java @@ -0,0 +1,119 @@ +package top.ysoft.admin.equipment.service.impl; + +import cn.crane4j.annotation.AutoOperate; +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import top.continew.starter.data.mp.util.QueryWrapperHelper; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.equipment.mapper.EquipmentMapper; +import top.ysoft.admin.equipment.mapstruct.EquipmentConvert; +import top.ysoft.admin.equipment.model.entity.EquipmentDO; +import top.ysoft.admin.equipment.model.query.EquipmentQuery; +import top.ysoft.admin.equipment.model.req.EquipmentReq; +import top.ysoft.admin.equipment.model.resp.EquipmentDetailResp; +import top.ysoft.admin.equipment.model.resp.EquipmentResp; +import top.ysoft.admin.equipment.service.EquipmentService; +import top.ysoft.admin.yfApi.service.IYFPushService; + +import java.util.List; + +/** + * 设备信息业务实现 + * + * @author zc + * @since 2025/03/26 17:14 + */ +@Service +@RequiredArgsConstructor +public class EquipmentServiceImpl extends BaseServiceImpl implements EquipmentService { + + private final EquipmentConvert equipmentConvert; + + private final IYFPushService iyfPushService; + + @Override + @AutoOperate(type = EquipmentResp.class, on = "list") + public PageResp page(EquipmentQuery query, PageQuery pageQuery) { + QueryWrapper queryWrapper = QueryWrapperHelper.build(query, pageQuery.getSort()); + IPage page = baseMapper.selectPage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + return PageResp.build(page.convert(equipmentConvert::DOToResp)); + } + + @Override + public EquipmentResp selectSysEquipment(EquipmentReq equipment) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(ObjectUtil.isNotEmpty(equipment.getName()), EquipmentDO::getName, equipment.getName()); + queryWrapper.eq(null != equipment.getPointId(), EquipmentDO::getPointId, equipment.getPointId()); + queryWrapper.eq(StrUtil.isNotBlank(equipment.getFlag()), EquipmentDO::getFlag, equipment.getFlag()); + queryWrapper.eq(StrUtil.isNotBlank(equipment.getIsCj()), EquipmentDO::getIsCj, equipment.getIsCj()); + queryWrapper.eq(null != equipment.getProductId(), EquipmentDO::getProductId, equipment.getProductId()); + return equipmentConvert.DOToResp(baseMapper.selectOne(queryWrapper)); + } + + @Override + public List getEquipmentNameList(EquipmentReq equipmentReq) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(null != equipmentReq.getPointId(), EquipmentDO::getPointId, equipmentReq.getPointId()); + queryWrapper.eq(StrUtil.isNotBlank(equipmentReq.getFlag()), EquipmentDO::getFlag, equipmentReq.getFlag()); + queryWrapper.eq(StrUtil.isNotBlank(equipmentReq.getIsCj()), EquipmentDO::getIsCj, equipmentReq.getIsCj()); + queryWrapper.eq(null != equipmentReq.getProductId(), EquipmentDO::getProductId, equipmentReq.getProductId()); + queryWrapper.orderByDesc(EquipmentDO::getId); + List equipmentDOS = baseMapper.selectList(queryWrapper); + return equipmentConvert.labelValueList(equipmentDOS); + } + + @Override + public EquipmentResp selectEquipmentById(Long id) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(null != id, EquipmentDO::getId, id); + return equipmentConvert.DOToResp(baseMapper.selectOne(queryWrapper)); + } + + @Override + public void afterAdd(EquipmentReq equipmentReq, EquipmentDO equipmentDO) { + if (StringUtils.isNotEmpty(equipmentReq.getBackUrl())) { + iyfPushService.setIdentifyCallBack(equipmentReq.getBackUrl(), equipmentDO.getIp(), equipmentDO + .getPassword()); + iyfPushService.setDeviceHeartBeat(equipmentReq.getBackUrl(), equipmentDO.getIp(), equipmentDO + .getPassword()); + } + } + + @Override + public void afterUpdate(EquipmentReq equipmentReq, EquipmentDO equipmentDO) { + if (StringUtils.isNotEmpty(equipmentReq.getBackUrl())) { + iyfPushService.setIdentifyCallBack(equipmentReq.getBackUrl(), equipmentDO.getIp(), equipmentDO + .getPassword()); + iyfPushService.setDeviceHeartBeat(equipmentReq.getBackUrl(), equipmentDO.getIp(), equipmentDO + .getPassword()); + } + } + + @Override + @ContainerMethod(namespace = ContainerConstants.EQUIPMENT_NAME, type = MappingType.ORDER_OF_KEYS) + public String getEquipmentName(Long id) { + if (null == id) { + return ""; + } + + EquipmentDO equipmentDO = baseMapper.lambdaQuery().eq(EquipmentDO::getId, id).one(); + return equipmentDO != null ? equipmentDO.getName() : ""; + } + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/equipment/service/impl/ProductServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/equipment/service/impl/ProductServiceImpl.java new file mode 100644 index 0000000..578cf5e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/equipment/service/impl/ProductServiceImpl.java @@ -0,0 +1,47 @@ +package top.ysoft.admin.equipment.service.impl; + +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.RequiredArgsConstructor; + +import org.springframework.stereotype.Service; + +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.equipment.mapper.ProductMapper; +import top.ysoft.admin.equipment.mapstruct.ProductConvert; +import top.ysoft.admin.equipment.model.entity.ProductDO; +import top.ysoft.admin.equipment.model.query.ProductQuery; +import top.ysoft.admin.equipment.model.req.ProductReq; +import top.ysoft.admin.equipment.model.resp.ProductResp; +import top.ysoft.admin.equipment.service.ProductService; + +import java.util.List; + +/** + * 产品信息业务实现 + * + * @author zc + * @since 2025/05/26 15:18 + */ +@Service +@RequiredArgsConstructor +public class ProductServiceImpl extends BaseServiceImpl implements ProductService { + + private final ProductConvert productConvert; + + @Override + @ContainerMethod(namespace = ContainerConstants.PRODUCT_NAME_AVATAR, resultType = ProductDO.class, resultKey = "id", type = MappingType.ONE_TO_ONE) + public List getProductNameAvatarList() { + return baseMapper.selectList(new QueryWrapper().select("id", "avatar", "name")); + } + + @Override + public List getProductNameList() { + List productDOS = baseMapper.selectList(new QueryWrapper<>()); + return productConvert.labelValueList(productDOS); + } + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapper/BranchMapper.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapper/BranchMapper.java new file mode 100644 index 0000000..9fc8862 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapper/BranchMapper.java @@ -0,0 +1,21 @@ +package top.ysoft.admin.peopleBranch.mapper; + +// import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.peopleBranch.model.entity.BranchDO; +import top.ysoft.admin.peopleBranch.model.resp.BranchResp; + +import java.util.List; + +/** + * 部门管理 Mapper + * + * @author zc + * @since 2025/03/19 17:46 + */ +@Repository +public interface BranchMapper extends BaseMapper { + + List queryJuniorBranch(String branchId); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapper/BranchRuleMapper.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapper/BranchRuleMapper.java new file mode 100644 index 0000000..014dd4b --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapper/BranchRuleMapper.java @@ -0,0 +1,20 @@ +package top.ysoft.admin.peopleBranch.mapper; + +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.peopleBranch.model.entity.BranchRuleDO; +import top.ysoft.admin.peopleBranch.model.resp.BranchRuleResp; + +import java.util.List; + +/** + * 通行规则 Mapper + * + * @author zc + * @since 2025/03/26 22:59 + */ +@Repository +public interface BranchRuleMapper extends BaseMapper { + + List selectRuleNames(); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapper/PeopleMapper.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapper/PeopleMapper.java new file mode 100644 index 0000000..263a5b4 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapper/PeopleMapper.java @@ -0,0 +1,60 @@ +package top.ysoft.admin.peopleBranch.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.peopleBranch.model.entity.PeopleDO; +import top.ysoft.admin.peopleBranch.model.resp.ConsumePeopleResp; +import top.ysoft.admin.peopleBranch.model.resp.PeopleResp; + +import java.util.List; + +/** + * 人员管理业 Mapper + * + * @author zc + * @since 2025/03/19 17:46 + */ +@Repository +public interface PeopleMapper extends BaseMapper { + + /** + * 导出消费人员充值列表 + * + * @param queryWrapper + * @return + */ + List selectExportConsume(@Param(Constants.WRAPPER) QueryWrapper queryWrapper); + + /** + * 导出人员列表 + * + * @param queryWrapper + * @return + */ + List selectExportPeople(@Param(Constants.WRAPPER) QueryWrapper queryWrapper); + + /** + * 分页查询人员充值列表 + * + * @param objectPage + * @param queryWrapper + * @return + */ + IPage selectPageConsume(@Param("page") Page objectPage, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); + + /** + * 分页查询人员充值列表 + * + * @param objectPage + * @param queryWrapper + * @return + */ + IPage selectPeoplePage(@Param("page") Page objectPage, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapstruct/BranchConvert.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapstruct/BranchConvert.java new file mode 100644 index 0000000..254262c --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapstruct/BranchConvert.java @@ -0,0 +1,18 @@ +package top.ysoft.admin.peopleBranch.mapstruct; + +import org.mapstruct.Mapper; +import top.ysoft.admin.peopleBranch.model.entity.BranchDO; +import top.ysoft.admin.peopleBranch.model.req.BranchReq; +import top.ysoft.admin.peopleBranch.model.resp.BranchResp; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface BranchConvert { + + BranchResp branchDOToResp(BranchDO branchDO); + + BranchDO branchReqToDO(BranchReq branchReq); + + List ListDOToResp(List branchDOS); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapstruct/PeopleConvert.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapstruct/PeopleConvert.java new file mode 100644 index 0000000..e97e059 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapstruct/PeopleConvert.java @@ -0,0 +1,33 @@ +package top.ysoft.admin.peopleBranch.mapstruct; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.ysoft.admin.peopleBranch.model.entity.PeopleDO; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface PeopleConvert { + + List labelValueList(List peopleDOS); + + @Mapping(source = "id", target = "value") + @Mapping(source = "name", target = "label") + LabelValueResp labelValueResp(PeopleDO peopleDO); + + /*@Mapping(target = "isConsume", expression = "java(convertEnableEnum(peopleDO.getIsConsume()))") + ConsumePeopleResp toConsumePeopleResp(PeopleDO peopleDO); + + default EnableEnum convertEnableEnum(Integer isConsume) { + if (isConsume == null) { + return null; + } + for (EnableEnum value : EnableEnum.values()) { + if (value.getValue().equals(isConsume)) { + return value; + } + } + return null; + }*/ +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapstruct/SexConverter.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapstruct/SexConverter.java new file mode 100644 index 0000000..3ccb485 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/mapstruct/SexConverter.java @@ -0,0 +1,55 @@ +package top.ysoft.admin.peopleBranch.mapstruct; + +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.data.ReadCellData; +import com.alibaba.excel.metadata.data.WriteCellData; +import com.alibaba.excel.metadata.property.ExcelContentProperty; + +import java.util.HashMap; +import java.util.Map; + +public class SexConverter implements Converter { + + private static final Map SEX_MAP = new HashMap<>(); + static { + SEX_MAP.put(1, "男"); + SEX_MAP.put(2, "女"); + SEX_MAP.put(4, "保密"); + } + + @Override + public Class supportJavaTypeKey() { + return Integer.class; // 支持的 Java 类型 + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; // 写入 Excel 时用字符串 + } + + @Override + public WriteCellData convertToExcelData(Integer value, + ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (value == null) { + return new WriteCellData<>("未知"); + } + String label = SEX_MAP.getOrDefault(value, "未知"); + return new WriteCellData<>(label); + } + + @Override + public Integer convertToJavaData(ReadCellData cellData, + ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + String stringValue = cellData.getStringValue(); + for (Map.Entry entry : SEX_MAP.entrySet()) { + if (entry.getValue().equals(stringValue)) { + return entry.getKey(); + } + } + return null; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/entity/BranchDO.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/entity/BranchDO.java new file mode 100644 index 0000000..57d424d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/entity/BranchDO.java @@ -0,0 +1,81 @@ +package top.ysoft.admin.peopleBranch.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.model.entity.BaseStrIdDO; + +import java.io.Serial; + +/** + * 部门管理实体 + * + * @author zc + * @since 2025/03/19 17:40 + */ +@Data +@TableName("sys_branch") +public class BranchDO extends BaseStrIdDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 部门名称 + */ + private String name; + + /** + * 部门负责人 + */ + private String leader; + + /** + * 联系电话 + */ + private String phone; + + /** + * 父级Id + */ + private String parentId; + + /** + * 祖级列表 + */ + private String ancestors; + + /** + * 所属空间 + */ + private Long spaceId; + + /** + * 备注 + */ + private String remark; + + /** + * 关联部门 + */ + private Long deptId; + + /** + * + */ + private String level; + + /** + * 部门code + */ + private String deptCode; + + /** + * 梯控规则id + */ + private String ladderRuleId; + + /** + * 访客是否审核 0 否;1 是 + */ + private String isExamine; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/entity/BranchRuleDO.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/entity/BranchRuleDO.java new file mode 100644 index 0000000..786832f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/entity/BranchRuleDO.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.peopleBranch.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Builder; +import lombok.Data; + +/** + * 通行规则实体 + * + * @author zc + * @since 2025/03/26 22:58 + */ +@Data +@Builder +@TableName("sys_branch_rule") +public class BranchRuleDO { + + /** + * 部门id + */ + private String branchId; + + /** + * 规则id + */ + private Long ruleId; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/entity/PeopleDO.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/entity/PeopleDO.java new file mode 100644 index 0000000..a9ce395 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/entity/PeopleDO.java @@ -0,0 +1,148 @@ +package top.ysoft.admin.peopleBranch.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 人员管理实体 + * + * @author zc + * @since 2025/03/21 18:10 + */ +@Data +@TableName("sys_people") +public class PeopleDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 人员名称 + */ + private String name; + + /** + * 联系电话 + */ + private String phone; + + /** + * 用户性别(1男 2女 4保密) + */ + private Integer sex; + + /** + * 头像地址 + */ + private String avatar; + + /** + * 所在部门 + */ + private String branchId; + + /** + * 职位标签 + */ + private String position; + + /** + * 身份证号码 + */ + private String idcard; + + /** + * 门卡号码 + */ + private String doorNo; + + /** + * 人员编号对接 + */ + private String guid; + + /** + * 人像对接 + */ + private String faceGuid; + + /** + * 关联用户 + */ + private Long userId; + + /** + * 备注 + */ + private String remark; + + /** + * 工号 + */ + private String gh; + + /** + * 指纹特征值 + */ + private String fingerprint; + + /** + * 入职时间 + */ + private Date joinTime; + + /** + * 下发次数 + */ + private Long down; + + /** + * + */ + private String delFlag; + + /** + * 微信公众号id + */ + private String openid; + + /** + * 消费标志(0未同步 1已同步 2删除同步) + */ + private Integer xfFlag; + + /** + * 充值金额 + */ + private BigDecimal czje; + + /** + * 补贴金额 + */ + private BigDecimal btje; + + /** + * 是否消费 0:否,1:是 + */ + private Integer isConsume; + + /** + * 消费金额 + */ + private BigDecimal xfje; + + /** + * 消费次数 + */ + private Integer xfcs; + + /** + * 是否冻结 0:否,1:是 + */ + private Integer freeze; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/query/BranchQuery.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/query/BranchQuery.java new file mode 100644 index 0000000..40c2852 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/query/BranchQuery.java @@ -0,0 +1,39 @@ +package top.ysoft.admin.peopleBranch.model.query; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 部门管理查询条件 + * + * @author zc + * @since 2025/03/19 17:40 + */ +@Data +@Schema(description = "部门管理查询条件") +public class BranchQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 部门名称 + */ + @Schema(description = "部门名称") + @Query(type = QueryType.EQ) + private String name; + + /** + * 部门负责人 + */ + @Schema(description = "部门负责人") + @Query(type = QueryType.EQ) + private String leader; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/query/PeopleQuery.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/query/PeopleQuery.java new file mode 100644 index 0000000..a05328e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/query/PeopleQuery.java @@ -0,0 +1,111 @@ +package top.ysoft.admin.peopleBranch.model.query; + +import lombok.Builder; +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 人员管理查询条件 + * + * @author zc + * @since 2025/03/21 18:10 + */ +@Data +@Schema(description = "人员管理查询条件") +@Builder +public class PeopleQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + private String id; + + /** + * 主键ids + */ + @Query(columns = {"id"}, type = QueryType.IN) + private List ids; + + /** + * 人员名称 + */ + @Schema(description = "人员名称") + private String name; + + /** + * 部门 + */ + @Schema(description = "部门") + private String branchId; + + /** + * 联系电话 + */ + @Schema(description = "联系电话") + private String phone; + + /** + * 身份证号码 + */ + @Schema(description = "身份证号码") + private String idcard; + + /** + * 是否注册 + */ + @Schema(description = "是否注册") + private String guidFlag; + + /** + * 是否下发 + */ + @Schema(description = "是否下发") + private String downFlag; + + /** + * 人脸注册 + */ + @Schema(description = "人脸注册") + private String faceFlag; + + /** + * 卡号注册 + */ + @Schema(description = "卡号注册") + private String cardFlag; + + /** + * 卡号 + */ + @Schema(description = "卡号") + private String doorNo; + + /** + * 工号 + */ + @Schema(description = "工号") + private String gh; + + /** + * 是否消费 0:否,1:是 + */ + @Schema(description = "是否消费 0:否,1:是") + private Integer isConsume; + + /** + * 是否冻结 0:否,1:是 + */ + @Schema(description = "是否冻结 0:否,1:是") + private Integer freeze; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/BranchReq.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/BranchReq.java new file mode 100644 index 0000000..3180377 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/BranchReq.java @@ -0,0 +1,69 @@ +package top.ysoft.admin.peopleBranch.model.req; + +import jakarta.validation.constraints.*; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 创建或修改部门管理参数 + * + * @author zc + * @since 2025/03/19 17:40 + */ +@Data +@Schema(description = "创建或修改部门管理参数") +public class BranchReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 部门名称 + */ + @Schema(description = "部门名称") + @NotBlank(message = "部门名称不能为空") + @Length(max = 50, message = "部门名称长度不能超过 {max} 个字符") + private String name; + + /** + * 部门负责人 + */ + @Schema(description = "部门负责人") + @Length(max = 50, message = "部门负责人长度不能超过 {max} 个字符") + private String leader; + + /** + * 联系电话 + */ + @Schema(description = "联系电话") + @Length(max = 50, message = "联系电话长度不能超过 {max} 个字符") + private String phone; + + /** + * 父级Id + */ + @Schema(description = "父级Id") + @NotBlank(message = "父级Id不能为空") + @Length(max = 36, message = "父级Id长度不能超过 {max} 个字符") + private String parentId; + + /** + * 关联部门 + */ + @Schema(description = "关联部门") + private Long deptId; + + /** + * 规则id + */ + @Schema(description = "规则ids") + private List ruleIdList; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/PeopleImportReq.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/PeopleImportReq.java new file mode 100644 index 0000000..d966785 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/PeopleImportReq.java @@ -0,0 +1,51 @@ +package top.ysoft.admin.peopleBranch.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.ImportPolicyEnum; + +import java.io.Serial; + +@Data +@Schema(description = "人员导入参数") +public class PeopleImportReq { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 导入会话KEY + */ + @Schema(description = "导入会话KEY", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed") + @NotBlank(message = "导入已过期,请重新上传") + private String importKey; + + /** + * 用户重复策略 + */ + @Schema(description = "重复用户策略", example = "1") + @NotNull(message = "重复用户策略不能为空") + private ImportPolicyEnum duplicateUser; + + /** + * 重复邮箱策略 + */ + // @Schema(description = "重复邮箱策略", example = "1") + // @NotNull(message = "重复邮箱策略不能为空") + // private ImportPolicyEnum duplicateEmail; + + /** + * 重复手机策略 + */ + // @Schema(description = "重复手机策略", example = "1") + // @NotNull(message = "重复手机策略不能为空") + // private ImportPolicyEnum duplicatePhone; + + /** + * 默认状态 + */ + @Schema(description = "默认状态", example = "1") + private DisEnableStatusEnum defaultStatus; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/PeopleImportRowReq.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/PeopleImportRowReq.java new file mode 100644 index 0000000..98c72fc --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/PeopleImportRowReq.java @@ -0,0 +1,65 @@ +package top.ysoft.admin.peopleBranch.model.req; + +import cn.hutool.core.lang.RegexPool; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import top.ysoft.admin.common.constant.RegexConstants; +import top.ysoft.admin.peopleBranch.mapstruct.SexConverter; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +@Data +@Schema(description = "人员导入行数据") +public class PeopleImportRowReq implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户名 + */ + @NotBlank(message = "用户名不能为空") + @Pattern(regexp = RegexConstants.USERNAME, message = "用户名长度为 4-64 个字符,支持大小写字母、数字、下划线,以字母开头") + @ExcelProperty(value = "姓名", order = 2) + private String name; + + /** + * 部门名称 + */ + @NotBlank(message = "所属部门不能为空") + @ExcelProperty(value = "部门名称", order = 6) + private String branchName; + + /** + * 性别 + */ + @ExcelProperty(value = "性别", converter = SexConverter.class, order = 4) + private Integer sex; + + /** + * 手机号码 + */ + @Pattern(regexp = "^$|" + RegexPool.MOBILE, message = "手机号码格式错误") + @ExcelProperty(value = "联系电话", order = 3) + private String phone; + + @ExcelProperty(value = "工号", order = 1) + private String gh; + + @ExcelProperty(value = "职位标签", order = 7) + private String position; + + @ExcelProperty(value = "身份证号码", order = 8) + private String idcard; + + @ExcelProperty(value = "卡号", order = 9) + private String doorNo; + + @ExcelProperty(value = "入职时间", order = 10) + private Date joinTime; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/PeopleReq.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/PeopleReq.java new file mode 100644 index 0000000..be36b19 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/req/PeopleReq.java @@ -0,0 +1,183 @@ +package top.ysoft.admin.peopleBranch.model.req; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + * 创建或修改人员管理参数 + * + * @author zc + * @since 2025/03/21 18:10 + */ +@Data +@Schema(description = "创建或修改人员管理参数") +public class PeopleReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @Schema(description = "主键id") + private Long id; + + /** + * 人员名称 + */ + @Schema(description = "人员名称") + @NotBlank(message = "人员名称不能为空") + @Length(max = 25, message = "人员名称长度不能超过 {max} 个字符") + private String name; + + /** + * 联系电话 + */ + @Schema(description = "联系电话") + @Length(max = 50, message = "联系电话长度不能超过 {max} 个字符") + private String phone; + + /** + * 用户性别(0男 1女 2保密) + */ + @Schema(description = "用户性别(1男 2女 4保密)") + private Integer sex; + + /** + * 头像地址 + */ + @Schema(description = "头像地址") + @Length(max = 500, message = "头像地址长度不能超过 {max} 个字符") + private String avatar; + + /** + * 所在部门 + */ + @Schema(description = "所在部门") + @NotNull(message = "所属部门不能为空") + @Length(max = 36, message = "所在部门长度不能超过 {max} 个字符") + private String branchId; + + /** + * 职位标签 + */ + @Schema(description = "职位标签") + @Length(max = 50, message = "职位标签长度不能超过 {max} 个字符") + private String position; + + /** + * 身份证号码 + */ + @Schema(description = "身份证号码") + @Length(max = 50, message = "身份证号码长度不能超过 {max} 个字符") + private String idcard; + + /** + * 门卡号码 + */ + @Schema(description = "门卡号码") + @Length(max = 50, message = "门卡号码长度不能超过 {max} 个字符") + private String doorNo; + + /** + * 人员编号对接 + */ + @Schema(description = "人员编号对接") + @Length(max = 200, message = "人员编号对接长度不能超过 {max} 个字符") + private String guid; + + /** + * 人像对接 + */ + @Schema(description = "人像对接") + @Length(max = 200, message = "人像对接长度不能超过 {max} 个字符") + private String faceGuid; + + /** + * 关联用户 + */ + @Schema(description = "关联用户") + private Long userId; + + /** + * 备注 + */ + @Schema(description = "备注") + @Length(max = 200, message = "备注长度不能超过 {max} 个字符") + private String remark; + + /** + * 工号 + */ + @Schema(description = "工号") + @NotNull(message = "工号不能为空") + @Length(max = 8, message = "工号长度不能超过 {max} 个字符") + private String gh; + + /** + * 指纹特征值 + */ + @Schema(description = "指纹特征值") + @Length(max = 255, message = "指纹特征值长度不能超过 {max} 个字符") + private String fingerprint; + + /** + * 入职时间 + */ + @Schema(description = "入职时间") + @JsonFormat(pattern = "yyyy-MM-dd") + private Date joinTime; + + /** + * 入职时间 + */ + @Schema(description = "入职时间") + private Long down; + + /** + * 是否需要下发设备(用于修改人员信息时,校验是否修改人员姓名,照片等需要下发的设备的参数) + */ + @Schema(description = "是否需要下发设备") + private Boolean needDown = false; + + /** + * 人员id列表 + */ + @Schema(description = "人员id列表") + private List ids; + + /** + * 消费充值的金额or补贴的金额 + */ + @Schema(description = "金额") + private BigDecimal money; + + /** + * 是否消费 0:否,1:是 + */ + @Schema(description = "是否消费 0:否,1:是") + private Integer isConsume; + + /** + * 一键清零所有人 0:否,1:是 + */ + @Schema(description = "一键清零所有人 0:否,1:是") + private Integer resetMoneyAll; + + /** + * 一键清零金额种类 0:全部,1:充值,2:补贴 + */ + @Schema(description = "一键清零金额种类 0:全部,1:充值,2:补贴") + private Integer resetMoneyType; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/BranchResp.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/BranchResp.java new file mode 100644 index 0000000..ad6eb1d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/BranchResp.java @@ -0,0 +1,114 @@ +package top.ysoft.admin.peopleBranch.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseStrIdResp; + +import java.io.Serial; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 部门管理信息 + * + * @author zc + * @since 2025/03/19 17:46 + */ +@Data +@Schema(description = "部门管理信息") +public class BranchResp extends BaseStrIdResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 部门名称 + */ + @Schema(description = "部门名称") + private String name; + + /** + * 部门负责人 + */ + @Schema(description = "部门负责人") + private String leader; + + /** + * 联系电话 + */ + @Schema(description = "联系电话") + private String phone; + + /** + * 父级Id + */ + @Schema(description = "父级Id") + private String parentId; + + /** + * 祖级列表 + */ + @Schema(description = "祖级列表") + private String ancestors; + + /** + * 所属空间 + */ + @Schema(description = "所属空间") + private Long spaceId; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + /** + * 关联部门 + */ + @Schema(description = "关联部门") + private Long deptId; + + /** + * + */ + @Schema(description = "") + private String level; + + /** + * 部门code + */ + @Schema(description = "部门code") + private String deptCode; + + /** + * 规则ids + */ + @Schema(description = "规则ids") + private List ruleIdList; + + /** + * 规则名称 + */ + @Schema(description = "规则名称") + private String ruleNames; + + /** + * 梯控规则id + */ + @Schema(description = "梯控规则id") + private String ladderRuleId; + + /** + * 访客是否审核 0 否;1 是 + */ + @Schema(description = "访客是否审核 0 否;1 是") + private String isExamine; + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/BranchRuleResp.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/BranchRuleResp.java new file mode 100644 index 0000000..ae3f63d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/BranchRuleResp.java @@ -0,0 +1,22 @@ +package top.ysoft.admin.peopleBranch.model.resp; + +import lombok.Data; + +@Data +public class BranchRuleResp { + + /** + * 部门id + */ + private String branchId; + + /** + * 规则id + */ + private Long ruleId; + + /** + * 规则名称 + */ + private String ruleName; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/ConsumePeopleResp.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/ConsumePeopleResp.java new file mode 100644 index 0000000..2d6b32a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/ConsumePeopleResp.java @@ -0,0 +1,233 @@ +package top.ysoft.admin.peopleBranch.model.resp; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.core.executor.handler.ManyToManyAssembleOperationHandler; +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.common.enums.EnableEnum; +import top.ysoft.admin.common.enums.YesNoEnum; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.peopleBranch.mapstruct.SexConverter; + +import java.io.Serial; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Date; + +/** + * 消费人员管理信息 + * + * @author zc + * @since 2025/03/21 18:10 + */ +@Data +@Schema(description = "人员管理信息") +public class ConsumePeopleResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 人员名称 + */ + @Schema(description = "人员名称") + @ExcelProperty(value = "姓名", order = 1) + @ColumnWidth(10) + private String name; + + /** + * 联系电话 + */ + @Schema(description = "联系电话") + @ExcelIgnore + private String phone; + + /** + * 用户性别(1男 2女 4保密) + */ + @Schema(description = "用户性别(1男 2女 4保密)") + @ExcelProperty(value = "性别", converter = SexConverter.class, order = 3) + private Integer sex; + + /** + * 头像地址 + */ + @Schema(description = "头像地址") + @ExcelIgnore + private String avatar; + + /** + * 所在部门 + */ + @Schema(description = "所在部门") + @Assemble(prop = ":branchName", container = ContainerConstants.BRANCH_NAME, handlerType = ManyToManyAssembleOperationHandler.class) + @ExcelIgnore + private String branchId; + + /** + * 所在部门名称 + */ + @Schema(description = "所在部门名称") + @ExcelProperty(value = "部门名称", order = 4) + private String branchName; + + /** + * 职位标签 + */ + @Schema(description = "职位标签") + @ExcelIgnore + private String position; + + /** + * 身份证号码 + */ + @Schema(description = "身份证号码") + @ExcelIgnore + private String idcard; + + /** + * 门卡号码 + */ + @Schema(description = "门卡号码") + @ExcelIgnore + private String doorNo; + + /** + * 人员编号对接 + */ + @Schema(description = "人员编号对接") + @ExcelIgnore + private String guid; + + /** + * 人像对接 + */ + @Schema(description = "人像对接") + @ExcelIgnore + private String faceGuid; + + /** + * 关联用户 + */ + @Schema(description = "关联用户") + @ExcelIgnore + private Long userId; + + /** + * 关联用户 + */ + @Schema(description = "关联用户") + @ExcelIgnore + private String userName; + + /** + * 备注 + */ + @Schema(description = "备注") + @ExcelIgnore + private String remark; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + @ExcelIgnore + private LocalDateTime updateTime; + + /** + * 工号 + */ + @Schema(description = "工号") + @ExcelProperty(value = "工号", order = 2) + @ColumnWidth(30) + private String gh; + + /** + * 指纹特征值 + */ + @Schema(description = "指纹特征值") + @ExcelIgnore + private String fingerprint; + + /** + * 入职时间 + */ + @Schema(description = "入职时间") + @ExcelIgnore + private Date joinTime; + + /** + * 下发次数 + */ + @Schema(description = "下发次数") + @ExcelIgnore + private Long down; + + /** + * + */ + @Schema(description = "") + @ExcelIgnore + private String delFlag; + + /** + * 微信公众号id + */ + @Schema(description = "微信公众号id") + @ExcelIgnore + private String openid; + + /** + * 消费标志(0未同步 1已同步 2删除同步) + */ + @Schema(description = "消费标志(0未同步 1已同步 2删除同步)") + @ExcelIgnore + private Integer xfFlag; + + /** + * 充值金额 + */ + @Schema(description = "充值金额") + @ExcelProperty(value = "充值金额", order = 5) + private BigDecimal czje = BigDecimal.ZERO; + + /** + * 补贴金额 + */ + @Schema(description = "补贴金额") + @ExcelProperty(value = "补贴金额", order = 6) + private BigDecimal btje = BigDecimal.ZERO; + + /** + * 是否消费 0:否,1:是 + */ + @Schema(description = "是否启用消费 0:否,1:是") + @ExcelProperty(value = "是否启用消费", converter = ExcelBaseEnumConverter.class, order = 7) + private EnableEnum isConsume; + + /** + * 消费金额 + */ + @Schema(description = "消费金额") + @ExcelIgnore + private BigDecimal xfje = BigDecimal.ZERO; + + /** + * 消费次数 + */ + @Schema(description = "消费次数") + @ExcelIgnore + private Integer xfcs; + + /** + * 是否冻结 0:否,1:是 + */ + @Schema(description = "是否冻结 0:否,1:是") + @ExcelProperty(value = "是否冻结", converter = ExcelBaseEnumConverter.class, order = 8) + private YesNoEnum freeze; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/PeopleImportParseResp.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/PeopleImportParseResp.java new file mode 100644 index 0000000..fefab93 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/PeopleImportParseResp.java @@ -0,0 +1,51 @@ +package top.ysoft.admin.peopleBranch.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "人员导入解析结果") +public class PeopleImportParseResp implements Serializable { + /** + * 导入会话 Key + */ + @Schema(description = "导入会话Key", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed") + private String importKey; + + /** + * 总计行数 + */ + @Schema(description = "总计行数", example = "100") + private Integer totalRows; + + /** + * 有效行数 + */ + @Schema(description = "有效行数", example = "100") + private Integer validRows; + + /** + * 重复人员行数 + */ + @Schema(description = "重复行数", example = "100") + private Integer duplicatePeopleRows; + + // + // /** + // * 重复工号行数 + // */ + // @Schema(description = "重复邮箱行数", example = "100") + // private Integer duplicateEmailRows; + // + // /** + // * 重复卡号行数 + // */ + // @Schema(description = "重复手机行数", example = "100") + // private Integer duplicatePhoneRows; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/PeopleImportResp.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/PeopleImportResp.java new file mode 100644 index 0000000..0e9f300 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/PeopleImportResp.java @@ -0,0 +1,31 @@ +package top.ysoft.admin.peopleBranch.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "人员导入结果") +public class PeopleImportResp { + private static final long serialVersionUID = 1L; + /** + * 总计行数 + */ + @Schema(description = "总计行数", example = "100") + private Integer totalRows; + + /** + * 新增行数 + */ + @Schema(description = "新增行数", example = "100") + private Integer insertRows; + + /** + * 修改行数 + */ + @Schema(description = "修改行数", example = "100") + private Integer updateRows; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/PeopleResp.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/PeopleResp.java new file mode 100644 index 0000000..5103ec4 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/model/resp/PeopleResp.java @@ -0,0 +1,237 @@ +package top.ysoft.admin.peopleBranch.model.resp; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.core.executor.handler.ManyToManyAssembleOperationHandler; +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.peopleBranch.mapstruct.SexConverter; + +import java.io.Serial; +import java.math.BigDecimal; +import java.time.*; +import java.util.Date; + +/** + * 人员管理信息 + * + * @author zc + * @since 2025/03/21 18:10 + */ +@Data +@Schema(description = "人员管理信息") +public class PeopleResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 人员名称 + */ + @Schema(description = "人员名称") + @ExcelProperty(value = "姓名", order = 2) + @ColumnWidth(10) + private String name; + + /** + * 联系电话 + */ + @Schema(description = "联系电话") + @ExcelProperty(value = "联系电话", order = 3) + @ColumnWidth(15) + private String phone; + + /** + * 用户性别(1男 2女 4保密) + */ + @Schema(description = "用户性别(1男 2女 4保密)") + @ExcelProperty(value = "性别", converter = SexConverter.class, order = 4) + private Integer sex; + + /** + * 头像地址 + */ + @Schema(description = "头像地址") + @ExcelIgnore + private String avatar; + + /** + * 所在部门 + */ + @Schema(description = "所在部门") + @ExcelIgnore + @Assemble(prop = ":branchName", container = ContainerConstants.BRANCH_NAME, handlerType = ManyToManyAssembleOperationHandler.class) + private String branchId; + + /** + * 所在部门名称 + */ + @Schema(description = "所在部门名称") + @ExcelProperty(value = "部门名称", order = 5) + private String branchName; + + /** + * 职位标签 + */ + @Schema(description = "职位标签") + @ExcelProperty(value = "职位标签", order = 6) + private String position; + + /** + * 身份证号码 + */ + @Schema(description = "身份证号码") + @ExcelProperty(value = "身份证号码", order = 7) + @ColumnWidth(25) + private String idcard; + + /** + * 门卡号码 + */ + @Schema(description = "门卡号码") + @ExcelProperty(value = "卡号", order = 8) + @ColumnWidth(15) + private String doorNo; + + /** + * 人员编号对接 + */ + @Schema(description = "人员编号对接") + @ExcelIgnore + private String guid; + + /** + * 人像对接 + */ + @Schema(description = "人像对接") + @ExcelIgnore + private String faceGuid; + + /** + * 关联用户 + */ + @Schema(description = "关联用户") + @Assemble(prop = ":userName", container = ContainerConstants.USER_NAME, handlerType = ManyToManyAssembleOperationHandler.class) + @ExcelIgnore + private Long userId; + + /** + * 关联用户 + */ + @Schema(description = "关联用户") + @ExcelIgnore + private String userName; + + /** + * 备注 + */ + @Schema(description = "备注") + @ExcelIgnore + private String remark; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + @ExcelIgnore + private LocalDateTime updateTime; + + /** + * 工号 + */ + @Schema(description = "工号") + @ExcelProperty(value = "工号", order = 1) + @ColumnWidth(30) + private String gh; + + /** + * 指纹特征值 + */ + @Schema(description = "指纹特征值") + @ExcelIgnore + private String fingerprint; + + /** + * 入职时间 + */ + @Schema(description = "入职时间") + @ExcelProperty(value = "入职时间", order = 9) + @ColumnWidth(35) + private Date joinTime; + + /** + * 下发次数 + */ + @Schema(description = "下发次数") + @ExcelIgnore + private Long down; + + /** + * + */ + @Schema(description = "") + @ExcelIgnore + private String delFlag; + + /** + * 微信公众号id + */ + @Schema(description = "微信公众号id") + @ExcelIgnore + private String openid; + + /** + * 消费标志(0未同步 1已同步 2删除同步) + */ + @Schema(description = "消费标志(0未同步 1已同步 2删除同步)") + @ExcelIgnore + private Integer xfFlag; + + /** + * 充值金额 + */ + @Schema(description = "充值金额") + @ExcelIgnore + private BigDecimal czje; + + /** + * 补贴金额 + */ + @Schema(description = "补贴金额") + @ExcelIgnore + private BigDecimal btje; + + /** + * 是否消费 0:否,1:是 + */ + @Schema(description = "是否消费 0:否,1:是") + @ExcelIgnore + private Integer isConsume; + + /** + * 消费金额 + */ + @Schema(description = "消费金额") + @ExcelIgnore + private BigDecimal xfje; + + /** + * 消费次数 + */ + @Schema(description = "消费次数") + @ExcelIgnore + private Integer xfcs; + + /** + * 是否冻结 0:否,1:是 + */ + @Schema(description = "是否冻结 0:否,1:是") + @ExcelIgnore + private Integer freeze; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/BranchService.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/BranchService.java new file mode 100644 index 0000000..d19db53 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/BranchService.java @@ -0,0 +1,35 @@ +package top.ysoft.admin.peopleBranch.service; + +import cn.hutool.core.lang.tree.Tree; +import top.continew.starter.data.mp.service.IService; +import top.ysoft.admin.peopleBranch.model.entity.BranchDO; +import top.ysoft.admin.peopleBranch.model.query.BranchQuery; +import top.ysoft.admin.peopleBranch.model.req.BranchReq; +import top.ysoft.admin.peopleBranch.model.resp.BranchResp; + +import java.util.List; + +/** + * 部门管理业务接口 + * + * @author zc + * @since 2025/03/19 17:46 + */ +public interface BranchService extends IService { + + List> selectBranchTree(BranchQuery branchQuery); + + BranchResp selectBranchById(String id); + + String insertBranch(BranchReq branchReq); + + void updateBranch(BranchReq branchReq, String id); + + void deleteBranchById(String id); + + List> tree(BranchQuery query); + + List listByNames(List list); + + String branchNameById(Long id); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/PeopleService.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/PeopleService.java new file mode 100644 index 0000000..202098e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/PeopleService.java @@ -0,0 +1,157 @@ +package top.ysoft.admin.peopleBranch.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.multipart.MultipartFile; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.consume.model.req.PeopleDepositImportReq; +import top.ysoft.admin.consume.model.resp.PeopleDepositImportParseResp; +import top.ysoft.admin.peopleBranch.model.query.PeopleQuery; +import top.ysoft.admin.peopleBranch.model.req.PeopleImportReq; +import top.ysoft.admin.peopleBranch.model.req.PeopleReq; +import top.ysoft.admin.peopleBranch.model.resp.ConsumePeopleResp; +import top.ysoft.admin.peopleBranch.model.resp.PeopleImportParseResp; +import top.ysoft.admin.peopleBranch.model.resp.PeopleImportResp; +import top.ysoft.admin.peopleBranch.model.resp.PeopleResp; +import top.ysoft.admin.system.model.resp.user.UserImportResp; + +import java.io.IOException; +import java.util.List; + +/** + * 人员管理业务接口 + * + * @author zc + * @since 2025/03/21 18:22 + */ +public interface PeopleService extends BaseService { + + /** + * 下发人员 + * + * @param ids 人员ID列表 + * @return 失败人员名称列表 + */ + String down(Long[] ids); + + /** + * 删除人员 + * + * @param ids 人员ID列表 + * @return 失败人员名称列表 + */ + String del(List ids); + + /** + * 解析导入数据 + * + * @param file 导入文件 + * @return 解析结果 + */ + PeopleImportParseResp parseImport(MultipartFile file); + + PeopleImportResp importPeople(PeopleImportReq req); + + /** + * 分页查询人员消费列表 + * + * @param query 查询条件 + * @param pageQuery 分页查询条件 + * @return 分页列表信息 + */ + PageResp pageConsume(PeopleQuery query, PageQuery pageQuery); + + /** + * 补贴人员 + * + * @param req 补贴参数 + * @return 补贴结果 + */ + void subsidy(PeopleReq req); + + /** + * 充值人员 + * + * @param req 充值参数 + * @return 充值结果 + */ + void deposit(PeopleReq req); + + /** + * 退款人员 + * + * @param req 退款参数 + * @return 退款结果 + */ + void refund(PeopleReq req); + + /** + * crane4j 容器方法,根据人员ID查询人员名称 + * + * @param id + * @return + */ + String getPeopleName(Long id); + + /** + * 下拉框数据,人员名称 + * + * @param peopleQuery + * @return + */ + List getPeopleNameList(PeopleQuery peopleQuery); + + /** + * 消费-人员充值导出 + * + * @param query + * @return + */ + void exportConsume(PeopleQuery query, HttpServletResponse response); + + /** + * 消费冻结 + * + * @param ids + */ + void freeze(List ids); + + /** + * 消费解冻 + * + * @param ids + */ + void unFreeze(List ids); + + /** + * 消费一键清零 + * + * @param req + */ + void resetMoney(PeopleReq req); + + /** + * 下载导入模板 + * + * @param response + */ + void downloadConsumeImportTemplate(HttpServletResponse response) throws IOException; + + /** + * 解析导入数据 + * + * @param file 导入文件 + * @return 解析结果 + */ + PeopleDepositImportParseResp parseConsumeImport(MultipartFile file); + + /** + * 导入人员充值 + * + * @param req 导入参数 + * @return 导入结果 + */ + UserImportResp importConsumePeople(PeopleDepositImportReq req); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/impl/BranchServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/impl/BranchServiceImpl.java new file mode 100644 index 0000000..21a9b59 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/impl/BranchServiceImpl.java @@ -0,0 +1,165 @@ +package top.ysoft.admin.peopleBranch.service.impl; + +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNodeConfig; +import cn.hutool.core.lang.tree.TreeUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.peopleBranch.mapper.BranchMapper; +import top.ysoft.admin.peopleBranch.mapper.BranchRuleMapper; +import top.ysoft.admin.peopleBranch.mapstruct.BranchConvert; +import top.ysoft.admin.peopleBranch.model.entity.BranchDO; +import top.ysoft.admin.peopleBranch.model.entity.BranchRuleDO; +import top.ysoft.admin.peopleBranch.model.query.BranchQuery; +import top.ysoft.admin.peopleBranch.model.req.BranchReq; +import top.ysoft.admin.peopleBranch.model.resp.BranchResp; +import top.ysoft.admin.peopleBranch.model.resp.BranchRuleResp; +import top.ysoft.admin.peopleBranch.service.BranchService; +import top.ysoft.admin.rule.mapper.RuleMapper; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 部门管理业务实现 + * + * @author zc + * @since 2025/03/19 17:46 + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class BranchServiceImpl extends ServiceImpl implements BranchService { + + private final BranchConvert branchConvert; + + private final BranchRuleMapper branchRuleMapper; + + private final RuleMapper ruleMapper; + + @Override + public List> selectBranchTree(BranchQuery branchQuery) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(StrUtil.isNotBlank(branchQuery.getName()), BranchDO::getName, branchQuery.getName()); + queryWrapper.eq(StrUtil.isNotBlank(branchQuery.getName()), BranchDO::getLeader, branchQuery.getLeader()); + + //查询部门列表 + List branchDOS = baseMapper.selectList(queryWrapper); + List branchResps = branchConvert.ListDOToResp(branchDOS); + + //查询部门规则列表 + List branchRuleDOS = branchRuleMapper.selectRuleNames(); + + Map collect = branchRuleDOS.stream() + .collect(Collectors.toMap(BranchRuleResp::getBranchId, BranchRuleResp::getRuleName, (k1, + k2) -> k1 + " | " + k2)); + + branchResps.forEach(branchResp -> { + if (collect.containsKey(branchResp.getId())) { + branchResp.setRuleNames(collect.get(branchResp.getId())); + } + }); + + TreeNodeConfig config = new TreeNodeConfig(); + return TreeUtil.build(branchResps, "0", config, (treeNode, tree) -> { + tree.setId(treeNode.getId()); + tree.setParentId(treeNode.getParentId()); + tree.setName(treeNode.getName()); + tree.putExtra("leader", treeNode.getLeader()); + tree.putExtra("phone", treeNode.getPhone()); + tree.putExtra("ruleNames", treeNode.getRuleNames()); + tree.putExtra("deptId", treeNode.getDeptId()); + }); + } + + @Override + public BranchResp selectBranchById(String id) { + BranchResp branchResp = branchConvert.branchDOToResp(baseMapper.selectById(id)); + List ruleDOS = branchRuleMapper.selectList(new LambdaQueryWrapper() + .eq(BranchRuleDO::getBranchId, id)); + branchResp.setRuleIdList(ruleDOS.stream().map(BranchRuleDO::getRuleId).collect(Collectors.toList())); + return branchResp; + } + + @Override + public String insertBranch(BranchReq branchReq) { + BranchDO branchDO = branchConvert.branchReqToDO(branchReq); + baseMapper.insert(branchDO); + if (CollUtil.isNotEmpty(branchReq.getRuleIdList())) { + List branchRuleDOS = branchReq.getRuleIdList() + .stream() + .map(ruleId -> BranchRuleDO.builder().ruleId(ruleId).branchId(branchDO.getId()).build()) + .collect(Collectors.toList()); + branchRuleMapper.insertBatch(branchRuleDOS); + } + return branchDO.getId(); + } + + @Override + public void updateBranch(BranchReq branchReq, String id) { + BranchDO branchDO = branchConvert.branchReqToDO(branchReq); + branchDO.setId(id); + if (CollUtil.isNotEmpty(branchReq.getRuleIdList())) { + branchRuleMapper.delete(new LambdaQueryWrapper().eq(BranchRuleDO::getBranchId, id)); + List branchRuleDOS = branchReq.getRuleIdList() + .stream() + .map(ruleId -> BranchRuleDO.builder().ruleId(ruleId).branchId(branchDO.getId()).build()) + .collect(Collectors.toList()); + branchRuleMapper.insertBatch(branchRuleDOS); + } + baseMapper.updateById(branchDO); + } + + @Override + public void deleteBranchById(String id) { + branchRuleMapper.delete(new LambdaQueryWrapper().eq(BranchRuleDO::getBranchId, id)); + baseMapper.deleteById(id); + } + + @Override + public List> tree(BranchQuery query) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(StrUtil.isNotBlank(query.getName()), BranchDO::getName, query.getName()); + queryWrapper.eq(StrUtil.isNotBlank(query.getLeader()), BranchDO::getLeader, query.getLeader()); + + List branchDOS = baseMapper.selectList(queryWrapper); + + TreeNodeConfig config = new TreeNodeConfig(); + return TreeUtil.build(branchDOS, "0", config, (treeNode, tree) -> { + tree.setId(treeNode.getId()); + tree.putExtra("key", treeNode.getId()); + tree.putExtra("title", treeNode.getName()); + tree.setParentId(treeNode.getParentId()); + }); + } + + @Override + public List listByNames(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + return this.list(Wrappers.lambdaQuery().in(BranchDO::getName, list)); + } + + @Override + @ContainerMethod(namespace = ContainerConstants.BRANCH_NAME, type = MappingType.ORDER_OF_KEYS) + public String branchNameById(Long id) { + if (null == id) { + return ""; + } + + BranchDO branchDO = baseMapper.selectById(id); + return branchDO != null ? branchDO.getName() : ""; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/impl/PeopleServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/impl/PeopleServiceImpl.java new file mode 100644 index 0000000..db1fe11 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleBranch/service/impl/PeopleServiceImpl.java @@ -0,0 +1,1070 @@ +package top.ysoft.admin.peopleBranch.service.impl; + +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.UUID; +import cn.hutool.core.util.*; +import cn.hutool.extra.validation.ValidationUtil; +import cn.hutool.http.ContentType; +import cn.hutool.json.JSONUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.ahoo.cosid.IdGenerator; +import me.ahoo.cosid.provider.DefaultIdGeneratorProvider; +import net.dreamlu.mica.core.result.R; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.exception.BusinessException; +import top.continew.starter.core.util.SpringUtils; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.query.SortQuery; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.continew.starter.file.excel.util.ExcelUtils; +import top.continew.starter.web.util.FileUploadUtils; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.common.enums.*; +import top.ysoft.admin.common.util.SecureUtils; +import top.ysoft.admin.consume.mapper.RechargeRecordMapper; +import top.ysoft.admin.consume.model.entity.RechargeRecordDO; +import top.ysoft.admin.consume.model.req.PeopleDepositImportReq; +import top.ysoft.admin.consume.model.req.PeopleDepositImportRowReq; +import top.ysoft.admin.consume.model.resp.PeopleDepositImportParseResp; +import top.ysoft.admin.peopleBranch.mapper.BranchMapper; +import top.ysoft.admin.peopleBranch.mapper.BranchRuleMapper; +import top.ysoft.admin.peopleBranch.mapper.PeopleMapper; +import top.ysoft.admin.peopleBranch.mapstruct.PeopleConvert; +import top.ysoft.admin.peopleBranch.model.entity.BranchDO; +import top.ysoft.admin.peopleBranch.model.entity.BranchRuleDO; +import top.ysoft.admin.peopleBranch.model.entity.PeopleDO; +import top.ysoft.admin.peopleBranch.model.query.PeopleQuery; +import top.ysoft.admin.peopleBranch.model.req.PeopleImportReq; +import top.ysoft.admin.peopleBranch.model.req.PeopleImportRowReq; +import top.ysoft.admin.peopleBranch.model.req.PeopleReq; +import top.ysoft.admin.peopleBranch.model.resp.*; +import top.ysoft.admin.peopleBranch.service.BranchService; +import top.ysoft.admin.peopleBranch.service.PeopleService; +import top.ysoft.admin.peopleDownRecord.mapper.PeopleDownMapper; +import top.ysoft.admin.peopleDownRecord.model.entity.PeopleDownDO; +import top.ysoft.admin.peopleDownRecord.model.req.PeopleDownReq; +import top.ysoft.admin.peopleDownRecord.service.PeopleDownService; +import top.ysoft.admin.system.mapper.RuleRelationMapper; +import top.ysoft.admin.system.model.entity.*; +import top.ysoft.admin.system.model.req.user.UserImportReq; +import top.ysoft.admin.system.model.req.user.UserImportRowReq; +import top.ysoft.admin.system.model.resp.user.UserImportResp; +import top.ysoft.admin.system.service.ConfigService; +import top.ysoft.admin.yfApi.service.ISysYFService; + +import java.io.IOException; +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static top.ysoft.admin.system.enums.ImportPolicyEnum.*; + +/** + * 人员管理业务实现 + * + * @author zc + * @since 2025/03/21 18:22 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class PeopleServiceImpl extends BaseServiceImpl implements PeopleService { + + private final BranchMapper branchMapper; + + private final PeopleConvert peopleConvert; + + private final ISysYFService sysYFService; + + private final PeopleDownMapper peopleDownMapper; + + private final PeopleDownService peopledownService; + + private final BranchService branchService; + + private final BranchRuleMapper branchRuleMapper; + + private final RuleRelationMapper ruleRelationMapper; + + private final ConfigService configService; + + private final RechargeRecordMapper rechargeRecordMapper; + + @Override + public PageResp page(PeopleQuery query, PageQuery pageQuery) { + + QueryWrapper queryWrapper = new QueryWrapper<>(); + //初始部门不查询下级部门 + if (StrUtil.isNotBlank(query.getBranchId()) && !StrUtil.equals(query.getBranchId(), "1")) { + List branchResps = branchMapper.queryJuniorBranch(query.getBranchId()); + List branchList = branchResps.stream().map(BranchResp::getId).collect(Collectors.toList()); + branchList.add(query.getBranchId()); + queryWrapper.in("p.branch_id", branchList); + } + + //查询条件 + queryWrapper.like(StrUtil.isNotBlank(query.getName()), "p.name", query.getName()); + queryWrapper.like(StrUtil.isNotBlank(query.getPhone()), "p.phone", query.getPhone()); + queryWrapper.likeRight(StrUtil.isNotBlank(query.getIdcard()), "p.idcard", query.getIdcard()); + if (StrUtil.isNotBlank(query.getFaceFlag())) { + if (StrUtil.equals(query.getFaceFlag(), "0")) { + queryWrapper.isNull("p.face_guid"); + } else { + queryWrapper.isNotNull("p.face_guid"); + } + } + if (StrUtil.isNotBlank(query.getGuidFlag())) { + if (StrUtil.equals(query.getGuidFlag(), "0")) { + queryWrapper.isNull("p.guid"); + } else { + queryWrapper.isNotNull("p.guid"); + } + } + if (StrUtil.isNotBlank(query.getDownFlag())) { + if (StrUtil.equals(query.getDownFlag(), "0")) { + queryWrapper.eq("p.down", 0); + } else { + queryWrapper.gt("p.down", 0); + } + } + if (StrUtil.isNotBlank(query.getCardFlag())) { + if (StrUtil.equals(query.getCardFlag(), "0")) { + queryWrapper.isNull("p.door_no"); + } else { + queryWrapper.isNotNull("p.door_no"); + } + } + queryWrapper.orderByDesc("p.id", "p.create_time"); + + IPage page = baseMapper.selectPeoplePage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + + return PageResp.build(page); + } + + @Override + public void export(PeopleQuery query, SortQuery sortQuery, HttpServletResponse response) { + + QueryWrapper queryWrapper = new QueryWrapper<>(); + //初始部门不查询下级部门 + if (StrUtil.isNotBlank(query.getBranchId()) && !StrUtil.equals(query.getBranchId(), "1")) { + List branchResps = branchMapper.queryJuniorBranch(query.getBranchId()); + List branchList = branchResps.stream().map(BranchResp::getId).collect(Collectors.toList()); + branchList.add(query.getBranchId()); + queryWrapper.in("p.branch_id", branchList); + } + + //查询条件 + queryWrapper.like(StrUtil.isNotBlank(query.getName()), "p.name", query.getName()); + queryWrapper.like(StrUtil.isNotBlank(query.getPhone()), "p.phone", query.getPhone()); + queryWrapper.likeRight(StrUtil.isNotBlank(query.getIdcard()), "p.idcard", query.getIdcard()); + if (StrUtil.isNotBlank(query.getFaceFlag())) { + if (StrUtil.equals(query.getFaceFlag(), "0")) { + queryWrapper.isNull("p.face_guid"); + } else { + queryWrapper.isNotNull("p.face_guid"); + } + } + if (StrUtil.isNotBlank(query.getGuidFlag())) { + if (StrUtil.equals(query.getGuidFlag(), "0")) { + queryWrapper.isNull("p.guid"); + } else { + queryWrapper.isNotNull("p.guid"); + } + } + if (StrUtil.isNotBlank(query.getDownFlag())) { + if (StrUtil.equals(query.getDownFlag(), "0")) { + queryWrapper.eq("p.down", 0); + } else { + queryWrapper.gt("p.down", 0); + } + } + if (StrUtil.isNotBlank(query.getCardFlag())) { + if (StrUtil.equals(query.getCardFlag(), "0")) { + queryWrapper.isNull("p.door_no"); + } else { + queryWrapper.isNotNull("p.door_no"); + } + } + queryWrapper.orderByDesc("p.id", "p.create_time"); + List peopleRespList = baseMapper.selectExportPeople(queryWrapper); + ExcelUtils.export(peopleRespList, "人员清单_" + DateUtil.today(), this.getDetailClass(), response); + } + + //下发 + @Override + public String down(Long[] ids) { + List failIds = new ArrayList<>(); + String failNames = ""; + //是否开启人脸机 + if (checkYFEnable()) { + failIds = sysYFService.authDevice(Arrays.asList(ids), OperTypeEnum.DOWN.getValue()); + } + if (CollUtil.isNotEmpty(failIds)) { + failNames = baseMapper.selectList(new LambdaQueryWrapper().in(PeopleDO::getId, failIds)) + .stream() + .map(PeopleDO::getName) + .collect(Collectors.joining(",")); + } + + return failNames; + } + + @Override + public void beforeAdd(PeopleReq req) { + //给新添加人员新增guid,用于设备注册 + req.setGuid(IdUtil.simpleUUID()); + //如果照片不为空 则生成faceguid + if (ObjectUtil.isNotEmpty(req.getAvatar())) { + req.setFaceGuid(IdUtil.simpleUUID()); + } + //入参重复数据校验 + CheckUtils.throwIf(isGhExists(req.getGh()), "新增失败,工号[{}] 已存在", req.getGh()); + if (StrUtil.isNotBlank(req.getDoorNo())) { + CheckUtils.throwIf(isDoorNoExists(req.getDoorNo()), "新增失败,门禁卡号[{}] 已存在", req.getDoorNo()); + } + } + + //新增人员下发进设备 + @Override + public void afterAdd(PeopleReq req, PeopleDO peopleDO) { + //是否开启人脸机 + if (checkYFEnable()) { + sysYFService.authDevice(Collections.singletonList(peopleDO.getId()), OperTypeEnum.ADD.getValue()); + } + } + + /** + * 工号是否存在 + * + * @param gh 工号 + * @return 是否存在 + */ + private boolean isGhExists(String gh) { + return baseMapper.lambdaQuery().eq(PeopleDO::getGh, gh).exists(); + } + + /** + * 门禁卡号是否存在 + * + * @param doorNo 门禁卡号 + * @return 是否存在 + */ + private boolean isDoorNoExists(String doorNo) { + return baseMapper.lambdaQuery().eq(PeopleDO::getDoorNo, doorNo).exists(); + } + + @Override + public void beforeUpdate(PeopleReq req, Long id) { + if (ObjectUtil.isNotEmpty(req.getAvatar()) && ObjectUtil.isEmpty(req.getFaceGuid())) { + req.setFaceGuid(IdUtil.simpleUUID()); + } + PeopleDO peopleDO = baseMapper.lambdaQuery().eq(PeopleDO::getId, req.getId()).select().one(); + //入参重复数据校验 + if (StrUtil.isNotBlank(req.getGh()) && !StrUtil.equals(peopleDO.getGh(), req.getGh())) { + CheckUtils.throwIf(isGhExists(req.getGh()), "修改失败,工号[{}] 已存在", req.getGh()); + } + if (StrUtil.isNotBlank(req.getDoorNo()) && !StrUtil.equals(peopleDO.getDoorNo(), req.getDoorNo())) { + CheckUtils.throwIf(isDoorNoExists(req.getDoorNo()), "修改失败,门禁卡号[{}] 已存在", req.getDoorNo()); + } + //如果修改了人员姓名,电话,照片,身份证号 则需要下发设备 + if ((StrUtil.isNotBlank(req.getName()) && !StrUtil.equals(peopleDO.getName(), req.getName()) || StrUtil + .isNotBlank(req.getPhone()) && !StrUtil.equals(peopleDO.getPhone(), req.getPhone())) || StrUtil + .isNotBlank(req.getAvatar()) && !StrUtil.equals(peopleDO.getAvatar(), req.getAvatar()) || StrUtil + .isNotBlank(req.getIdcard()) && !StrUtil.equals(peopleDO.getIdcard(), req.getIdcard())) { + req.setNeedDown(true); + } + } + + @Override + public void afterUpdate(PeopleReq sysPeople, PeopleDO peopleDO) { + //是否开启人脸机 + if (checkYFEnable() && sysPeople.getNeedDown()) { + sysYFService.authDevice(Collections.singletonList(peopleDO.getId()), OperTypeEnum.UPDATE.getValue()); + } + } + + @Override + public String del(List ids) { + List failIds; + String failNames = ""; + //是否开启人脸机 + if (checkYFEnable()) { + failIds = sysYFService.delDevice(ids, OperTypeEnum.DEL.getValue()); + if (CollUtil.isNotEmpty(failIds)) { + log.info("人员删除-人员[{}]删除失败", JSON.toJSONString(failIds)); + ids = ids.stream().filter(id -> !failIds.contains(id)).toList(); + } + } else { + failIds = new ArrayList<>(); + } + //删除人员信息 + if (CollUtil.isNotEmpty(ids)) { + baseMapper.deleteByIds(ids); + //从下发记录里删去相关记录 + peopleDownMapper.delete(new LambdaQueryWrapper().in(PeopleDownDO::getPeopleId, ids)); + } + + if (CollUtil.isNotEmpty(failIds)) { + failNames = baseMapper.selectList(new LambdaQueryWrapper().in(PeopleDO::getId, failIds)) + .stream() + .map(PeopleDO::getName) + .collect(Collectors.joining(",")); + } + + return failNames; + } + + private Boolean checkYFEnable() { + String configValue = configService.getConfigValue("yf.enable"); + if (StrUtil.isNotBlank(configValue)) { + return Boolean.valueOf(configValue); + } + return false; + } + + @Override + public PeopleImportParseResp parseImport(MultipartFile file) { + PeopleImportParseResp peopleImportResp = new PeopleImportParseResp(); + List importRowList; + // 读取表格数据 + try { + importRowList = EasyExcel.read(file.getInputStream()) + .head(PeopleImportRowReq.class) + .sheet() + .headRowNumber(1) + .doReadSync(); + } catch (Exception e) { + log.error("用户导入数据文件解析异常:{}", e.getMessage(), e); + throw new BusinessException("数据文件解析异常"); + } + // 总计行数 + peopleImportResp.setTotalRows(importRowList.size()); + CheckUtils.throwIfEqual(importRowList.size(), 0, "导入人员为空!"); + // 有效行数:过滤无效数据,过滤重复(姓名+手机号重复)数据 + List validRowList = this.filterImportData(importRowList); + CheckUtils.throwIfEqual(validRowList.size(), 0, "文件数据异常"); + peopleImportResp.setValidRows(validRowList.size()); + + // 检测表格内数据是否合法 + Set seenGhs = new HashSet<>(); + boolean hasDuplicateGh = importRowList.stream() + .map(PeopleImportRowReq::getGh) + .anyMatch(gh -> gh != null && !seenGhs.add(gh)); + CheckUtils.throwIf(hasDuplicateGh, "存在重复工号,请检查数据"); + Set seenDNs = new HashSet<>(); + boolean hasDuplicateDN = importRowList.stream() + .map(PeopleImportRowReq::getDoorNo) + .anyMatch(DN -> DN != null && !seenDNs.add(DN)); + CheckUtils.throwIf(hasDuplicateDN, "存在重复卡号,请检查数据"); + //手机号查重暂不需要 + // Set seenPhones = new HashSet<>(); + // boolean hasDuplicatePhone = validRowList.stream() + // .map(UserImportRowReq::getPhone) + // .anyMatch(phone -> phone != null && !seenPhones.add(phone)); + // CheckUtils.throwIf(hasDuplicatePhone, "存在重复手机,请检测数据"); + + // 查询重复用户,同名忽略 + // peopleImportResp + // .setDuplicatePeopleRows(countExistByField(validRowList, PeopleImportRowReq::getName, PeopleDO::getName, false)); + // 查询重复工号 + // peopleImportResp + // .setDuplicateEmailRows(countExistByField(validRowList, PeopleImportRowReq::getGh, PeopleDO::getGh, true)); + // 查询重复卡号 + // peopleImportResp + // .setDuplicatePhoneRows(countExistByField(validRowList, PeopleImportRowReq::getDoorNo, PeopleDO::getDoorNo, true)); + + // 查询重复身份证号码->重复人员 + peopleImportResp + .setDuplicatePeopleRows(countExistByField(validRowList, PeopleImportRowReq::getIdcard, PeopleDO::getIdcard, false)); + + // 设置导入会话并缓存数据,有效期10分钟 + String importKey = UUID.fastUUID().toString(true); + RedisUtils.set(CacheConstants.DATA_IMPORT_KEY + importKey, JSONUtil.toJsonStr(importRowList), Duration + .ofMinutes(10)); + peopleImportResp.setImportKey(importKey); + return peopleImportResp; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public PeopleImportResp importPeople(PeopleImportReq req) { + // 校验导入会话是否过期 + List importUserList; + try { + String data = RedisUtils.get(CacheConstants.DATA_IMPORT_KEY + req.getImportKey()); + importUserList = JSONUtil.toList(data, PeopleImportRowReq.class); + CheckUtils.throwIf(CollUtil.isEmpty(importUserList), "导入已过期,请重新上传"); + } catch (Exception e) { + log.error("导入异常:", e); + throw new BusinessException("导入已过期,请重新上传"); + } + //通过身份证获取重复人员 id,name,idcard + List existUserList = listByUsernames(importUserList.stream() + .map(PeopleImportRowReq::getIdcard) + .filter(Objects::nonNull) + .toList()); + List existPeopleIdCards = existUserList.stream().map(PeopleDO::getIdcard).toList(); + + CheckUtils.throwIf(isExitImportUser(req, importUserList, existPeopleIdCards), "数据不符合导入策略,已退出导入"); + + // 基础数据准备 + //设置重复人员身份证与id的映射,方便处理 + Map peopleIdMap = existUserList.stream() + .collect(Collectors.toMap(PeopleDO::getIdcard, PeopleDO::getId)); + List branchList = branchService.listByNames(importUserList.stream() + .map(PeopleImportRowReq::getBranchName) + .distinct() + .toList()); + Map branchMap = branchList.stream() + .collect(Collectors.toMap(BranchDO::getName, BranchDO::getId)); + + // 批量操作数据库集合 + List insertList = new ArrayList<>(); + List updateList = new ArrayList<>(); + for (PeopleImportRowReq row : importUserList) { + if (isSkipUserImport(req, row, existPeopleIdCards)) { + // 按规则跳过该行 + continue; + } + PeopleDO peopleDO = BeanUtil.toBeanIgnoreError(row, PeopleDO.class); + peopleDO.setBranchId(branchMap.get(row.getBranchName())); + // 修改 or 新增 + if (UPDATE.validate(req.getDuplicateUser(), row.getIdcard(), existPeopleIdCards)) { + peopleDO.setId(peopleIdMap.get(row.getIdcard())); + updateList.add(peopleDO); + } else { + insertList.add(peopleDO); + } + } + doImportUser(insertList, updateList); + handleDown(insertList, updateList); + RedisUtils.delete(CacheConstants.DATA_IMPORT_KEY + req.getImportKey()); + return new PeopleImportResp(insertList.size() + updateList.size(), insertList.size(), updateList.size()); + } + + @Override + public PageResp pageConsume(PeopleQuery query, PageQuery pageQuery) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + //初始部门不查询下级部门 + if (StrUtil.isNotBlank(query.getBranchId()) && !StrUtil.equals(query.getBranchId(), "1")) { + List branchResps = branchMapper.queryJuniorBranch(query.getBranchId()); + List branchList = branchResps.stream().map(BranchResp::getId).collect(Collectors.toList()); + branchList.add(query.getBranchId()); + queryWrapper.in("p.branch_id", branchList); + } + + //查询条件 + queryWrapper.like(StrUtil.isNotBlank(query.getName()), "p.name", query.getName()); + queryWrapper.like(StrUtil.isNotBlank(query.getPhone()), "p.phone", query.getPhone()); + queryWrapper.likeRight(StrUtil.isNotBlank(query.getIdcard()), "p.idcard", query.getIdcard()); + queryWrapper.eq(StrUtil.isNotBlank(query.getGh()), "p.gh", query.getGh()); + queryWrapper.eq(Objects.nonNull(query.getIsConsume()), "p.is_consume", query.getIsConsume()); + queryWrapper.eq(Objects.nonNull(query.getFreeze()), "p.freeze", query.getFreeze()); + queryWrapper.orderByDesc("p.create_time", "p.id"); + + IPage page = baseMapper.selectPageConsume(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + + return PageResp.build(page); + } + + /** + * 消费-人员充值导出 + * + * @param query + * @return + */ + @Override + public void exportConsume(PeopleQuery query, HttpServletResponse response) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + //初始部门不查询下级部门 + if (StrUtil.isNotBlank(query.getBranchId()) && !StrUtil.equals(query.getBranchId(), "1")) { + List branchResps = branchMapper.queryJuniorBranch(query.getBranchId()); + List branchList = branchResps.stream().map(BranchResp::getId).collect(Collectors.toList()); + branchList.add(query.getBranchId()); + queryWrapper.in("p.branch_id", branchList); + } + + //查询条件 + queryWrapper.like(StrUtil.isNotBlank(query.getName()), "p.name", query.getName()); + queryWrapper.like(StrUtil.isNotBlank(query.getPhone()), "p.phone", query.getPhone()); + queryWrapper.likeRight(StrUtil.isNotBlank(query.getIdcard()), "p.idcard", query.getIdcard()); + queryWrapper.eq(StrUtil.isNotBlank(query.getGh()), "p.gh", query.getGh()); + queryWrapper.eq(Objects.nonNull(query.getIsConsume()), "p.is_consume", query.getIsConsume()); + queryWrapper.eq(Objects.nonNull(query.getFreeze()), "p.freeze", query.getFreeze()); + queryWrapper.orderByDesc("p.create_time", "p.id"); + List peopleRespList = baseMapper.selectExportConsume(queryWrapper); + + ExcelUtils.export(peopleRespList, "人员充值清单_" + DateUtil.today(), ConsumePeopleResp.class, response); + } + + /** + * 消费冻结 + * + * @param ids + */ + @Override + public void freeze(List ids) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper().in(PeopleDO::getId, ids); + PeopleDO peopleDO = new PeopleDO(); + peopleDO.setFreeze(YesNoEnum.YES.getValue()); + baseMapper.update(peopleDO, wrapper); + } + + /** + * 消费解冻 + * + * @param ids + */ + @Override + public void unFreeze(List ids) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper().in(PeopleDO::getId, ids); + PeopleDO peopleDO = new PeopleDO(); + peopleDO.setFreeze(YesNoEnum.NO.getValue()); + baseMapper.update(peopleDO, wrapper); + } + + /** + * 消费一键清零 + * + * @param req + */ + @Override + public void resetMoney(PeopleReq req) { + if (null == req.getResetMoneyType() || null == req.getResetMoneyAll()) { + throw new IllegalArgumentException("请选择重置类型和重置范围"); + } + + PeopleDO peopleDO = new PeopleDO(); + if (ObjectUtil.equals(0, req.getResetMoneyType())) { + peopleDO.setCzje(BigDecimal.ZERO); + peopleDO.setBtje(BigDecimal.ZERO); + } else if (ObjectUtil.equals(1, req.getResetMoneyType())) { + peopleDO.setCzje(BigDecimal.ZERO); + } else if (ObjectUtil.equals(2, req.getResetMoneyType())) { + peopleDO.setBtje(BigDecimal.ZERO); + } + + List rechargeRecordDOList = new ArrayList<>(); + UpdateWrapper wrapper = new UpdateWrapper<>(); + // 0:不重置所有人员,1:重置所有人员 + if (ObjectUtil.equals(0, req.getResetMoneyAll()) && CollectionUtil.isNotEmpty(req.getIds())) { + wrapper.in("id", req.getIds()); + + // 保存清零记录 + List peopleDOList = baseMapper.selectByIds(req.getIds()); + peopleDOList.forEach(people -> { + //充值记录 + RechargeRecordDO rechargeRecordDO = new RechargeRecordDO(); + rechargeRecordDO.setAccountId(people.getId()); + rechargeRecordDO.setEmpId(people.getGh()); + if (ObjectUtil.equals(0, req.getResetMoneyType())) { + //清零(全部) + rechargeRecordDO.setRechargeType(ConsumeRechargeTypeEnum.CLEAR.getValue()); + rechargeRecordDO.setClearBtje(people.getBtje()); + rechargeRecordDO.setClearCzje(people.getCzje()); + } else if (ObjectUtil.equals(1, req.getResetMoneyType())) { + //清零(充值) + rechargeRecordDO.setRechargeType(ConsumeRechargeTypeEnum.CLEAR_CZ.getValue()); + rechargeRecordDO.setClearCzje(people.getCzje()); + } else if (ObjectUtil.equals(2, req.getResetMoneyType())) { + //清零(补贴) + rechargeRecordDO.setRechargeType(ConsumeRechargeTypeEnum.CLEAR_BT.getValue()); + rechargeRecordDO.setClearBtje(people.getBtje()); + } + rechargeRecordDO.setRechargeWay(ConsumeRechargeWayEnum.WEB.getValue()); + rechargeRecordDOList.add(rechargeRecordDO); + }); + } else { + //全表更新 + wrapper.eq("1", "1"); + + //全部清零,记录一条数据 + RechargeRecordDO rechargeRecordDO = new RechargeRecordDO(); + rechargeRecordDO.setAccountId(0L); + rechargeRecordDO.setEmpId("0"); + rechargeRecordDO.setRechargeWay(ConsumeRechargeWayEnum.WEB.getValue()); + rechargeRecordDO.setRechargeType(ConsumeRechargeTypeEnum.CLEAR.getValue()); + rechargeRecordDOList.add(rechargeRecordDO); + } + baseMapper.update(peopleDO, wrapper); + + //充值记录 + rechargeRecordMapper.insertBatch(rechargeRecordDOList); + } + + + /** + * 补贴人员 + * + * @param req 补贴参数 + * @return 补贴结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void subsidy(PeopleReq req) { + // 校验参数 + CheckUtils.throwIf(CollectionUtil.isEmpty(req.getIds()), "请选择人员"); + CheckUtils.throwIf(req.getMoney() == null || req.getMoney().compareTo(BigDecimal.ZERO) <= 0, "请输入正确的大于0的金额"); + + List peopleDOList = baseMapper.selectByIds(req.getIds()); + List rechargeRecordDOList = new ArrayList<>(); + peopleDOList.forEach(peopleDO -> { + peopleDO.setBtje(peopleDO.getBtje().add(req.getMoney())); + + //充值记录 + RechargeRecordDO rechargeRecordDO = new RechargeRecordDO(); + rechargeRecordDO.setAccountId(peopleDO.getId()); + rechargeRecordDO.setEmpId(peopleDO.getGh()); + rechargeRecordDO.setConsumeMoney(req.getMoney()); + rechargeRecordDO.setRechargeWay(ConsumeRechargeWayEnum.WEB.getValue()); + rechargeRecordDO.setRechargeType(ConsumeRechargeTypeEnum.SUBSIDY.getValue()); + rechargeRecordDOList.add(rechargeRecordDO); + }); + + this.baseMapper.updateBatchById(peopleDOList); + + //充值记录 + rechargeRecordMapper.insertBatch(rechargeRecordDOList); + } + + /** + * 充值人员 + * + * @param req 充值参数 + * @return 充值结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deposit(PeopleReq req) { + // 校验参数 + CheckUtils.throwIf(CollectionUtil.isEmpty(req.getIds()), "请选择人员"); + CheckUtils.throwIf(req.getMoney() == null || req.getMoney().compareTo(BigDecimal.ZERO) <= 0, "请输入正确的大于0的金额"); + + List peopleDOList = baseMapper.selectByIds(req.getIds()); + List rechargeRecordDOList = new ArrayList<>(); + peopleDOList.forEach(peopleDO -> { + peopleDO.setCzje(peopleDO.getCzje().add(req.getMoney())); + + //充值记录 + RechargeRecordDO rechargeRecordDO = new RechargeRecordDO(); + rechargeRecordDO.setAccountId(peopleDO.getId()); + rechargeRecordDO.setEmpId(peopleDO.getGh()); + rechargeRecordDO.setConsumeMoney(req.getMoney()); + rechargeRecordDO.setRechargeWay(ConsumeRechargeWayEnum.WEB.getValue()); + rechargeRecordDO.setRechargeType(ConsumeRechargeTypeEnum.RECHARGE.getValue()); + rechargeRecordDOList.add(rechargeRecordDO); + }); + + this.baseMapper.updateBatchById(peopleDOList); + + //充值记录 + rechargeRecordMapper.insertBatch(rechargeRecordDOList); + } + + /** + * 退款人员 + * + * @param req 退款参数 + * @return 退款结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void refund(PeopleReq req) { + // 校验参数 + CheckUtils.throwIf(null == req.getId(), "请选择人员"); + CheckUtils.throwIf(req.getMoney() == null || req.getMoney().compareTo(BigDecimal.ZERO) <= 0, "请输入正确的大于0的金额"); + + PeopleDO peopleDO = baseMapper.selectById(req.getId()); + CheckUtils.throwIf(peopleDO.getCzje().compareTo(req.getMoney()) < 0, "人员余额不足"); + peopleDO.setCzje(peopleDO.getCzje().subtract(req.getMoney())); + baseMapper.updateById(peopleDO); + + //充值记录 + RechargeRecordDO rechargeRecordDO = new RechargeRecordDO(); + rechargeRecordDO.setAccountId(peopleDO.getId()); + rechargeRecordDO.setEmpId(peopleDO.getGh()); + rechargeRecordDO.setConsumeMoney(req.getMoney()); + rechargeRecordDO.setRechargeWay(ConsumeRechargeWayEnum.WEB.getValue()); + rechargeRecordDO.setRechargeType(ConsumeRechargeTypeEnum.REFUND.getValue()); + rechargeRecordMapper.insert(rechargeRecordDO); + } + + /** + * 过滤无效的导入用户数据(批量导入不严格校验数据) + * + * @param importRowList 导入数据 + */ + private List filterImportData(List importRowList) { + // 校验过滤 + // List list = importRowList.stream() + // .filter(row -> ValidationUtil.validate(row).isEmpty()) + // .toList(); + // 用户名+手机号去重 + return importRowList.stream() + .collect(Collectors.toMap(row -> row.getName() + "_" + row.getPhone(), user -> user, (existing, + replacement) -> existing)) + .values() + .stream() + .toList(); + } + + /** + * 按指定数据集获取数据库已存在的数量 + * + * @param peopleRowList 导入的数据源 + * @param rowField 导入数据的字段 + * @param dbField 对比数据库的字段 + * @return 存在的数量 + */ + private int countExistByField(List peopleRowList, + Function rowField, + SFunction dbField, + boolean fieldEncrypt) { + List fieldValues = peopleRowList.stream().map(rowField).filter(Objects::nonNull).toList(); + if (fieldValues.isEmpty()) { + return 0; + } + return (int)this.count(Wrappers.lambdaQuery() + .in(dbField, fieldEncrypt ? SecureUtils.encryptFieldByAes(fieldValues) : fieldValues)); + } + + /** + * 导入用户 + * + * @param insertList 新增用户 + * @param updateList 修改用户 + */ + private void doImportUser(List insertList, List updateList) { + if (CollUtil.isNotEmpty(insertList)) { + for (PeopleDO peopleDO : insertList) { + peopleDO.setGuid(IdUtil.simpleUUID()); + } + baseMapper.insert(insertList); + } + if (CollUtil.isNotEmpty(updateList)) { + SpringUtils.getProxy(this).updateBatchById(updateList); + } + } + + /** + * 根据人员身份证获取用户列表 + * + * @param peopleIdCards 人员身份证列表 + * @return 用户列表 + */ + private List listByUsernames(List peopleIdCards) { + return this.list(Wrappers.lambdaQuery() + .in(PeopleDO::getIdcard, peopleIdCards) + .select(PeopleDO::getId, PeopleDO::getIdcard, PeopleDO::getId)); + } + + /** + * 判断是否退出导入 + * + * @param req 导入参数 + * @param list 导入数据 + * @param existPeopleIdCards 导入数据中已存在的人员身份证 + * @return 是否退出 + */ + private boolean isExitImportUser(PeopleImportReq req, + List list, + List existPeopleIdCards) { + return list.stream() + .anyMatch(row -> EXIT.validate(req.getDuplicateUser(), row.getIdcard(), existPeopleIdCards)); + } + + /** + * 判断是否跳过导入 + * + * @param req 导入参数 + * @param row 导入数据 + * @param existPeopleIdCards 导入数据中已存在的用户名 + * @return 是否跳过 + */ + private boolean isSkipUserImport(PeopleImportReq req, PeopleImportRowReq row, List existPeopleIdCards) { + return SKIP.validate(req.getDuplicateUser(), row.getIdcard(), existPeopleIdCards); + } + + /** + * + * @param insertList 导入的新增名单 + * @param updateList 导入的修改名单 + */ + private void handleDown(List insertList, List updateList) { + //新增人员添加下发记录 + for (PeopleDO peopleDO : insertList) { + //从表中拿更详细的数据 + PeopleDO people = baseMapper.lambdaQuery() + .eq(PeopleDO::getName, peopleDO.getName()) + .eq(PeopleDO::getIdcard, peopleDO.getIdcard()) + .one(); + //获取部门绑定规则 + List branchRules = branchRuleMapper.lambdaQuery() + .eq(BranchRuleDO::getBranchId, people.getBranchId()) + .list(); + CheckUtils.throwIfEmpty(branchRules, people.getName() + "所在部门暂未绑定规则"); + for (BranchRuleDO branchRule : branchRules) { + Long ruleId = branchRule.getRuleId(); + //获取规则绑定设备 + List ruleRelationS = ruleRelationMapper.lambdaQuery() + .eq(RuleRelationDO::getRuleId, ruleId) + .list(); + PeopleDownReq peopleDownReq = new PeopleDownReq(); + peopleDownReq.setPeopleId(people.getId()); + peopleDownReq.setRuleId(ruleId); + peopleDownReq.setDownResult(1); + peopleDownReq.setOperType(0); + peopleDownReq.setMsg("人员导入"); + for (RuleRelationDO ruleRelation : ruleRelationS) { + peopleDownReq.setDownTime(new Date()); + peopleDownReq.setEquipmentId(ruleRelation.getEquipmentId()); + peopledownService.upsertRecord(peopleDownReq); + } + } + + } + //修改人员增加下发记录 + for (PeopleDO peopleDO : updateList) { + //从表中拿更详细的数据 + PeopleDO people = baseMapper.lambdaQuery() + .eq(PeopleDO::getName, peopleDO.getName()) + .eq(PeopleDO::getIdcard, peopleDO.getIdcard()) + .one(); + //获取部门绑定规则 + List branchRules = branchRuleMapper.lambdaQuery() + .eq(BranchRuleDO::getBranchId, people.getBranchId()) + .list(); + CheckUtils.throwIfEmpty(branchRules, people.getName() + "所在部门暂未绑定规则"); + for (BranchRuleDO branchRule : branchRules) { + Long ruleId = branchRule.getRuleId(); + //获取规则绑定设备 + List ruleRelationS = ruleRelationMapper.lambdaQuery() + .eq(RuleRelationDO::getRuleId, ruleId) + .list(); + PeopleDownReq peopleDownReq = new PeopleDownReq(); + peopleDownReq.setPeopleId(people.getId()); + peopleDownReq.setRuleId(ruleId); + peopleDownReq.setDownResult(1); + peopleDownReq.setOperType(1); + peopleDownReq.setMsg("人员导入"); + for (RuleRelationDO ruleRelation : ruleRelationS) { + peopleDownReq.setDownTime(new Date()); + peopleDownReq.setEquipmentId(ruleRelation.getEquipmentId()); + peopledownService.upsertRecord(peopleDownReq); + } + } + + } + } + + @Override + @ContainerMethod(namespace = ContainerConstants.PEOPLE_NAME, type = MappingType.ORDER_OF_KEYS) + public String getPeopleName(Long id) { + if (null == id) { + return ""; + } + + PeopleDO peopleDO = baseMapper.lambdaQuery().eq(PeopleDO::getId, id).one(); + return peopleDO != null ? peopleDO.getName() : ""; + } + + @Override + public List getPeopleNameList(PeopleQuery peopleQuery) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(StrUtil.isNotBlank(peopleQuery.getBranchId()), PeopleDO::getBranchId, peopleQuery + .getBranchId()); + queryWrapper.select(PeopleDO::getId, PeopleDO::getName); + List peopleDOS = baseMapper.selectList(queryWrapper); + return peopleConvert.labelValueList(peopleDOS); + } + + + @Override + public void downloadConsumeImportTemplate(HttpServletResponse response) throws IOException { + try { + FileUploadUtils.download(response, ResourceUtil.getStream("templates/import/consumePeopleDeposit.xlsx"), "人员充值导入模板.xlsx"); + } catch (Exception e) { + log.error("下载人员充值导入模板失败:{}", e.getMessage(), e); + response.setCharacterEncoding(CharsetUtil.UTF_8); + response.setContentType(ContentType.JSON.toString()); + response.getWriter().write(JSONUtil.toJsonStr(R.fail("下载人员充值导入模板失败"))); + } + } + + @Override + public PeopleDepositImportParseResp parseConsumeImport(MultipartFile file) { + PeopleDepositImportParseResp userImportResp = new PeopleDepositImportParseResp(); + List importRowList; + // 读取表格数据 + try { + importRowList = EasyExcel.read(file.getInputStream()) + .head(PeopleDepositImportRowReq.class) + .sheet() + .headRowNumber(1) + .doReadSync(); + } catch (Exception e) { + log.error("用户导入数据文件解析异常:{}", e.getMessage(), e); + throw new BusinessException("数据文件解析异常"); + } + // 总计行数 + userImportResp.setTotalRows(importRowList.size()); + CheckUtils.throwIfEmpty(importRowList, "数据文件格式错误"); + // 有效行数:过滤无效数据 + List validRowList = this.filterConsumeImportData(importRowList); + CheckUtils.throwIfEmpty(validRowList, "数据文件格式错误"); + + // 检测表格内数据是否合法 + Set seenGhs = new HashSet<>(); + boolean hasDuplicateGh = validRowList.stream() + .map(PeopleDepositImportRowReq::getGh) + .anyMatch(gh -> gh != null && !seenGhs.add(gh)); + CheckUtils.throwIf(hasDuplicateGh, "存在重复工号,请检测数据"); + + + // 查询重复用户 + int duplicateGhRows = 0; + List fieldValues = validRowList.stream().map(PeopleDepositImportRowReq::getGh).filter(Objects::nonNull).toList(); + if (CollUtil.isNotEmpty(fieldValues)) { + int peopleCount = (int)this.count(Wrappers.lambdaQuery() + .in(PeopleDO::getGh, fieldValues)); + duplicateGhRows = fieldValues.size() - peopleCount; + } + userImportResp.setValidRows(validRowList.size() - duplicateGhRows); + userImportResp.setDuplicateUserRows(duplicateGhRows); + + // 查询未启用消费人员数量 + int unEnabledRows = 0; + List fieldValues2 = validRowList.stream().filter(row -> "禁用".equals(row.getIsConsume())).toList(); + if (CollUtil.isNotEmpty(fieldValues2)) { + unEnabledRows = fieldValues2.size(); + } + userImportResp.setUnEnabledRows(unEnabledRows); + + // 设置导入会话并缓存数据,有效期10分钟 + String importKey = UUID.fastUUID().toString(true); + RedisUtils.set(CacheConstants.DATA_IMPORT_KEY + importKey, JSONUtil.toJsonStr(validRowList), Duration + .ofMinutes(10)); + userImportResp.setImportKey(importKey); + return userImportResp; + } + + /** + * 过滤无效的导入用户数据 + * + * @param importRowList 导入数据 + */ + private List filterConsumeImportData(List importRowList) { + // 校验过滤 + return importRowList.stream() + .filter(row -> ValidationUtil.validate(row).isEmpty()) + .toList(); + } + + + + + @Override + @Transactional(rollbackFor = Exception.class) + public UserImportResp importConsumePeople(PeopleDepositImportReq req) { + // 校验导入会话是否过期 + List importUserList; + try { + String data = RedisUtils.get(CacheConstants.DATA_IMPORT_KEY + req.getImportKey()); + importUserList = JSONUtil.toList(data, PeopleDepositImportRowReq.class); + CheckUtils.throwIf(CollUtil.isEmpty(importUserList), "导入已过期,请重新上传"); + } catch (Exception e) { + log.error("导入异常:", e); + throw new BusinessException("导入已过期,请重新上传"); + } + + //不存在得人员 + List fieldValues = importUserList.stream().map(PeopleDepositImportRowReq::getGh).filter(Objects::nonNull).toList(); + List peopleNotExistGhs = new ArrayList<>(); + List peopleList = new ArrayList<>(); + if (CollUtil.isNotEmpty(fieldValues)) { + peopleList = baseMapper.selectList(new QueryWrapper().in("gh",fieldValues)); + List finalPeopleList = peopleList; + List collect = finalPeopleList.stream().map(PeopleDO::getGh).toList(); + peopleNotExistGhs = fieldValues.stream().filter(gh -> !collect.contains(gh)).toList(); + } + + + //禁用消费得人员 + List consumeNotEnable = importUserList.stream().map(PeopleDepositImportRowReq::getIsConsume).filter("禁用"::equals).toList(); + + CheckUtils.throwIf(errorImport(req, importUserList, peopleNotExistGhs, consumeNotEnable), "数据异常,已停止导入!"); + + Map peopleIdMap = peopleList.stream().collect(Collectors.toMap(PeopleDO::getGh, PeopleDO::getId, (people1, people2) -> people1)); + List updateList = new ArrayList<>(); + for(PeopleDepositImportRowReq row : importUserList) { + if (isSkipImport(req, row, peopleNotExistGhs, consumeNotEnable)) { + // 按规则跳过该行 + continue; + } + + PeopleDO peopleDO = new PeopleDO(); + //是否启用消费 + if (UPDATE.equals(req.getConsumeNotEnable())) { + peopleDO.setIsConsume(EnableEnum.ENABLE.getValue()); + } + peopleDO.setCzje(new BigDecimal(row.getCzje())); + peopleDO.setBtje(new BigDecimal(row.getBtje())); + peopleDO.setFreeze(YesNoEnum.getByDescription(row.getFreeze()).getValue()); + peopleDO.setId(peopleIdMap.get(row.getGh())); + updateList.add(peopleDO); + } + + SpringUtils.getProxy(this).updateBatchById(updateList); + + RedisUtils.delete(CacheConstants.DATA_IMPORT_KEY + req.getImportKey()); + return new UserImportResp(importUserList.size(), 0, updateList.size()); + } + + private boolean errorImport(PeopleDepositImportReq req, + List list, + List peopleNotExistGhs, + List consumeNotEnable) { + return list.stream() + .anyMatch(row -> EXIT.validate(req.getPeopleNotExist(), row.getGh(), peopleNotExistGhs) || EXIT + .validate(req.getConsumeNotEnable(), row.getIsConsume(), consumeNotEnable)); + } + + private boolean isSkipImport(PeopleDepositImportReq req, + PeopleDepositImportRowReq row, + List peopleNotExistGhs, + List consumeNotEnable) { + return SKIP.validate(req.getPeopleNotExist(), row.getGh(), peopleNotExistGhs) || SKIP.validate(req + .getConsumeNotEnable(), row.getIsConsume(), consumeNotEnable); + } + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/mapper/PeopleDownMapper.java b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/mapper/PeopleDownMapper.java new file mode 100644 index 0000000..375d317 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/mapper/PeopleDownMapper.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.peopleDownRecord.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.peopleDownRecord.model.entity.PeopleDownDO; +import top.ysoft.admin.peopleDownRecord.model.req.PeopleDownReq; +import top.ysoft.admin.peopleDownRecord.model.resp.PeopleDownResp; + +import java.util.List; + +@Repository +public interface PeopleDownMapper extends BaseMapper { + + /** + * 新增或更新人员下发记录 + * + * @param record + * @return + */ + int upsert(PeopleDownReq record); + + /** + * 导出人员下发记录 + * + * @param queryWrapper + * @return + */ + List selectListExport(@Param(Constants.WRAPPER) QueryWrapper queryWrapper); + + /** + * 分页查询人员下发记录 + * + * @param page + * @param queryWrapper + * @return + */ + IPage selectDownPage(@Param("page") IPage page, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/mapstruct/PeopleDownConvert.java b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/mapstruct/PeopleDownConvert.java new file mode 100644 index 0000000..8f17202 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/mapstruct/PeopleDownConvert.java @@ -0,0 +1,8 @@ +package top.ysoft.admin.peopleDownRecord.mapstruct; + +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface PeopleDownConvert { + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/entity/PeopleDownDO.java b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/entity/PeopleDownDO.java new file mode 100644 index 0000000..9d662b8 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/entity/PeopleDownDO.java @@ -0,0 +1,50 @@ +package top.ysoft.admin.peopleDownRecord.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.continew.starter.extension.crud.model.entity.BaseIdDO; +import java.io.Serial; +import java.time.LocalDateTime; + +@Data +@TableName("equipment_down_record") +public class PeopleDownDO extends BaseIdDO { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + private Long equipmentId; + + /** + * 人员ID + */ + private Long peopleId; + + /** + * 规则id + */ + private Long ruleId; + + /** + * 下发时间 + */ + private LocalDateTime downTime; + + /** + * 下发结果:0-成功,1-失败 + */ + private Integer downResult; + + /** 下发回执 */ + private String msg; + + //操作类型 + private Integer operType; + + private Long createUser; + + private LocalDateTime createTime; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/query/PeopleDownQuery.java b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/query/PeopleDownQuery.java new file mode 100644 index 0000000..586d69a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/query/PeopleDownQuery.java @@ -0,0 +1,52 @@ +package top.ysoft.admin.peopleDownRecord.model.query; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + +@Data +@Schema(description = "下发管理查询条件") +@Builder +public class PeopleDownQuery implements Serializable { + private static final long serialVersionUID = 1L; + + private String id; + + private Long equipmentId; + + private Long peopleId; + + private Long ruleId; + + private Integer downResult; + + private String createUser; + + /** + * 时间段查询起始时间 + */ + @Schema(description = "下发时间开始(格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date downTimeStart; + + /** + * 时间段查询结束时间 + */ + @Schema(description = "下发时间结束(格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date downTimeEnd; + + @Schema(description = "员工姓名") + private String peopleName; + + @Schema(description = "设备名称") + private String equipmentName; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/req/PeopleDownReq.java b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/req/PeopleDownReq.java new file mode 100644 index 0000000..4ecb2b1 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/req/PeopleDownReq.java @@ -0,0 +1,84 @@ +package top.ysoft.admin.peopleDownRecord.model.req; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +@Data +@Schema(description = "创建或修改下发管理参数") +public class PeopleDownReq implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @Schema(description = "主键id") + private Long id; + + /** + * 设备id + */ + @Schema(description = "设备id") + @NotNull(message = "设备id不能为空") + private Long equipmentId; + + @Schema(description = "设备名称") + @NotEmpty(message = "设备名称不能为空") + private String equipmentName; + + @Schema(description = "人员名称") + @NotEmpty(message = "人员名称不能为空") + private String name; + + @Schema(description = "工号") + @NotEmpty(message = "工号不能为空") + private String gh; + + /** + * 人员id + */ + @Schema(description = "人员id") + @NotEmpty(message = "人员id不能为空") + private Long peopleId; + + /** + * 规则id + */ + @Schema(description = "规则id") + @NotNull(message = "规则id不能为空") + private Long ruleId; + + /** + * 下发时间 + */ + @Schema(description = "下发时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date downTime; + + /** + * 下发结果 + */ + @Schema(description = "下发结果('0': 成功 '1':失败)") + @NotEmpty(message = "下发结果不能为空") + private Integer downResult; + + @Schema(description = "下发人") + private Long createUser; + + @Schema(description = "创建时间") + private Date createTime; + + @Schema(description = "备注") + private String msg; + + @Schema(description = "操作类型 0:新增人员 1:修改人员 2:下发人员 3:删除") + private Integer operType = 0; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/resp/PeopleDownResp.java b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/resp/PeopleDownResp.java new file mode 100644 index 0000000..b0d3c47 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/model/resp/PeopleDownResp.java @@ -0,0 +1,83 @@ +package top.ysoft.admin.peopleDownRecord.model.resp; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import org.springframework.format.annotation.DateTimeFormat; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; +import top.ysoft.admin.common.enums.SuccessFailureStatusEnum; +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.time.LocalDateTime; + +@Data +@Schema(description = "下发管理信息") +public class PeopleDownResp extends BaseDetailResp { + + /** + * 设备id + */ + @Schema(description = "设备id") + @Length(max = 25, message = "设备id长度不能超过 {max} 个字符") + @ExcelIgnore + private Long equipmentId; + + /** + * 人员id + */ + @Schema(description = "人员id") + @Length(max = 25, message = "人员id长度不能超过 {max} 个字符") + @ExcelIgnore + private Long peopleId; + + @Schema(description = "规则id") + @ExcelIgnore + private Long ruleId; + + /** + * 下发时间 + */ + @Schema(description = "下发时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ExcelProperty(value = "下发时间", order = 6) + @ColumnWidth(30) + private LocalDateTime downTime; + + /** + * 下发结果 + */ + @Schema(description = "下发结果('0': 成功 '1':失败)") + @ExcelProperty(value = "下发结果", order = 5, converter = ExcelBaseEnumConverter.class) + private SuccessFailureStatusEnum downResult; + + @Schema(description = "备注") + @ExcelIgnore + private String msg; + + @Schema(description = "操作类型 0:新增 1:修改 2:下发") + @ExcelIgnore + private Integer operType; + + @Schema(description = "工号") + @ExcelProperty(value = "工号", order = 2) + @ColumnWidth(20) + private String gh; + + @Schema(description = "员工姓名") + @ExcelProperty(value = "员工姓名", order = 1) + private String peopleName; + + @Schema(description = "设备名称") + @ExcelProperty(value = "设备名称", order = 3) + private String equipmentName; + + @Schema(description = "规则") + @ExcelProperty(value = "规则名称", order = 4) + private String ruleName; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/service/PeopleDownService.java b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/service/PeopleDownService.java new file mode 100644 index 0000000..b3fac71 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/service/PeopleDownService.java @@ -0,0 +1,26 @@ +package top.ysoft.admin.peopleDownRecord.service; + +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.peopleDownRecord.model.query.PeopleDownQuery; +import top.ysoft.admin.peopleDownRecord.model.req.PeopleDownReq; +import top.ysoft.admin.peopleDownRecord.model.resp.PeopleDownResp; + +import java.util.List; + +public interface PeopleDownService extends BaseService { + /** + * 分页查询列表 + * + * @param query 查询条件 + * @param pageQuery 分页查询条件 + * @return 分页列表信息 + */ + PageResp page(PeopleDownQuery query, PageQuery pageQuery); + + public int upsertRecord(PeopleDownReq record); + + public void batchUpsertRecord(List records); + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/service/impl/PeopleDownServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/service/impl/PeopleDownServiceImpl.java new file mode 100644 index 0000000..a8200ad --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/peopleDownRecord/service/impl/PeopleDownServiceImpl.java @@ -0,0 +1,131 @@ +package top.ysoft.admin.peopleDownRecord.service.impl; + +import cn.crane4j.annotation.AutoOperate; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.query.SortQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.continew.starter.file.excel.util.ExcelUtils; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.equipment.model.req.EquipmentReq; +import top.ysoft.admin.equipment.model.resp.EquipmentResp; +import top.ysoft.admin.equipment.service.EquipmentService; +import top.ysoft.admin.peopleBranch.mapper.PeopleMapper; +import top.ysoft.admin.peopleBranch.model.entity.PeopleDO; +import top.ysoft.admin.peopleDownRecord.mapper.PeopleDownMapper; +import top.ysoft.admin.peopleDownRecord.mapstruct.PeopleDownConvert; +import top.ysoft.admin.peopleDownRecord.model.entity.PeopleDownDO; +import top.ysoft.admin.peopleDownRecord.model.query.PeopleDownQuery; +import top.ysoft.admin.peopleDownRecord.model.req.PeopleDownReq; +import top.ysoft.admin.peopleDownRecord.model.resp.PeopleDownResp; +import top.ysoft.admin.peopleDownRecord.service.PeopleDownService; + +import java.util.Date; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Slf4j +public class PeopleDownServiceImpl extends BaseServiceImpl implements PeopleDownService { + + private final PeopleDownConvert peopleDownConvert; + + private final PeopleMapper peopleMapper; + + private final EquipmentService equipmentService; + + @Override + @AutoOperate(type = PeopleDownResp.class, on = "list") + public PageResp page(PeopleDownQuery query, PageQuery pageQuery) { + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(ObjectUtil.isNotEmpty(query.getPeopleName()), "sp.name", query.getPeopleName()); + //查询条件 + queryWrapper.eq(ObjectUtil.isNotEmpty(query.getEquipmentId()), "pd.equipment_id", query.getEquipmentId()); + queryWrapper.eq(ObjectUtil.isNotEmpty(query.getDownResult()), "pd.down_result", query.getDownResult()); + //时间段查询 + if (ObjectUtil.isNotEmpty(query.getDownTimeStart()) && ObjectUtil.isNotEmpty(query.getDownTimeEnd())) { + queryWrapper.between("pd.down_time", query.getDownTimeStart(), query.getDownTimeEnd()); + } + this.sort(queryWrapper, pageQuery); + //执行查询 + IPage page = baseMapper.selectDownPage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + + // 5. 返回给前端 + return PageResp.build(page); + + } + + @Override + public int upsertRecord(PeopleDownReq record) { + + if (ObjectUtil.isEmpty(record.getCreateUser())) { + Long userId = UserContextHolder.getUserId(); + record.setCreateUser(userId); + } + if (ObjectUtil.isEmpty(record.getCreateTime())) { + Date now = new Date(); + record.setCreateTime(now); + } + if (ObjectUtil.isNotEmpty(record.getName()) && ObjectUtil.isNotEmpty(record.getGh())) { + PeopleDO peopleDO = peopleMapper.lambdaQuery() + .eq(PeopleDO::getName, record.getName()) + .eq(PeopleDO::getGh, record.getGh()) + .one(); + CheckUtils.throwIfEmpty(peopleDO, "该人员不存在"); + record.setPeopleId(peopleDO.getId()); + } + if (ObjectUtil.isNotEmpty(record.getEquipmentName())) { + EquipmentReq equipmentReq = new EquipmentReq(); + equipmentReq.setName(record.getEquipmentName()); + EquipmentResp equipmentResp = equipmentService.selectSysEquipment(equipmentReq); + CheckUtils.throwIfNull(equipmentResp, "该设备不存在"); + record.setEquipmentId(equipmentResp.getId()); + } + + return this.baseMapper.upsert(record); + } + + @Override + public void batchUpsertRecord(List records) { + for (PeopleDownReq record : records) { + upsertRecord(record); + } + } + + @Override + public void delete(List peopleIds) { + baseMapper.delete(new QueryWrapper().lambda().in(PeopleDownDO::getPeopleId, peopleIds)); + } + + @Override + public void export(PeopleDownQuery query, SortQuery sortQuery, HttpServletResponse response) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + + queryWrapper.like(ObjectUtil.isNotEmpty(query.getPeopleName()), "sp.name", query.getPeopleName()); + //查询条件 + queryWrapper.eq(ObjectUtil.isNotEmpty(query.getEquipmentId()), "pd.equipment_id", query.getEquipmentId()); + queryWrapper.eq(ObjectUtil.isNotEmpty(query.getDownResult()), "pd.down_result", query.getDownResult()); + //时间段查询 + if (ObjectUtil.isNotEmpty(query.getDownTimeStart()) && ObjectUtil.isNotEmpty(query.getDownTimeEnd())) { + queryWrapper.between("pd.down_time", query.getDownTimeStart(), query.getDownTimeEnd()); + } + this.sort(queryWrapper, sortQuery); + //执行查询 + List list = baseMapper.selectListExport(queryWrapper); + + ExcelUtils.export(list, "人员下发记录_" + DateUtil.today(), PeopleDownResp.class, response); + } + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/mapper/EmpowerRecordMapper.java b/wms-module-system/src/main/java/top/wms/admin/rule/mapper/EmpowerRecordMapper.java new file mode 100644 index 0000000..4c88740 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/mapper/EmpowerRecordMapper.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.rule.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.rule.model.entity.EmpowerRecordDO; +import top.ysoft.admin.rule.model.resp.EmpowerRecordDetailResp; +import top.ysoft.admin.rule.model.resp.EmpowerRecordResp; + +import java.util.List; + +/** + * 授权记录 Mapper + * + * @author zc + * @since 2025/04/08 23:08 + */ +@Repository +public interface EmpowerRecordMapper extends BaseMapper { + IPage selectEmpowerRecordPage(@Param("page") Page page, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); + + List selectForEmpowerDel(@Param("list") List ids); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/mapper/PeopleRecordMapper.java b/wms-module-system/src/main/java/top/wms/admin/rule/mapper/PeopleRecordMapper.java new file mode 100644 index 0000000..0cddb2e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/mapper/PeopleRecordMapper.java @@ -0,0 +1,22 @@ +package top.ysoft.admin.rule.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.rule.model.entity.PeopleRecordDO; +import top.ysoft.admin.rule.model.resp.PeopleRecordResp; + +/** + * 人员识别记录 Mapper + * + * @author zc + * @since 2025/04/08 23:06 + */ +@Repository +public interface PeopleRecordMapper extends BaseMapper { + IPage selectPeopleRecordPage(@Param("page") IPage page, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/mapper/RuleMapper.java b/wms-module-system/src/main/java/top/wms/admin/rule/mapper/RuleMapper.java new file mode 100644 index 0000000..ff65312 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/mapper/RuleMapper.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.rule.mapper; + +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.rule.model.entity.RuleDO; +import top.ysoft.admin.rule.model.resp.YFPeopleResp; + +import java.util.List; + +/** + * 通行规则 Mapper + * + * @author zc + * @since 2025/03/26 22:59 + */ +@Repository +public interface RuleMapper extends BaseMapper { + + List querySysRuleList(@Param("ruleIds") List ruleIds); + + List getEquipmentRulesByPeoples(@Param("peopleIds") List peopleIds); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/mapstruct/RuleConvert.java b/wms-module-system/src/main/java/top/wms/admin/rule/mapstruct/RuleConvert.java new file mode 100644 index 0000000..69e292c --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/mapstruct/RuleConvert.java @@ -0,0 +1,24 @@ +package top.ysoft.admin.rule.mapstruct; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.ysoft.admin.rule.model.entity.RuleDO; +import top.ysoft.admin.rule.model.req.RuleReq; +import top.ysoft.admin.rule.model.resp.RuleResp; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface RuleConvert { + + RuleReq DOToReq(RuleDO ruleDO); + + RuleReq RespToReq(RuleResp resp); + + @Mapping(source = "id", target = "value") + @Mapping(source = "name", target = "label") + LabelValueResp labelValue(RuleDO ruleDO); + + List labelValueList(List ruleDOS); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/entity/EmpowerRecordDO.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/entity/EmpowerRecordDO.java new file mode 100644 index 0000000..cd6dc75 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/entity/EmpowerRecordDO.java @@ -0,0 +1,63 @@ +package top.ysoft.admin.rule.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 授权记录实体 + * + * @author zc + * @since 2025/04/08 23:08 + */ +@Data +@TableName("sys_empower_record") +public class EmpowerRecordDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 姓名 + */ + private String name; + + /** + * 人员id + */ + private String peopleId; + + /** + * 信息下发(0成功 1失败) + */ + private String infoDown; + + /** + * 授权方式(0本地 1云盘) + */ + private String empower; + + /** + * 设备Id + */ + private Long equipmentId; + + /** + * 操作(0启用 1禁用) + */ + private String status; + + /** + * 备注 + */ + private String remark; + + /** + * 规则Id + */ + private Long ruleId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/entity/PeopleRecordDO.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/entity/PeopleRecordDO.java new file mode 100644 index 0000000..3b1c949 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/entity/PeopleRecordDO.java @@ -0,0 +1,165 @@ +package top.ysoft.admin.rule.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; +import java.time.*; +import java.util.Date; + +/** + * 人员识别记录实体 + * + * @author zc + * @since 2025/04/08 23:06 + */ +@Data +@TableName("sys_people_record") +public class PeopleRecordDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 人员id + */ + private Long peopleId; + + /** + * 设备id + */ + private Long equipmentId; + + /** + * 设备内网ip + */ + private String deviceIp; + + /** + * 人员guid 或者STRANGERBABY + */ + private String admitGuid; + + /** + * 识别模式,1:人像识别, 2:刷卡识别 ,3:人卡合一 4,人证比对 7:密码识别 8 二维码识别 + */ + private String recMode; + + /** + * 现场照url + */ + private String filePath; + + /** + * 识别记录时间戳 + */ + private Long showTime; + + /** + * 识别记录时间 + */ + private Date showDate; + + /** + * 活体结果 1:活体判断成功 2:活体判断失败 3:未进行活体判断 + */ + private String aliveType; + + /** + * 识别分数 + */ + private String recScore; + + /** + * 设备序列号 + */ + private String deviceNo; + + /** + * 软件版本号 + */ + private String deviceVersion; + + /** + * 设备来源 + */ + private String source; + + /** + * 人员比对结果,1:比对成功 2:比对失败 + */ + private String type; + + /** + * 识别卡号 + */ + private String cardNo; + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 比对模式,1:本地识别 2:云端识别 + */ + private String recType; + + /** + * 结果 + */ + private String result; + + /** + * 有效日期判断 1:有效期内 2:有效期外 3:未进行有效期判断 + */ + private String permissionTimeType; + + /** + * 有效时间段判断 1:时间段内 2:时间段外 3:未进行时间段判断 + */ + private String passTimeType; + + /** + * 识别模式判断 1. 模式正确 2.模式不正确 + */ + private String recModeType; + + /** + * 保留字段 + */ + private String storageId; + + /** + * 当前时间戳 + */ + private Long timestamp; + + /** + * 识别主体姓名 + */ + private String admitName; + + /** + * 备注 + */ + private String remark; + + /** + * 人员记录或访客记录标示 + */ + private String flag; + + /** + * 考勤组id + */ + private Long groupId; + + /** + * 下级系统标志 + */ + private String sign; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/entity/RuleDO.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/entity/RuleDO.java new file mode 100644 index 0000000..385425d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/entity/RuleDO.java @@ -0,0 +1,75 @@ +package top.ysoft.admin.rule.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; +import java.time.*; + +/** + * 通行规则实体 + * + * @author zc + * @since 2025/03/26 22:58 + */ +@Data +@TableName("sys_rule") +public class RuleDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 规则名称 + */ + private String name; + + /** + * 有效期开始 + */ + private LocalDateTime startTime; + + /** + * 有效期结束 + */ + private LocalDateTime endTime; + + /** + * 准入时间起 + */ + private String admittanceStart; + + /** + * 准入时间止 + */ + private String admittanceEnd; + + /** + * 设备空间 + */ + private Long spaceId; + + /** + * 设备位置 + */ + private String pointId; + + /** + * 备注 + */ + private String remark; + + /** + * 1:本地库 2:云端库 默认本地 + */ + private String type; + + /** + * facePermission 刷脸权限 idCardPermission 刷卡权限 faceAndCardPermission 人卡合一权限 idCardFacePermission 人证比对权限 + * passwordPermission 密码权限 + */ + private String permission; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/query/EmpowerRecordQuery.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/query/EmpowerRecordQuery.java new file mode 100644 index 0000000..4956351 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/query/EmpowerRecordQuery.java @@ -0,0 +1,33 @@ +package top.ysoft.admin.rule.model.query; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 授权记录查询条件 + * + * @author zc + * @since 2025/04/08 23:08 + */ +@Data +@Schema(description = "授权记录查询条件") +public class EmpowerRecordQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 姓名 + */ + private String name; + + /** + * 设备id + */ + private String equipmentId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/query/PeopleRecordQuery.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/query/PeopleRecordQuery.java new file mode 100644 index 0000000..079593a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/query/PeopleRecordQuery.java @@ -0,0 +1,53 @@ +package top.ysoft.admin.rule.model.query; + +import cn.hutool.core.date.DatePattern; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 人员识别记录查询条件 + * + * @author zc + * @since 2025/04/08 23:06 + */ +@Data +@Schema(description = "人员识别记录查询条件") +public class PeopleRecordQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 人员姓名 + */ + private String name; + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 设备id + */ + private String equipmentId; + + /** + * 识别记录时间 + */ + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + @Size(max = 2, message = "创建时间必须是一个范围") + private List showDateList; + + /** + * 部门名称 + */ + private String branchName; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/query/RuleQuery.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/query/RuleQuery.java new file mode 100644 index 0000000..0aad090 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/query/RuleQuery.java @@ -0,0 +1,32 @@ +package top.ysoft.admin.rule.model.query; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 通行规则查询条件 + * + * @author zc + * @since 2025/03/26 22:59 + */ +@Data +@Schema(description = "通行规则查询条件") +public class RuleQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 规则名称 + */ + @Schema(description = "规则名称") + @Query(type = QueryType.EQ) + private String name; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/req/EmpowerRecordReq.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/req/EmpowerRecordReq.java new file mode 100644 index 0000000..c9afa1c --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/req/EmpowerRecordReq.java @@ -0,0 +1,79 @@ +package top.ysoft.admin.rule.model.req; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 创建或修改授权记录参数 + * + * @author zc + * @since 2025/04/08 23:08 + */ +@Data +@Schema(description = "创建或修改授权记录参数") +public class EmpowerRecordReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 姓名 + */ + @Schema(description = "姓名") + @Length(max = 50, message = "姓名长度不能超过 {max} 个字符") + private String name; + + /** + * 人员id + */ + @Schema(description = "人员id") + @Length(max = 50, message = "人员id长度不能超过 {max} 个字符") + private String peopleId; + + /** + * 信息下发(0成功 1失败) + */ + @Schema(description = "信息下发(0成功 1失败)") + @Length(max = 1, message = "信息下发(0成功 1失败)长度不能超过 {max} 个字符") + private String infoDown; + + /** + * 授权方式(0本地 1云盘) + */ + @Schema(description = "授权方式(0本地 1云盘)") + @Length(max = 1, message = "授权方式(0本地 1云盘)长度不能超过 {max} 个字符") + private String empower; + + /** + * 设备Id + */ + @Schema(description = "设备Id") + private Long equipmentId; + + /** + * 操作(0启用 1禁用) + */ + @Schema(description = "操作(0启用 1禁用)") + @Length(max = 1, message = "操作(0启用 1禁用)长度不能超过 {max} 个字符") + private String status; + + /** + * 备注 + */ + @Schema(description = "备注") + @Length(max = 200, message = "备注长度不能超过 {max} 个字符") + private String remark; + + /** + * 规则Id + */ + @Schema(description = "规则Id") + private Long ruleId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/req/PeopleRecordReq.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/req/PeopleRecordReq.java new file mode 100644 index 0000000..64b1982 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/req/PeopleRecordReq.java @@ -0,0 +1,198 @@ +package top.ysoft.admin.rule.model.req; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; +import java.util.Date; + +/** + * 创建或修改人员识别记录参数 + * + * @author zc + * @since 2025/04/08 23:06 + */ +@Data +@Schema(description = "创建或修改人员识别记录参数") +public class PeopleRecordReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 人员id + */ + @Schema(description = "人员id") + @Length(max = 50, message = "人员id长度不能超过 {max} 个字符") + private Long peopleId; + + /** + * 设备id + */ + @Schema(description = "设备id") + @Length(max = 50, message = "设备id长度不能超过 {max} 个字符") + private Long equipmentId; + + /** + * 设备内网ip + */ + @Schema(description = "设备内网ip") + @Length(max = 50, message = "设备内网ip长度不能超过 {max} 个字符") + private String deviceIp; + + /** + * 人员guid 或者STRANGERBABY + */ + @Schema(description = "人员guid 或者STRANGERBABY") + @Length(max = 50, message = "人员guid 或者STRANGERBABY长度不能超过 {max} 个字符") + private String admitGuid; + + /** + * 识别模式,1:人像识别, 2:刷卡识别 ,3:人卡合一 4,人证比对 7:密码识别 8 二维码识别 + */ + @Schema(description = "识别模式,1:人像识别, 2:刷卡识别 ,3:人卡合一 4,人证比对 7:密码识别 8 二维码识别") + @Length(max = 50, message = "识别模式,1:人像识别, 2:刷卡识别 ,3:人卡合一 4,人证比对 7:密码识别 8 二维码识别长度不能超过 {max} 个字符") + private String recMode; + + /** + * 现场照url + */ + @Schema(description = "现场照url") + @Length(max = 255, message = "现场照url长度不能超过 {max} 个字符") + private String filePath; + + /** + * 识别记录时间戳 + */ + @Schema(description = "识别记录时间戳") + private Long showTime; + + /** + * 识别记录时间 + */ + @Schema(description = "识别记录时间") + private Date showDate; + + /** + * 活体结果 1:活体判断成功 2:活体判断失败 3:未进行活体判断 + */ + @Schema(description = "活体结果 1:活体判断成功 2:活体判断失败 3:未进行活体判断") + @Length(max = 50, message = "活体结果 1:活体判断成功 2:活体判断失败 3:未进行活体判断长度不能超过 {max} 个字符") + private String aliveType; + + /** + * 识别分数 + */ + @Schema(description = "识别分数") + @Length(max = 50, message = "识别分数长度不能超过 {max} 个字符") + private String recScore; + + /** + * 设备序列号 + */ + @Schema(description = "设备序列号") + @Length(max = 50, message = "设备序列号长度不能超过 {max} 个字符") + private String deviceNo; + + /** + * 软件版本号 + */ + @Schema(description = "软件版本号") + @Length(max = 50, message = "软件版本号长度不能超过 {max} 个字符") + private String deviceVersion; + + /** + * 设备来源 + */ + @Schema(description = "设备来源") + @Length(max = 50, message = "设备来源长度不能超过 {max} 个字符") + private String source; + + /** + * 人员比对结果,1:比对成功 2:比对失败 + */ + @Schema(description = "人员比对结果,1:比对成功 2:比对失败") + @Length(max = 50, message = "人员比对结果,1:比对成功 2:比对失败长度不能超过 {max} 个字符") + private String type; + + /** + * 识别卡号 + */ + @Schema(description = "识别卡号") + @Length(max = 50, message = "识别卡号长度不能超过 {max} 个字符") + private String cardNo; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + @Length(max = 50, message = "设备名称长度不能超过 {max} 个字符") + private String deviceName; + + /** + * 比对模式,1:本地识别 2:云端识别 + */ + @Schema(description = "比对模式,1:本地识别 2:云端识别") + @Length(max = 50, message = "比对模式,1:本地识别 2:云端识别长度不能超过 {max} 个字符") + private String recType; + + /** + * 结果 + */ + @Schema(description = "结果") + @Length(max = 50, message = "结果长度不能超过 {max} 个字符") + private String result; + + /** + * 有效日期判断 1:有效期内 2:有效期外 3:未进行有效期判断 + */ + @Schema(description = "有效日期判断 1:有效期内 2:有效期外 3:未进行有效期判断") + @Length(max = 50, message = "有效日期判断 1:有效期内 2:有效期外 3:未进行有效期判断长度不能超过 {max} 个字符") + private String permissionTimeType; + + /** + * 有效时间段判断 1:时间段内 2:时间段外 3:未进行时间段判断 + */ + @Schema(description = "有效时间段判断 1:时间段内 2:时间段外 3:未进行时间段判断") + @Length(max = 50, message = "有效时间段判断 1:时间段内 2:时间段外 3:未进行时间段判断长度不能超过 {max} 个字符") + private String passTimeType; + + /** + * 识别模式判断 1. 模式正确 2.模式不正确 + */ + @Schema(description = "识别模式判断 1. 模式正确 2.模式不正确") + @Length(max = 50, message = "识别模式判断 1. 模式正确 2.模式不正确长度不能超过 {max} 个字符") + private String recModeType; + + /** + * 保留字段 + */ + @Schema(description = "保留字段") + @Length(max = 50, message = "保留字段长度不能超过 {max} 个字符") + private String storageId; + + /** + * 当前时间戳 + */ + @Schema(description = "当前时间戳") + private Long timestamp; + + /** + * 识别主体姓名 + */ + @Schema(description = "识别主体姓名") + @Length(max = 50, message = "识别主体姓名长度不能超过 {max} 个字符") + private String admitName; + + /** + * 备注 + */ + @Schema(description = "备注") + @Length(max = 200, message = "备注长度不能超过 {max} 个字符") + private String remark; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/req/RuleReq.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/req/RuleReq.java new file mode 100644 index 0000000..41ff623 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/req/RuleReq.java @@ -0,0 +1,141 @@ +package top.ysoft.admin.rule.model.req; + +import jakarta.validation.constraints.*; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 创建或修改通行规则参数 + * + * @author zc + * @since 2025/03/26 16:57 + */ +@Data +@Schema(description = "创建或修改通行规则参数") +public class RuleReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 规则Id + */ + @Schema(description = "规则Id") + private Long id; + + /** + * 规则名称 + */ + @Schema(description = "规则名称") + @NotBlank(message = "规则名称不能为空") + @Length(max = 50, message = "规则名称长度不能超过 {max} 个字符") + private String name; + + /** + * 有效期开始 + */ + @Schema(description = "有效期开始") + private LocalDateTime startTime; + + /** + * 有效期结束 + */ + @Schema(description = "有效期结束") + private LocalDateTime endTime; + + /** + * 准入时间起 + */ + @Schema(description = "准入时间起") + @Length(max = 50, message = "准入时间起长度不能超过 {max} 个字符") + private String admittanceStart; + + /** + * 准入时间止 + */ + @Schema(description = "准入时间止") + @Length(max = 50, message = "准入时间止长度不能超过 {max} 个字符") + private String admittanceEnd; + + /** + * 设备空间 + */ + @Schema(description = "设备空间") + private Long spaceId; + + /** + * 设备位置 + */ + @Schema(description = "设备位置") + @Length(max = 255, message = "设备位置长度不能超过 {max} 个字符") + private String pointId; + + /** + * 备注 + */ + @Schema(description = "备注") + @Length(max = 200, message = "备注长度不能超过 {max} 个字符") + private String remark; + + /** + * + */ + @Schema(description = "") + private Long createUser; + + /** + * 创建时间 + */ + @Schema(description = "创建时间") + private LocalDateTime createTime; + + /** + * + */ + @Schema(description = "") + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + /** + * 1:本地库 2:云端库 默认本地 + */ + @Schema(description = "1:本地库 2:云端库 默认本地") + @Length(max = 200, message = "1:本地库 2:云端库 默认本地长度不能超过 {max} 个字符") + private String type; + + /** + * facePermission 刷脸权限 idCardPermission 刷卡权限 faceAndCardPermission 人卡合一权限 idCardFacePermission 人证比对权限 + * passwordPermission 密码权限 + */ + @Schema(description = "facePermission 刷脸权限 idCardPermission 刷卡权限 faceAndCardPermission 人卡合一权限 idCardFacePermission 人证比对权限passwordPermission 密码权限") + private List permissionList; + + private String permission; + + @Schema(description = "规则绑定设备id集合") + private List equipmentIds; + + private Map equipmentRuleIdMap = new HashMap<>(); + + //修改失败的设备 + private String failures; + + private Boolean isChange = false; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/EmpowerRecordDetailResp.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/EmpowerRecordDetailResp.java new file mode 100644 index 0000000..1e2e5b6 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/EmpowerRecordDetailResp.java @@ -0,0 +1,114 @@ +package top.ysoft.admin.rule.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; + +/** + * 授权记录详情信息 + * + * @author zc + * @since 2025/04/08 23:08 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "授权记录详情信息") +public class EmpowerRecordDetailResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 姓名 + */ + @Schema(description = "姓名") + @ExcelProperty(value = "姓名") + private String name; + + /** + * 人员id + */ + @Schema(description = "人员id") + @ExcelProperty(value = "人员id") + private String peopleId; + + /** + * 信息下发(0成功 1失败) + */ + @Schema(description = "信息下发(0成功 1失败)") + @ExcelProperty(value = "信息下发(0成功 1失败)") + private String infoDown; + + /** + * 授权方式(0本地 1云盘) + */ + @Schema(description = "授权方式(0本地 1云盘)") + @ExcelProperty(value = "授权方式(0本地 1云盘)") + private String empower; + + /** + * 设备Id + */ + @Schema(description = "设备Id") + @ExcelProperty(value = "设备Id") + private Long equipmentId; + + /** + * 操作(0启用 1禁用) + */ + @Schema(description = "操作(0启用 1禁用)") + @ExcelProperty(value = "操作(0启用 1禁用)") + private String status; + + /** + * 备注 + */ + @Schema(description = "备注") + @ExcelProperty(value = "备注") + private String remark; + + /** + * 规则Id + */ + @Schema(description = "规则Id") + @ExcelProperty(value = "规则Id") + private Long ruleId; + + /** + * 所属产品id + */ + @Schema(description = "所属产品id") + private Long productId; + + /** + * 设备ip + */ + @Schema(description = "设备ip") + private String ip; + + /** + * 设备密码 + */ + @Schema(description = "设备密码") + private String password; + + /** + * 门卡号 + */ + @Schema(description = "门卡号") + private String doorNo; + + /** + * 人脸机人员编号 + */ + @Schema(description = "人脸机人员编号") + private String guid; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/EmpowerRecordResp.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/EmpowerRecordResp.java new file mode 100644 index 0000000..6395eb9 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/EmpowerRecordResp.java @@ -0,0 +1,107 @@ +package top.ysoft.admin.rule.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; + +/** + * 授权记录信息 + * + * @author zc + * @since 2025/04/08 23:08 + */ +@Data +@Schema(description = "授权记录信息") +public class EmpowerRecordResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 姓名 + */ + @Schema(description = "姓名") + private String name; + + /** + * 姓名 + */ + @Schema(description = "姓名") + private String peopleName; + + /** + * 人员id + */ + @Schema(description = "人员id") + private String peopleId; + + /** + * 信息下发(0成功 1失败) + */ + @Schema(description = "信息下发(0成功 1失败)") + private String infoDown; + + /** + * 授权方式(0本地 1云盘) + */ + @Schema(description = "授权方式(0本地 1云盘)") + private String empower; + + /** + * 设备Id + */ + @Schema(description = "设备Id") + private Long equipmentId; + + /** + * 操作(0启用 1禁用) + */ + @Schema(description = "操作(0启用 1禁用)") + private String status; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; + + /** + * + */ + @Schema(description = "") + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + /** + * 规则Id + */ + @Schema(description = "规则Id") + private Long ruleId; + + /** + * 头像 + */ + @Schema(description = "头像") + private String peopleAvatar; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + private String equipmentName; + + /** + * 门禁设备位置 1进 2出 + */ + private String entryExitType; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/PeopleRecordDetailResp.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/PeopleRecordDetailResp.java new file mode 100644 index 0000000..664cd81 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/PeopleRecordDetailResp.java @@ -0,0 +1,225 @@ +package top.ysoft.admin.rule.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; +import java.util.Date; + +/** + * 人员识别记录详情信息 + * + * @author zc + * @since 2025/04/08 23:06 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "人员识别记录详情信息") +public class PeopleRecordDetailResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 人员id + */ + @Schema(description = "人员id") + @ExcelProperty(value = "人员id") + private Long peopleId; + + /** + * 设备id + */ + @Schema(description = "设备id") + @ExcelProperty(value = "设备id") + private Long equipmentId; + + /** + * 设备内网ip + */ + @Schema(description = "设备内网ip") + @ExcelProperty(value = "设备内网ip") + private String deviceIp; + + /** + * 人员guid 或者STRANGERBABY + */ + @Schema(description = "人员guid 或者STRANGERBABY") + @ExcelProperty(value = "人员guid 或者STRANGERBABY") + private String admitGuid; + + /** + * 识别模式,1:人像识别, 2:刷卡识别 ,3:人卡合一 4,人证比对 7:密码识别 8 二维码识别 + */ + @Schema(description = "识别模式,1:人像识别, 2:刷卡识别 ,3:人卡合一 4,人证比对 7:密码识别 8 二维码识别") + @ExcelProperty(value = "识别模式,1:人像识别, 2:刷卡识别 ,3:人卡合一 4,人证比对 7:密码识别 8 二维码识别") + private String recMode; + + /** + * 现场照url + */ + @Schema(description = "现场照url") + @ExcelProperty(value = "现场照url") + private String filePath; + + /** + * 识别记录时间戳 + */ + @Schema(description = "识别记录时间戳") + @ExcelProperty(value = "识别记录时间戳") + private Long showTime; + + /** + * 识别记录时间 + */ + @Schema(description = "识别记录时间") + @ExcelProperty(value = "识别记录时间") + private Date showDate; + + /** + * 活体结果 1:活体判断成功 2:活体判断失败 3:未进行活体判断 + */ + @Schema(description = "活体结果 1:活体判断成功 2:活体判断失败 3:未进行活体判断") + @ExcelProperty(value = "活体结果 1:活体判断成功 2:活体判断失败 3:未进行活体判断") + private String aliveType; + + /** + * 识别分数 + */ + @Schema(description = "识别分数") + @ExcelProperty(value = "识别分数") + private String recScore; + + /** + * 设备序列号 + */ + @Schema(description = "设备序列号") + @ExcelProperty(value = "设备序列号") + private String deviceNo; + + /** + * 软件版本号 + */ + @Schema(description = "软件版本号") + @ExcelProperty(value = "软件版本号") + private String deviceVersion; + + /** + * 设备来源 + */ + @Schema(description = "设备来源") + @ExcelProperty(value = "设备来源") + private String source; + + /** + * 人员比对结果,1:比对成功 2:比对失败 + */ + @Schema(description = "人员比对结果,1:比对成功 2:比对失败") + @ExcelProperty(value = "人员比对结果,1:比对成功 2:比对失败") + private String type; + + /** + * 识别卡号 + */ + @Schema(description = "识别卡号") + @ExcelProperty(value = "识别卡号") + private String cardNo; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + @ExcelProperty(value = "设备名称") + private String deviceName; + + /** + * 比对模式,1:本地识别 2:云端识别 + */ + @Schema(description = "比对模式,1:本地识别 2:云端识别") + @ExcelProperty(value = "比对模式,1:本地识别 2:云端识别") + private String recType; + + /** + * 结果 + */ + @Schema(description = "结果") + @ExcelProperty(value = "结果") + private String result; + + /** + * 有效日期判断 1:有效期内 2:有效期外 3:未进行有效期判断 + */ + @Schema(description = "有效日期判断 1:有效期内 2:有效期外 3:未进行有效期判断") + @ExcelProperty(value = "有效日期判断 1:有效期内 2:有效期外 3:未进行有效期判断") + private String permissionTimeType; + + /** + * 有效时间段判断 1:时间段内 2:时间段外 3:未进行时间段判断 + */ + @Schema(description = "有效时间段判断 1:时间段内 2:时间段外 3:未进行时间段判断") + @ExcelProperty(value = "有效时间段判断 1:时间段内 2:时间段外 3:未进行时间段判断") + private String passTimeType; + + /** + * 识别模式判断 1. 模式正确 2.模式不正确 + */ + @Schema(description = "识别模式判断 1. 模式正确 2.模式不正确") + @ExcelProperty(value = "识别模式判断 1. 模式正确 2.模式不正确") + private String recModeType; + + /** + * 保留字段 + */ + @Schema(description = "保留字段") + @ExcelProperty(value = "保留字段") + private String storageId; + + /** + * 当前时间戳 + */ + @Schema(description = "当前时间戳") + @ExcelProperty(value = "当前时间戳") + private Long timestamp; + + /** + * 识别主体姓名 + */ + @Schema(description = "识别主体姓名") + @ExcelProperty(value = "识别主体姓名") + private String admitName; + + /** + * 备注 + */ + @Schema(description = "备注") + @ExcelProperty(value = "备注") + private String remark; + + /** + * 人员记录或访客记录标示 + */ + @Schema(description = "人员记录或访客记录标示") + @ExcelProperty(value = "人员记录或访客记录标示") + private String flag; + + /** + * 考勤组id + */ + @Schema(description = "考勤组id") + @ExcelProperty(value = "考勤组id") + private Long groupId; + + /** + * 下级系统标志 + */ + @Schema(description = "下级系统标志") + @ExcelProperty(value = "下级系统标志") + private String sign; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/PeopleRecordResp.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/PeopleRecordResp.java new file mode 100644 index 0000000..039218d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/PeopleRecordResp.java @@ -0,0 +1,269 @@ +package top.ysoft.admin.rule.model.resp; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; +import java.util.Date; + +/** + * 人员识别记录信息 + * + * @author zc + * @since 2025/04/08 23:06 + */ +@Data +@Schema(description = "人员识别记录信息") +public class PeopleRecordResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 人员id + */ + @Schema(description = "人员id") + @ExcelIgnore + private Long peopleId; + + /** + * 设备id + */ + @Schema(description = "设备id") + @ExcelIgnore + private Long equipmentId; + + /** + * 设备内网ip + */ + @Schema(description = "设备内网ip") + @ExcelIgnore + private String deviceIp; + + /** + * 人员guid 或者STRANGERBABY + */ + @Schema(description = "人员guid 或者STRANGERBABY") + @ExcelIgnore + private String admitGuid; + + /** + * 识别模式 + * '0':人像验证, + * '1':人卡合一, + * '2':人证比对, + * '3':刷卡验证, + * '4':按钮开门, + * '5':远程开门, + * '6':密码开门, + * '7':密码识别, + * '8':二维码识别, + * '9':指纹比对, + * '10':二维码&人像验证, + * '12':身份证验证 + */ + @Schema(description = "识别模式") + private String recMode; + + /** + * 现场照url + */ + @Schema(description = "现场照url") + @ExcelIgnore + private String filePath; + + /** + * 识别记录时间戳 + */ + @Schema(description = "识别记录时间戳") + @ExcelIgnore + private Long showTime; + + /** + * 识别记录时间 + */ + @Schema(description = "识别记录时间") + @ExcelProperty(value = "识别时间", order = 1) + private Date showDate; + + /** + * 活体结果 1:活体判断成功 2:活体判断失败 3:未进行活体判断 + */ + @Schema(description = "活体结果 1:活体判断成功 2:活体判断失败 3:未进行活体判断") + @ExcelIgnore + private String aliveType; + + /** + * 识别分数 + */ + @Schema(description = "识别分数") + @ExcelIgnore + private String recScore; + + /** + * 设备序列号 + */ + @Schema(description = "设备序列号") + @ExcelProperty(value = "设备序列号", order = 1) + private String deviceNo; + + /** + * 软件版本号 + */ + @Schema(description = "软件版本号") + @ExcelIgnore + private String deviceVersion; + + /** + * 设备来源 + */ + @Schema(description = "设备来源") + @ExcelIgnore + private String source; + + /** + * 人员比对结果,1:比对成功 2:比对失败 + */ + @Schema(description = "人员比对结果,1:比对成功 2:比对失败") + @ExcelIgnore + private String type; + + /** + * 识别卡号 + */ + @Schema(description = "识别卡号") + @ExcelProperty(value = "识别卡号", order = 1) + private String cardNo; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + @ExcelProperty(value = "设备名称", order = 1) + private String deviceName; + + /** + * 比对模式,1:本地识别 2:云端识别 + */ + @Schema(description = "比对模式,1:本地识别 2:云端识别") + @ExcelIgnore + private String recType; + + /** + * 结果 + */ + @Schema(description = "结果") + @ExcelProperty(value = "识别结果", order = 1) + private String result; + + /** + * 有效日期判断 1:有效期内 2:有效期外 3:未进行有效期判断 + */ + @Schema(description = "有效日期判断 1:有效期内 2:有效期外 3:未进行有效期判断") + @ExcelIgnore + private String permissionTimeType; + + /** + * 有效时间段判断 1:时间段内 2:时间段外 3:未进行时间段判断 + */ + @Schema(description = "有效时间段判断 1:时间段内 2:时间段外 3:未进行时间段判断") + @ExcelIgnore + private String passTimeType; + + /** + * 识别模式判断 1. 模式正确 2.模式不正确 + */ + @Schema(description = "识别模式判断 1. 模式正确 2.模式不正确") + @ExcelIgnore + private String recModeType; + + /** + * 保留字段 + */ + @Schema(description = "保留字段") + @ExcelIgnore + private String storageId; + + /** + * 当前时间戳 + */ + @Schema(description = "当前时间戳") + @ExcelIgnore + private Long timestamp; + + /** + * 识别主体姓名 + */ + @Schema(description = "识别主体姓名") + @ExcelIgnore + private String admitName; + + /** + * 备注 + */ + @Schema(description = "备注") + @ExcelIgnore + private String remark; + + /** + * + */ + @Schema(description = "") + @ExcelIgnore + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + @ExcelIgnore + private LocalDateTime updateTime; + + /** + * 人员记录或访客记录标示 + */ + @Schema(description = "人员记录或访客记录标示") + @ExcelIgnore + private String flag; + + /** + * 考勤组id + */ + @Schema(description = "考勤组id") + @ExcelIgnore + private Long groupId; + + /** + * 下级系统标志 + */ + @Schema(description = "下级系统标志") + @ExcelIgnore + private String sign; + + /** + * 工号 + */ + @Schema(description = "工号") + @ExcelProperty(value = "工号", order = 1) + private String gh; + + /** + * 部门名称 + */ + @Schema(description = "部门名称") + @ExcelProperty(value = "部门名称", order = 3) + private String branchName; + + /** + * 人员姓名 + */ + @Schema(description = "人员姓名") + @ExcelProperty(value = "人员姓名", order = 4) + private String peopleName; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/RuleResp.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/RuleResp.java new file mode 100644 index 0000000..2575df3 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/RuleResp.java @@ -0,0 +1,134 @@ +package top.ysoft.admin.rule.model.resp; + +import cn.crane4j.annotation.Assemble; +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 通行规则信息 + * + * @author zc + * @since 2025/03/26 22:59 + */ +@Data +@Schema(description = "通行规则信息") +public class RuleResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 规则名称 + */ + @Schema(description = "规则名称") + @ExcelProperty(value = "规则名称", order = 1) + private String name; + + /** + * 有效期开始 + */ + @Schema(description = "有效期开始") + @ExcelProperty(value = "有效期开始", order = 2) + @ColumnWidth(30) + private LocalDateTime startTime; + + /** + * 有效期结束 + */ + @Schema(description = "有效期结束") + @ExcelProperty(value = "有效期结束", order = 3) + @ColumnWidth(30) + private LocalDateTime endTime; + + /** + * 准入时间起 + */ + @Schema(description = "准入时间起") + @ExcelProperty(value = "准入时间起", order = 4) + @ColumnWidth(30) + private String admittanceStart; + + /** + * 准入时间止 + */ + @Schema(description = "准入时间止") + @ExcelProperty(value = "准入时间止", order = 5) + @ColumnWidth(30) + private String admittanceEnd; + + /** + * 设备空间 + */ + @Schema(description = "设备空间") + @Assemble(prop = ":spaceName", container = ContainerConstants.SPACE_NAME) + @ExcelIgnore + private Long spaceId; + + /** + * 设备空间名称 + */ + @Schema(description = "设备空间名称") + @ExcelIgnore + private String spaceName; + + /** + * 设备位置 + */ + @Schema(description = "设备位置") + @ExcelIgnore + private String pointId; + + /** + * 备注 + */ + @Schema(description = "备注") + @ExcelIgnore + private String remark; + + /** + * + */ + @Schema(description = "") + @ExcelIgnore + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + @ExcelProperty(value = "更新时间", order = 6) + @ColumnWidth(30) + private LocalDateTime updateTime; + + /** + * 1:本地库 2:云端库 默认本地 + */ + @Schema(description = "1:本地库 2:云端库 默认本地") + @ExcelIgnore + private String type; + + /** + * facePermission 刷脸权限 idCardPermission 刷卡权限 faceAndCardPermission 人卡合一权限 idCardFacePermission 人证比对权限 + * passwordPermission 密码权限 + */ + @Schema(description = "权限") + @ExcelProperty(value = "权限", order = 7) + private String permission; + + @ExcelIgnore + @Schema(description = "facePermission 刷脸权限 idCardPermission 刷卡权限 faceAndCardPermission 人卡合一权限 idCardFacePermission 人证比对权限passwordPermission 密码权限") + private List permissionList; + + @ExcelIgnore + @Schema(description = "规则绑定设备id集合") + private List equipmentIds; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/YFPeopleResp.java b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/YFPeopleResp.java new file mode 100644 index 0000000..e84cc87 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/model/resp/YFPeopleResp.java @@ -0,0 +1,54 @@ +package top.ysoft.admin.rule.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 设备规则信息 + * + * @author zc + * @since 2025/03/26 22:59 + */ +@Data +public class YFPeopleResp { + + @Schema(description = "规则id") + private Long ruleId; + + @Schema(description = "规则在设备上的规则id") + private String equipmentRuleId; + + @Schema(description = "设备id") + private Long equipmentId; + + @Schema(description = "设备ip") + private String ip; + + @Schema(description = "设备密码") + private String password; + + @Schema(description = "在线状态") + private String flag; + + @Schema(description = "人员id") + private Long peopleId; + + @Schema(description = "人员姓名") + private String peopleName; + + @Schema(description = "身份证号") + private String idcard; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "设备人员id") + private String guid; + + @Schema(description = "设备人员照片id") + private String faceGuid; + + @Schema(description = "设备人员照片id") + private String avatar; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/service/EmpowerRecordService.java b/wms-module-system/src/main/java/top/wms/admin/rule/service/EmpowerRecordService.java new file mode 100644 index 0000000..1c8070a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/service/EmpowerRecordService.java @@ -0,0 +1,14 @@ +package top.ysoft.admin.rule.service; + +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.rule.model.query.EmpowerRecordQuery; +import top.ysoft.admin.rule.model.req.EmpowerRecordReq; +import top.ysoft.admin.rule.model.resp.EmpowerRecordResp; + +/** + * 授权记录业务接口 + * + * @author zc + * @since 2025/04/08 23:08 + */ +public interface EmpowerRecordService extends BaseService {} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/service/PeopleRecordService.java b/wms-module-system/src/main/java/top/wms/admin/rule/service/PeopleRecordService.java new file mode 100644 index 0000000..bfbf18f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/service/PeopleRecordService.java @@ -0,0 +1,14 @@ +package top.ysoft.admin.rule.service; + +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.rule.model.query.PeopleRecordQuery; +import top.ysoft.admin.rule.model.req.PeopleRecordReq; +import top.ysoft.admin.rule.model.resp.PeopleRecordResp; + +/** + * 人员识别记录业务接口 + * + * @author zc + * @since 2025/04/08 23:06 + */ +public interface PeopleRecordService extends BaseService {} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/service/RuleService.java b/wms-module-system/src/main/java/top/wms/admin/rule/service/RuleService.java new file mode 100644 index 0000000..2263398 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/service/RuleService.java @@ -0,0 +1,34 @@ +package top.ysoft.admin.rule.service; + +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.rule.model.query.RuleQuery; +import top.ysoft.admin.rule.model.req.RuleReq; +import top.ysoft.admin.rule.model.resp.YFPeopleResp; +import top.ysoft.admin.rule.model.resp.RuleResp; + +import java.util.List; + +/** + * 通行规则业务接口 + * + * @author zc + * @since 2025/03/26 22:59 + */ +public interface RuleService extends BaseService { + + List getEquipmentRulesByPeoples(List peopleIds); + + List getRuleNameList(RuleReq ruleReq); + + String addRule1(RuleReq ruleReq); + + String updateRule1(RuleReq ruleReq, Long ruleId); + + /** + * crane4j存储 + * + * @return + */ + String getRuleName(Long id); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/service/impl/EmpowerRecordServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/rule/service/impl/EmpowerRecordServiceImpl.java new file mode 100644 index 0000000..ce596ac --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/service/impl/EmpowerRecordServiceImpl.java @@ -0,0 +1,117 @@ +package top.ysoft.admin.rule.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import top.continew.starter.core.exception.BusinessException; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.common.enums.CommonExceptionEnum; +import top.ysoft.admin.rule.mapper.EmpowerRecordMapper; +import top.ysoft.admin.rule.model.entity.EmpowerRecordDO; +import top.ysoft.admin.rule.model.query.EmpowerRecordQuery; +import top.ysoft.admin.rule.model.req.EmpowerRecordReq; +import top.ysoft.admin.rule.model.resp.EmpowerRecordDetailResp; +import top.ysoft.admin.rule.model.resp.EmpowerRecordResp; +import top.ysoft.admin.rule.service.EmpowerRecordService; +import top.ysoft.admin.common.constant.Constants; +import top.ysoft.admin.yfApi.service.IYFPushService; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 授权记录业务实现 + * + * @author zc + * @since 2025/04/08 23:08 + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class EmpowerRecordServiceImpl extends BaseServiceImpl implements EmpowerRecordService { + + private final IYFPushService iyfPushService; + + /** + * 卡权限删除接口 + */ + public static final String delete = "/cardinfo/delete"; + + @Override + public PageResp page(EmpowerRecordQuery query, PageQuery pageQuery) { + QueryWrapper queryWrapper = this.buildQueryWrapper(query); + super.sort(queryWrapper, pageQuery); + IPage page = baseMapper.selectEmpowerRecordPage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + return PageResp.build(page, super.getListClass()); + } + + @Override + protected QueryWrapper buildQueryWrapper(EmpowerRecordQuery query) { + return new QueryWrapper().like(StrUtil.isNotBlank(query.getName()), "p.name", query.getName()) + .eq(StrUtil.isNotBlank(query.getEquipmentId()), "ser.equipment_id", query.getEquipmentId()); + } + + @Override + public void beforeDelete(List ids) { + + //查询授权记录 设备列表需要删除的人员信息(已去重,group by需要设置数据库模式) + List resp = baseMapper.selectForEmpowerDel(ids); + if (CollUtil.isNotEmpty(resp)) { + return; + } + + //删除设备的人员信息 + resp.forEach(record -> { + try { + iyfPushService.personDelete(record.getGuid(), record.getIp(), record.getPassword()); + } catch (Exception e) { + log.error("EmpowerRecordServiceImpl-personDelete失败:{},{},失败原因", record.getGuid(), record.getIp(), e); + } + //设备所属产品id为5的需要删除梯控 + if (record.getProductId() == 5L) { + try { + deleteLadder(record); + } catch (Exception e) { + log.error("EmpowerRecordServiceImpl-deleteLadder失败:{},{}", record.getGuid(), record.getIp()); + } + } + }); + } + + /** + * 删除梯控 + * + * @param equipmentResp + */ + private void deleteLadder(EmpowerRecordDetailResp equipmentResp) { + log.info("梯控删除:" + equipmentResp); + log.info("梯控删除地址:" + "http://" + equipmentResp.getIp() + ":8090" + delete); + Map map = new HashMap<>(); + map.put("idcardnum", equipmentResp.getDoorNo()); + map.put("pass", equipmentResp.getPassword()); + String body = HttpUtil.createPost("http://" + equipmentResp.getIp() + ":8090" + delete) + .timeout(5000) + .body(JSON.toJSONString(map)) + .execute() + .body(); + log.info("梯控删除卡号接口返回:" + body); + JSONObject object = JSONObject.parseObject(body); + if (!Constants.CODE.equals(object.get("msg"))) { + log.error("梯控删除卡号接口异常码:{}", object.getString("msg")); + throw new BusinessException("梯控删除卡号接口" + CommonExceptionEnum.REQUEST_ERROR.getMessage()); + } + } + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/service/impl/PeopleRecordServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/rule/service/impl/PeopleRecordServiceImpl.java new file mode 100644 index 0000000..77fb7c4 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/service/impl/PeopleRecordServiceImpl.java @@ -0,0 +1,49 @@ +package top.ysoft.admin.rule.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.rule.mapper.PeopleRecordMapper; +import top.ysoft.admin.rule.model.entity.PeopleRecordDO; +import top.ysoft.admin.rule.model.query.PeopleRecordQuery; +import top.ysoft.admin.rule.model.req.PeopleRecordReq; +import top.ysoft.admin.rule.model.resp.PeopleRecordResp; +import top.ysoft.admin.rule.service.PeopleRecordService; + +/** + * 人员识别记录业务实现 + * + * @author zc + * @since 2025/04/08 23:06 + */ +@Service +@RequiredArgsConstructor +public class PeopleRecordServiceImpl extends BaseServiceImpl implements PeopleRecordService { + + @Override + public PageResp page(PeopleRecordQuery query, PageQuery pageQuery) { + QueryWrapper queryWrapper = this.buildQueryWrapper(query); + super.sort(queryWrapper, pageQuery); + IPage page = baseMapper.selectPeopleRecordPage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + return PageResp.build(page, super.getListClass()); + } + + @Override + protected QueryWrapper buildQueryWrapper(PeopleRecordQuery query) { + + return new QueryWrapper().eq(StrUtil.isNotBlank(query + .getEquipmentId()), "spr.equipment_id", query.getEquipmentId()) + .like(StrUtil.isNotBlank(query.getName()), "p.name", query.getName()) + .between(CollUtil.isNotEmpty(query.getShowDateList()), "spr.show_date", CollUtil.getFirst(query + .getShowDateList()), CollUtil.getLast(query.getShowDateList())); + } + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/rule/service/impl/RuleServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/rule/service/impl/RuleServiceImpl.java new file mode 100644 index 0000000..1731ff2 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/rule/service/impl/RuleServiceImpl.java @@ -0,0 +1,412 @@ +package top.ysoft.admin.rule.service.impl; + +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.equipment.model.resp.EquipmentResp; +import top.ysoft.admin.equipment.service.EquipmentService; +import top.ysoft.admin.peopleBranch.mapper.BranchRuleMapper; +import top.ysoft.admin.peopleBranch.model.entity.BranchRuleDO; +import top.ysoft.admin.rule.mapper.RuleMapper; +import top.ysoft.admin.rule.mapstruct.RuleConvert; +import top.ysoft.admin.rule.model.entity.RuleDO; +import top.ysoft.admin.rule.model.query.RuleQuery; +import top.ysoft.admin.rule.model.req.RuleReq; +import top.ysoft.admin.rule.model.resp.RuleResp; +import top.ysoft.admin.rule.model.resp.YFPeopleResp; +import top.ysoft.admin.rule.service.RuleService; +import top.ysoft.admin.system.mapper.RuleRelationMapper; +import top.ysoft.admin.system.model.entity.RuleRelationDO; +import top.ysoft.admin.yfApi.domain.RuleBody; +import top.ysoft.admin.yfApi.service.IYFPushService; + +import java.time.ZoneOffset; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 通行规则业务实现 + * + * @author zc + * @since 2025/03/26 22:59 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class RuleServiceImpl extends BaseServiceImpl implements RuleService { + + private final RuleMapper ruleMapper; + + public final EquipmentService equipmentService; + + public final IYFPushService yfPushService; + + public final RuleRelationMapper ruleRelationMapper; + + public final BranchRuleMapper branchRuleMapper; + + public final RuleConvert ruleConvert; + + @Override + public String addRule1(RuleReq ruleReq) { + if (CollUtil.isNotEmpty(ruleReq.getPermissionList())) { + ruleReq.setPermission(String.join(",", ruleReq.getPermissionList())); + } + //下发失败的设备名单 + String failure = ""; + + //先将规则下发到设备当中 + //创建规则体 + RuleBody ruleBody = this.buildRuleBody(ruleReq); + //规则创建结果 + String response = ""; + //获取人脸机设备 + List equipmentRespList = this.getRuleEquipment(ruleReq.getEquipmentIds()); + //向所有在线设备添加规则 + for (EquipmentResp equipmentResp : equipmentRespList) { + //获取密码 + ruleBody.setPass(equipmentResp.getPassword()); + response = yfPushService.ruleCreate(ruleBody, equipmentResp.getIp()); + JSONObject jsonObject = JSONObject.parseObject(response); + if (jsonObject == null || !jsonObject.containsKey("code") || !StrUtil.equals("LAN_SUS-0", jsonObject + .getString("code"))) { + log.info(equipmentResp.getName() + "状态异常"); + failure += equipmentResp.getName() + ","; + } else { + JSONObject data = jsonObject.getJSONObject("data"); + String equipmentRuleId = data.getString("ruleId"); + ruleReq.getEquipmentRuleIdMap().put(equipmentResp.getId(), equipmentRuleId); + } + } + //没有下发到任何一台设备,新增失败 + CheckUtils.throwIfEmpty(ruleReq.getEquipmentRuleIdMap(), "所选设备全部异常,请检查后重新添加"); + //如果下发到设备当中,则添加到表中 + RuleDO ruleDO = new RuleDO(); + if (StringUtils.hasText(ruleReq.getName())) { + ruleDO.setName(ruleReq.getName()); + } + if (ObjectUtil.isNotEmpty(ruleReq.getStartTime())) { + ruleDO.setStartTime(ruleReq.getStartTime()); + } + if (ObjectUtil.isNotEmpty(ruleReq.getEndTime())) { + ruleDO.setEndTime(ruleReq.getEndTime()); + } + if (ObjectUtil.isNotEmpty(ruleReq.getAdmittanceStart())) { + ruleDO.setAdmittanceStart(ruleReq.getAdmittanceStart()); + } + if (ObjectUtil.isNotEmpty(ruleReq.getAdmittanceEnd())) { + ruleDO.setAdmittanceEnd(ruleReq.getAdmittanceEnd()); + } + if (ObjectUtil.isNotEmpty(ruleReq.getType())) { + ruleDO.setType(ruleReq.getType()); + } + List permissionList = ruleReq.getPermissionList(); + if (permissionList != null && !permissionList.isEmpty()) { + ruleDO.setPermission(String.join(",", permissionList)); + } + baseMapper.insert(ruleDO); + for (Long equipmentId : ruleReq.getEquipmentIds()) { + if (ObjectUtil.isNotEmpty(ruleReq.getEquipmentRuleIdMap().get(equipmentId))) { + createRelation(ruleDO.getId(), equipmentId, ruleReq.getEquipmentRuleIdMap().get(equipmentId)); + } + } + if (ObjectUtil.equal(failure, "")) { + return "规则下发完成"; + } else { + return "规则下发完成,部分设备下发失败,名单如下:" + failure; + } + } + + @Override + public RuleResp get(Long id) { + RuleDO ruleDO = baseMapper.selectById(id); + List ruleRelationDOList = ruleRelationMapper.selectList(new QueryWrapper() + .eq("rule_id", id)); + List equipmentIds = ruleRelationDOList.stream().map(RuleRelationDO::getEquipmentId).toList(); + RuleResp ruleResp = BeanUtil.toBean(ruleDO, RuleResp.class); + ruleResp.setPermissionList(List.of(ruleDO.getPermission().split(","))); + ruleResp.setEquipmentIds(equipmentIds); + return ruleResp; + } + + @Override + public void beforeDelete(List ids) { + if (ids.size() == 1) { + List list = baseMapper.querySysRuleList(ids); + CheckUtils.throwIfEmpty(list, "该规则不存在"); + RuleDO ruleDO = list.get(0); + this.deleteRelation(ruleDO.getId()); + } else { + List ruleDOList = baseMapper.querySysRuleList(ids); + CheckUtils.throwIfEmpty(ruleDOList, "规则不存在"); + for (RuleDO ruleDO : ruleDOList) { + this.deleteRelation(ruleDO.getId()); + } + } + } + + @Override + public String updateRule1(RuleReq ruleReq, Long ruleId) { + if (CollUtil.isNotEmpty(ruleReq.getPermissionList())) { + ruleReq.setPermission(String.join(",", ruleReq.getPermissionList())); + } + + //修改失败的设备名单 + String failure = ""; + List failureAddIds = new ArrayList<>(); + List failureUpdateIds = new ArrayList<>(); + + //创建规则体 + RuleBody ruleBody = this.buildRuleBody(ruleReq); + //规则创建结果 + String response = ""; + + //获取表中所有该规则已绑定的设备 + List ruleRelationList = ruleRelationMapper.lambdaQuery() + .eq(RuleRelationDO::getRuleId, ruleId) + .list(); + + List boundEquipmentIds = ruleRelationList.stream() + .map(RuleRelationDO::getEquipmentId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + //处理修改-删除逻辑 + //处理请求中需要删除的设备 + List deleteIds = boundEquipmentIds.stream() + .filter(num -> !ruleReq.getEquipmentIds().contains(num)) + .collect(Collectors.toList()); + + //处理修改-添加逻辑 + //处理请求中需要添加的设备 + List addIds = ruleReq.getEquipmentIds() + .stream() + .filter(num -> !boundEquipmentIds.contains(num)) + .collect(Collectors.toList()); + if (ObjectUtil.isNotEmpty(addIds)) { + failure = updateAddRelation(ruleReq, addIds, failure, failureAddIds); + } + //处理修改-修改逻辑 + //处理请求中需要修改的设备 + List updateIds = boundEquipmentIds.stream() + .filter(Objects::nonNull) // 过滤null值,避免异常 + .filter(num -> !deleteIds.contains(num) && !addIds.contains(num)) // 一次判断两个条件 + .collect(Collectors.toList()); + if (CollUtil.isNotEmpty(updateIds) && ruleReq.getIsChange()) { + //获取需要修改的设备 + List equipmentRespList = this.getRuleEquipment(updateIds); + //对设备修改规则 + for (EquipmentResp equipmentResp : equipmentRespList) { + //获取密码 + ruleBody.setPass(equipmentResp.getPassword()); + //获取设备上对应规则id + RuleRelationDO ruleRelationDO = ruleRelationMapper.selectOne(new LambdaQueryWrapper() + .eq(RuleRelationDO::getRuleId, ruleId) + .eq(RuleRelationDO::getEquipmentId, equipmentResp.getId())); + ruleBody.setRuleId(String.valueOf(ruleRelationDO.getEquipmentRuleId())); + response = yfPushService.ruleUpdate(ruleBody, equipmentResp.getIp()); + JSONObject jsonObject = JSONObject.parseObject(response); + if (jsonObject == null || !jsonObject.containsKey("code") || !StrUtil.equals("LAN_SUS-0", jsonObject + .getString("code"))) { + log.info(equipmentResp.getName() + "规则修改失败!"); + failure += equipmentResp.getName() + ","; + failureUpdateIds.add(equipmentResp.getId()); + } + } + if (ObjectUtil.isNotEmpty(failureUpdateIds)) { + updateDeleteRelation(ruleId, failureUpdateIds); + } + } + CheckUtils.throwIfEqual(failureUpdateIds.size() + failureAddIds.size(), updateIds.size() + addIds + .size(), "规则涉及修改和新增的设备全部异常!!!"); + //删除设备放在最后再执行 防止提前删除 导致规则无绑定的设备的情况 + if (ObjectUtil.isNotEmpty(deleteIds)) { + updateDeleteRelation(ruleId, deleteIds); + } + if (ruleReq.getIsChange()) { + updateUpdate(ruleReq, ruleId); + } + if (ObjectUtil.equal(failure, "")) { + return "规则修改完成,规则正常下发"; + } else { + return "规则修改完成,存在设备下发异常,请检查后重试。异常设备名单如下:" + failure; + } + } + + @Override + public List getEquipmentRulesByPeoples(List peopleIds) { + if (CollUtil.isEmpty(peopleIds)) { + return new ArrayList<>(); + } + return ruleMapper.getEquipmentRulesByPeoples(peopleIds); + } + + @Override + public List getRuleNameList(RuleReq ruleReq) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.like(StrUtil.isNotBlank(ruleReq.getName()), RuleDO::getName, ruleReq.getName()); + List ruleDOS = ruleMapper.selectList(queryWrapper); + return ruleConvert.labelValueList(ruleDOS); + } + + //从设备上删除+从关联表删去 + public void deleteRelation(Long ruleId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(RuleRelationDO::getRuleId, ruleId); + List ruleRelationDOList = ruleRelationMapper.selectList(queryWrapper); + for (RuleRelationDO ruleRelationDO : ruleRelationDOList) { + EquipmentResp equipmentResp = equipmentService.selectEquipmentById(ruleRelationDO.getEquipmentId()); + String response = yfPushService.ruleDelete(ruleRelationDO.getEquipmentRuleId(), equipmentResp + .getIp(), equipmentResp.getPassword(), 2); + JSONObject jsonObject = JSONObject.parseObject(response); + if (jsonObject == null || !jsonObject.containsKey("code") || !StrUtil.equals("LAN_SUS-0", jsonObject + .getString("code"))) { + log.info(equipmentResp.getName() + "状态异常,规则删除失败"); + } + ruleRelationMapper.deleteByRuleId(ruleRelationDO); + } + branchRuleMapper.delete(new QueryWrapper().eq("rule_id", ruleId)); + } + + public void updateDeleteRelation(Long ruleId, List deleteIds) { + List ruleRelationList = ruleRelationMapper.lambdaQuery() + .eq(RuleRelationDO::getRuleId, ruleId) + .in(RuleRelationDO::getEquipmentId, deleteIds) + .list(); + for (RuleRelationDO ruleRelationDO : ruleRelationList) { + EquipmentResp equipmentResp = equipmentService.selectEquipmentById(ruleRelationDO.getEquipmentId()); + String response = yfPushService.ruleDelete(ruleRelationDO.getEquipmentRuleId(), equipmentResp + .getIp(), equipmentResp.getPassword(), 2); + JSONObject jsonObject = JSONObject.parseObject(response); + if (jsonObject == null || !jsonObject.containsKey("code") || !StrUtil.equals("LAN_SUS-0", jsonObject + .getString("code"))) { + log.info(equipmentResp.getName() + "删除失败"); + } + ruleRelationMapper.deleteByRuleId(ruleRelationDO); + } + } + + public String updateAddRelation(RuleReq ruleReq, List addIds, String failure, List deleteIds) { + //创建规则体 + RuleBody ruleBody = this.buildRuleBody(ruleReq); + //规则创建结果 + String response = ""; + //获取人脸机设备 + List equipmentRespList = this.getRuleEquipment(addIds); + //向所有在线设备添加规则 + for (EquipmentResp equipmentResp : equipmentRespList) { + //获取密码 + ruleBody.setPass(equipmentResp.getPassword()); + response = yfPushService.ruleCreate(ruleBody, equipmentResp.getIp()); + JSONObject jsonObject = JSONObject.parseObject(response); + if (jsonObject == null || !jsonObject.containsKey("code") || !StrUtil.equals("LAN_SUS-0", jsonObject + .getString("code"))) { + log.info(ruleReq.getName() + "下发设备:" + equipmentResp.getName() + "失败"); + failure += equipmentResp.getName() + ","; + deleteIds.add(equipmentResp.getId()); + } else { + JSONObject data = jsonObject.getJSONObject("data"); + String equipmentRuleId = data.getString("ruleId"); + createRelation(ruleReq.getId(), equipmentResp.getId(), equipmentRuleId); + } + } + return failure; + } + + public void updateUpdate(RuleReq ruleReq, Long ruleId) { + RuleDO ruleDO = new RuleDO(); + ruleDO.setId(ruleId); + if (StringUtils.hasText(ruleReq.getName())) { + ruleDO.setName(ruleReq.getName()); + } + if (ObjectUtil.isNotEmpty(ruleReq.getStartTime())) { + ruleDO.setStartTime(ruleReq.getStartTime()); + } + if (ObjectUtil.isNotEmpty(ruleReq.getEndTime())) { + ruleDO.setEndTime(ruleReq.getEndTime()); + } + if (ObjectUtil.isNotEmpty(ruleReq.getAdmittanceStart())) { + ruleDO.setAdmittanceStart(ruleReq.getAdmittanceStart()); + } + if (ObjectUtil.isNotEmpty(ruleReq.getAdmittanceEnd())) { + ruleDO.setAdmittanceEnd(ruleReq.getAdmittanceEnd()); + } + if (ObjectUtil.isNotEmpty(ruleReq.getType())) { + ruleDO.setType(ruleReq.getType()); + } + List permissionList = ruleReq.getPermissionList(); + if (permissionList != null && !permissionList.isEmpty()) { + ruleDO.setPermission(String.join(",", permissionList)); + } + baseMapper.updateById(ruleDO); + } + + //保存规则设备关联表 + public void createRelation(Long ruleId, Long equipmentId, String equipmentRuleId) { + RuleRelationDO ruleRelationDO = new RuleRelationDO(); + ruleRelationDO.setRuleId(ruleId); + ruleRelationDO.setEquipmentId(equipmentId); + ruleRelationDO.setEquipmentRuleId(equipmentRuleId); + ruleRelationMapper.insert(ruleRelationDO); + } + + public RuleBody buildRuleBody(RuleReq ruleReq) { + //创建 RuleBody 实例并设置基础参数 + RuleBody ruleBody = new RuleBody(); + ruleBody.setRuleId(IdUtil.simpleUUID()); + ruleBody.setRuleName(ruleReq.getName()); + + //转换规则生命周期为东八区时间戳(毫秒) + Long startTimeStamp = ruleReq.getStartTime().toInstant(ZoneOffset.ofHours(8)).toEpochMilli(); + Long endTimeStamp = ruleReq.getEndTime().toInstant(ZoneOffset.ofHours(8)).toEpochMilli(); + // 构建 content 嵌套结构 + // 构建时段分段(准入开始/结束) + List segments = Arrays.asList(ruleReq.getAdmittanceStart(), ruleReq.getAdmittanceEnd()); + // 构建单日规则项 + Map dayItem = RuleBody.createDayItem(endTimeStamp, startTimeStamp, segments); + List> dayList = Arrays.asList(dayItem); + // 构建完整 content + Map content = RuleBody.createContent(endTimeStamp, startTimeStamp, dayList); + + // 设置 content 并返回 + ruleBody.setContent(content); + return ruleBody; + } + + public List getRuleEquipment(List equipmentIds) { + List equipmentRespList = new ArrayList<>(); + for (Long equipmentId : equipmentIds) { + EquipmentResp equipmentResp = equipmentService.selectEquipmentById(equipmentId); + CheckUtils.throwIfNotEqual(equipmentResp.getFlag(), "0", equipmentResp.getName() + "不在线"); + equipmentRespList.add(equipmentResp); + } + return equipmentRespList; + } + + @Override + @ContainerMethod(namespace = ContainerConstants.RULE_NAME, type = MappingType.ORDER_OF_KEYS) + public String getRuleName(Long id) { + if (null == id) { + return ""; + } + + RuleDO ruleDO = baseMapper.lambdaQuery().eq(RuleDO::getId, id).one(); + return ruleDO != null ? ruleDO.getName() : ""; + } + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/space/mapper/PointMapper.java b/wms-module-system/src/main/java/top/wms/admin/space/mapper/PointMapper.java new file mode 100644 index 0000000..b22cf61 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/mapper/PointMapper.java @@ -0,0 +1,20 @@ +package top.ysoft.admin.space.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.space.model.entity.PointDO; +import top.ysoft.admin.space.model.resp.PointResp; + +/** + * 点位管理 Mapper + * + * @author zc + * @since 2025/04/03 15:01 + */ +public interface PointMapper extends BaseMapper { + IPage selectPointPage(@Param("page") IPage page, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/mapper/SpaceMapper.java b/wms-module-system/src/main/java/top/wms/admin/space/mapper/SpaceMapper.java new file mode 100644 index 0000000..8057bbe --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/mapper/SpaceMapper.java @@ -0,0 +1,17 @@ +package top.ysoft.admin.space.mapper; + +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.space.model.entity.SpaceDO; +import top.ysoft.admin.space.model.resp.SpaceResp; + +import java.util.List; + +/** + * 空间管理 Mapper + * + * @author zc + * @since 2025/04/03 10:47 + */ +public interface SpaceMapper extends BaseMapper { + List queryJuniorSpace(Long id); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/mapstruct/PointConvert.java b/wms-module-system/src/main/java/top/wms/admin/space/mapstruct/PointConvert.java new file mode 100644 index 0000000..63fc2bd --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/mapstruct/PointConvert.java @@ -0,0 +1,18 @@ +package top.ysoft.admin.space.mapstruct; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import top.ysoft.admin.space.model.entity.PointDO; +import top.ysoft.admin.space.model.resp.SpaceResp; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface PointConvert { + + @Mapping(source = "spaceId", target = "parentId") + @Mapping(target = "type", expression = "java(\"2\")") + SpaceResp PointToSpace(PointDO pointDO); + + List PointToSpaceList(List pointDO); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/space/mapstruct/SpaceConvert.java b/wms-module-system/src/main/java/top/wms/admin/space/mapstruct/SpaceConvert.java new file mode 100644 index 0000000..92f8591 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/mapstruct/SpaceConvert.java @@ -0,0 +1,12 @@ +package top.ysoft.admin.space.mapstruct; + +import org.mapstruct.Mapper; +import top.ysoft.admin.space.model.entity.SpaceDO; +import top.ysoft.admin.space.model.resp.SpaceResp; + +@Mapper(componentModel = "spring") +public interface SpaceConvert { + + SpaceResp DOToResp(SpaceDO spaceDO); + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/space/model/entity/PointDO.java b/wms-module-system/src/main/java/top/wms/admin/space/model/entity/PointDO.java new file mode 100644 index 0000000..db50942 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/model/entity/PointDO.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.space.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 点位管理实体 + * + * @author zc + * @since 2025/04/03 15:01 + */ +@Data +@TableName("sys_point") +public class PointDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 点位名称 + */ + private String name; + + /** + * 所属空间 + */ + private Long spaceId; + + /** + * 备注 + */ + private String remark; + + /** + * 所属空间 + */ + private Long sort; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/model/entity/SpaceDO.java b/wms-module-system/src/main/java/top/wms/admin/space/model/entity/SpaceDO.java new file mode 100644 index 0000000..f4cba90 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/model/entity/SpaceDO.java @@ -0,0 +1,48 @@ +package top.ysoft.admin.space.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 空间管理实体 + * + * @author zc + * @since 2025/04/03 10:47 + */ +@Data +@TableName("sys_space") +public class SpaceDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 空间名称 + */ + private String name; + + /** + * 父部门id + */ + private Long parentId; + + /** + * 祖级列表 + */ + private String ancestors; + + /** + * 空间类型(0父级 1子级) + */ + private String type; + + /** + * 备注 + */ + private String remark; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/model/query/PointQuery.java b/wms-module-system/src/main/java/top/wms/admin/space/model/query/PointQuery.java new file mode 100644 index 0000000..8632c17 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/model/query/PointQuery.java @@ -0,0 +1,35 @@ +package top.ysoft.admin.space.model.query; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 点位管理查询条件 + * + * @author zc + * @since 2025/04/03 15:01 + */ +@Data +@Schema(description = "点位管理查询条件") +public class PointQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 点位名称 + */ + @Schema(description = "点位名称") + private String name; + + /** + * 点位名称 + */ + @Schema(description = "空间id") + private Long spaceId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/model/query/SpaceQuery.java b/wms-module-system/src/main/java/top/wms/admin/space/model/query/SpaceQuery.java new file mode 100644 index 0000000..2aa19f9 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/model/query/SpaceQuery.java @@ -0,0 +1,40 @@ +package top.ysoft.admin.space.model.query; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 空间管理查询条件 + * + * @author zc + * @since 2025/04/03 10:47 + */ +@Data +@Schema(description = "空间管理查询条件") +public class SpaceQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 空间名称 + */ + @Schema(description = "空间名称") + @Query(type = QueryType.EQ) + private String name; + + /** + * 空间类型(0父级 1子级) + */ + @Schema(description = "空间类型(0父级 1子级)") + @Query(type = QueryType.EQ) + private String type; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/model/req/PointReq.java b/wms-module-system/src/main/java/top/wms/admin/space/model/req/PointReq.java new file mode 100644 index 0000000..0416c12 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/model/req/PointReq.java @@ -0,0 +1,54 @@ +package top.ysoft.admin.space.model.req; + +import jakarta.validation.constraints.*; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 创建或修改点位管理参数 + * + * @author zc + * @since 2025/04/03 15:01 + */ +@Data +@Schema(description = "创建或修改点位管理参数") +public class PointReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 点位名称 + */ + @Schema(description = "点位名称") + @NotBlank(message = "点位名称不能为空") + @Length(max = 50, message = "点位名称长度不能超过 {max} 个字符") + private String name; + + /** + * 所属空间 + */ + @Schema(description = "所属空间") + private Long spaceId; + + /** + * 备注 + */ + @Schema(description = "备注") + @Length(max = 200, message = "备注长度不能超过 {max} 个字符") + private String remark; + + /** + * 所属空间 + */ + @Schema(description = "所属空间") + private Long sort; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/model/req/SpaceReq.java b/wms-module-system/src/main/java/top/wms/admin/space/model/req/SpaceReq.java new file mode 100644 index 0000000..d47e0b9 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/model/req/SpaceReq.java @@ -0,0 +1,74 @@ +package top.ysoft.admin.space.model.req; + +import jakarta.validation.constraints.*; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 创建或修改空间管理参数 + * + * @author zc + * @since 2025/04/03 10:47 + */ +@Data +@Schema(description = "创建或修改空间管理参数") +public class SpaceReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 空间名称 + */ + @Schema(description = "空间名称") + @NotBlank(message = "空间名称不能为空") + @Length(max = 50, message = "空间名称长度不能超过 {max} 个字符") + private String name; + + /** + * 父部门id + */ + @Schema(description = "父部门id") + private Long parentId; + + /** + * 祖级列表 + */ + @Schema(description = "祖级列表") + @Length(max = 50, message = "祖级列表长度不能超过 {max} 个字符") + private String ancestors; + + /** + * 空间类型(0父级 1子级) + */ + @Schema(description = "空间类型(0父级 1子级)") + @Length(max = 1, message = "空间类型(0父级 1子级)长度不能超过 {max} 个字符") + private String type; + + /** + * 备注 + */ + @Schema(description = "备注") + @Length(max = 200, message = "备注长度不能超过 {max} 个字符") + private String remark; + + /** + * 创建时间 + */ + @Schema(description = "创建时间") + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/model/resp/PointResp.java b/wms-module-system/src/main/java/top/wms/admin/space/model/resp/PointResp.java new file mode 100644 index 0000000..89e6355 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/model/resp/PointResp.java @@ -0,0 +1,66 @@ +package top.ysoft.admin.space.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; + +/** + * 点位管理信息 + * + * @author zc + * @since 2025/04/03 15:01 + */ +@Data +@Schema(description = "点位管理信息") +public class PointResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 点位名称 + */ + @Schema(description = "点位名称") + private String name; + + /** + * 所属空间 + */ + @Schema(description = "所属空间") + private Long spaceId; + + /** + * 所属空间名称 + */ + @Schema(description = "所属空间名称") + private String spaceName; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; + + /** + * + */ + @Schema(description = "") + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + /** + * 所属空间 + */ + @Schema(description = "所属空间") + private Long sort; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/model/resp/SpaceResp.java b/wms-module-system/src/main/java/top/wms/admin/space/model/resp/SpaceResp.java new file mode 100644 index 0000000..35a543a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/model/resp/SpaceResp.java @@ -0,0 +1,68 @@ +package top.ysoft.admin.space.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.continew.starter.extension.crud.annotation.TreeField; +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; + +/** + * 空间管理信息 + * + * @author zc + * @since 2025/04/03 10:47 + */ +@Data +@TreeField(value = "id", nameKey = "name") +@Schema(description = "空间管理信息") +public class SpaceResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 空间名称 + */ + @Schema(description = "空间名称") + private String name; + + /** + * 父部门id + */ + @Schema(description = "父部门id") + private Long parentId; + + /** + * 祖级列表 + */ + @Schema(description = "祖级列表") + private String ancestors; + + /** + * 空间类型(0父级 1子级) + */ + @Schema(description = "空间类型(0父级 1子级)") + private String type; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + /** + * 排序 + */ + @Schema(description = "排序") + private Integer sort; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/service/PointService.java b/wms-module-system/src/main/java/top/wms/admin/space/service/PointService.java new file mode 100644 index 0000000..d67bc01 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/service/PointService.java @@ -0,0 +1,20 @@ +package top.ysoft.admin.space.service; + +import cn.hutool.core.lang.tree.Tree; +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.space.model.query.PointQuery; +import top.ysoft.admin.space.model.req.PointReq; +import top.ysoft.admin.space.model.resp.PointResp; + +import java.util.List; + +/** + * 点位管理业务接口 + * + * @author zc + * @since 2025/04/03 15:01 + */ +public interface PointService extends BaseService { + + List> tree(PointQuery query); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/service/SpaceService.java b/wms-module-system/src/main/java/top/wms/admin/space/service/SpaceService.java new file mode 100644 index 0000000..aa56b7a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/service/SpaceService.java @@ -0,0 +1,21 @@ +package top.ysoft.admin.space.service; + +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.space.model.query.SpaceQuery; +import top.ysoft.admin.space.model.req.SpaceReq; +import top.ysoft.admin.space.model.resp.SpaceResp; + +import java.util.List; + +/** + * 空间管理业务接口 + * + * @author zc + * @since 2025/04/03 10:47 + */ +public interface SpaceService extends BaseService { + + List queryJuniorSpace(Long id); + + String getSpaceNameById(Long id); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/service/impl/PointServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/space/service/impl/PointServiceImpl.java new file mode 100644 index 0000000..4bb7a5f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/service/impl/PointServiceImpl.java @@ -0,0 +1,89 @@ +package top.ysoft.admin.space.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNodeConfig; +import cn.hutool.core.lang.tree.TreeUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.query.SortQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.space.mapper.PointMapper; +import top.ysoft.admin.space.mapstruct.PointConvert; +import top.ysoft.admin.space.model.entity.PointDO; +import top.ysoft.admin.space.model.query.PointQuery; +import top.ysoft.admin.space.model.query.SpaceQuery; +import top.ysoft.admin.space.model.req.PointReq; +import top.ysoft.admin.space.model.resp.PointResp; +import top.ysoft.admin.space.model.resp.SpaceResp; +import top.ysoft.admin.space.service.PointService; +import top.ysoft.admin.space.service.SpaceService; + +import java.util.ArrayList; +import java.util.List; + +/** + * 点位管理业务实现 + * + * @author zc + * @since 2025/04/03 15:01 + */ +@Service +@RequiredArgsConstructor +public class PointServiceImpl extends BaseServiceImpl implements PointService { + + private final SpaceService spaceService; + + private final PointConvert pointConvert; + + @Override + public PageResp page(PointQuery query, PageQuery pageQuery) { + QueryWrapper queryWrapper = this.buildQueryWrapper(query); + super.sort(queryWrapper, pageQuery); + IPage page = baseMapper.selectPointPage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + PageResp pageResp = PageResp.build(page, super.getListClass()); + return pageResp; + } + + @Override + protected QueryWrapper buildQueryWrapper(PointQuery query) { + + List spaceIds = new ArrayList<>(); + Long spaceId = query.getSpaceId(); + if (null != spaceId) { + List spaceResps = spaceService.queryJuniorSpace(spaceId); + spaceIds = spaceResps.stream().map(SpaceResp::getId).toList(); + } + + return new QueryWrapper().like(StrUtil.isNotBlank(query.getName()), "t1.name", query.getName()) + .in(CollUtil.isNotEmpty(spaceIds), "space_id", spaceIds); + } + + @Override + public List> tree(PointQuery query) { + //查询空间列表 + List list = spaceService.list(new SpaceQuery(), new SortQuery()); + + //查询点位信息 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(StrUtil.isNotBlank(query.getName()), PointDO::getName, query.getName()); + List spaceResps = pointConvert.PointToSpaceList(baseMapper.selectList(queryWrapper)); + list.addAll(spaceResps); + + TreeNodeConfig config = new TreeNodeConfig(); + return TreeUtil.build(list, 0L, config, (treeNode, tree) -> { + tree.setId(treeNode.getId()); + tree.putExtra("key", treeNode.getId()); + tree.putExtra("title", treeNode.getName()); + tree.setParentId(treeNode.getParentId()); + }); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/space/service/impl/SpaceServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/space/service/impl/SpaceServiceImpl.java new file mode 100644 index 0000000..8cf6e4f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/space/service/impl/SpaceServiceImpl.java @@ -0,0 +1,49 @@ +package top.ysoft.admin.space.service.impl; + +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.space.mapper.SpaceMapper; +import top.ysoft.admin.space.mapstruct.SpaceConvert; +import top.ysoft.admin.space.model.entity.SpaceDO; +import top.ysoft.admin.space.model.query.SpaceQuery; +import top.ysoft.admin.space.model.req.SpaceReq; +import top.ysoft.admin.space.model.resp.SpaceResp; +import top.ysoft.admin.space.service.SpaceService; + +import java.util.List; + +/** + * 空间管理业务实现 + * + * @author zc + * @since 2025/04/03 10:47 + */ +@Service +@RequiredArgsConstructor +public class SpaceServiceImpl extends BaseServiceImpl implements SpaceService { + + private final SpaceConvert spaceConvert; + + @Override + public List queryJuniorSpace(Long id) { + SpaceResp spaceResp = spaceConvert.DOToResp(baseMapper.selectById(id)); + List spaceResps = baseMapper.queryJuniorSpace(id); + spaceResps.add(spaceResp); + return spaceResps; + } + + @Override + @ContainerMethod(namespace = ContainerConstants.SPACE_NAME, type = MappingType.ORDER_OF_KEYS) + public String getSpaceNameById(Long id) { + if (null == id) { + return ""; + } + + SpaceDO spaceDO = baseMapper.lambdaQuery().eq(SpaceDO::getId, id).one(); + return spaceDO.getName(); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/config/file/FileInfoContainer.java b/wms-module-system/src/main/java/top/wms/admin/system/config/file/FileInfoContainer.java new file mode 100644 index 0000000..f34abef --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/config/file/FileInfoContainer.java @@ -0,0 +1,38 @@ +package top.ysoft.admin.system.config.file; + +import cn.crane4j.core.container.Container; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.system.model.entity.FileDO; +import top.ysoft.admin.system.service.FileService; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 文件信息填充容器 + * + * @author luoqiz + * @since 2025/3/12 18:11 + */ +@Component +@RequiredArgsConstructor +public class FileInfoContainer implements Container { + + private final FileService fileService; + + @Override + public String getNamespace() { + return ContainerConstants.FILE_INFO; + } + + @Override + public Map get(Collection ids) { + List list = fileService.listByIds(ids); + return list.stream().collect(Collectors.toMap(FileDO::getId, Function.identity())); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/config/file/FileRecorderImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/config/file/FileRecorderImpl.java new file mode 100644 index 0000000..8a481f2 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/config/file/FileRecorderImpl.java @@ -0,0 +1,127 @@ +package top.ysoft.admin.system.config.file; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.EscapeUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.springframework.stereotype.Component; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.system.enums.FileTypeEnum; +import top.ysoft.admin.system.mapper.FileMapper; +import top.ysoft.admin.system.mapper.StorageMapper; +import top.ysoft.admin.system.model.entity.FileDO; +import top.ysoft.admin.system.model.entity.StorageDO; +import top.continew.starter.core.constant.StringConstants; + +import java.util.Optional; + +/** + * 文件记录实现类 + * + * @author Charles7c + * @since 2023/12/24 22:31 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class FileRecorderImpl implements FileRecorder { + + private final FileMapper fileMapper; + private final StorageMapper storageMapper; + private final IdentifierGenerator identifierGenerator; + + @Override + public boolean save(FileInfo fileInfo) { + if (StrUtil.isNotBlank(fileInfo.getObjectType()) && StrUtil.equals("false", fileInfo.getObjectType())) { + return true; + } + FileDO file = new FileDO(); + Number id = identifierGenerator.nextId(fileInfo); + file.setId(id.longValue()); + fileInfo.setId(id.longValue() + ""); + String originalFilename = EscapeUtil.unescape(fileInfo.getOriginalFilename()); + file.setName(StrUtil.contains(originalFilename, StringConstants.DOT) + ? StrUtil.subBefore(originalFilename, StringConstants.DOT, true) + : originalFilename); + file.setUrl(fileInfo.getUrl()); + file.setSize(fileInfo.getSize()); + String absPath = fileInfo.getPath(); + if (absPath.endsWith(StringConstants.SLASH)) { + String tempAbsPath = absPath.substring(0, absPath.length() - 1); + String[] pathArr = tempAbsPath.split(StringConstants.SLASH); + if (pathArr.length > 1) { + file.setParentPath(pathArr[pathArr.length - 1]); + } else { + file.setParentPath(StringConstants.SLASH); + } + } + file.setAbsPath(fileInfo.getPath()); + file.setExtension(fileInfo.getExt()); + file.setType(FileTypeEnum.getByExtension(file.getExtension())); + file.setContentType(fileInfo.getContentType()); + file.setMd5(fileInfo.getHashInfo().getMd5()); + file.setMetadata(JSONUtil.toJsonStr(fileInfo.getMetadata())); + file.setThumbnailUrl(fileInfo.getThUrl()); + file.setThumbnailSize(fileInfo.getThSize()); + file.setThumbnailMetadata(JSONUtil.toJsonStr(fileInfo.getThMetadata())); + StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false)); + file.setStorageId(storage.getId()); + file.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime())); + file.setUpdateUser(UserContextHolder.getUserId()); + file.setUpdateTime(file.getCreateTime()); + fileMapper.insert(file); + return true; + } + + @Override + public FileInfo getByUrl(String url) { + FileDO file = this.getFileByUrl(url); + if (null == file) { + return null; + } + StorageDO storageDO = storageMapper.lambdaQuery().eq(StorageDO::getId, file.getStorageId()).one(); + return file.toFileInfo(storageDO); + } + + @Override + public boolean delete(String url) { + FileDO file = this.getFileByUrl(url); + return fileMapper.lambdaUpdate().eq(FileDO::getUrl, file.getUrl()).remove(); + } + + @Override + public void update(FileInfo fileInfo) { + /* 不使用分片功能则无需重写 */ + } + + @Override + public void saveFilePart(FilePartInfo filePartInfo) { + /* 不使用分片功能则无需重写 */ + } + + @Override + public void deleteFilePartByUploadId(String s) { + /* 不使用分片功能则无需重写 */ + } + + /** + * 根据 URL 查询文件 + * + * @param url URL + * @return 文件信息 + */ + private FileDO getFileByUrl(String url) { + Optional fileOptional = fileMapper.lambdaQuery().eq(FileDO::getUrl, url).oneOpt(); + return fileOptional.orElseGet(() -> fileMapper.lambdaQuery() + .likeLeft(FileDO::getUrl, StrUtil.subAfter(url, StringConstants.SLASH, true)) + .oneOpt() + .orElse(null)); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/config/file/FileStorageConfigLoader.java b/wms-module-system/src/main/java/top/wms/admin/system/config/file/FileStorageConfigLoader.java new file mode 100644 index 0000000..0683582 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/config/file/FileStorageConfigLoader.java @@ -0,0 +1,41 @@ +package top.ysoft.admin.system.config.file; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.model.query.StorageQuery; +import top.ysoft.admin.system.model.req.StorageReq; +import top.ysoft.admin.system.model.resp.StorageResp; +import top.ysoft.admin.system.service.StorageService; + +import java.util.List; + +/** + * 文件存储配置加载器 + * + * @author Charles7c + * @since 2023/12/24 22:31 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class FileStorageConfigLoader implements ApplicationRunner { + + private final StorageService storageService; + + @Override + public void run(ApplicationArguments args) { + StorageQuery query = new StorageQuery(); + query.setStatus(DisEnableStatusEnum.ENABLE); + List storageList = storageService.list(query, null); + if (CollUtil.isEmpty(storageList)) { + return; + } + storageList.forEach(s -> storageService.load(BeanUtil.copyProperties(s, StorageReq.class))); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/config/mail/MailConfigurerImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/config/mail/MailConfigurerImpl.java new file mode 100644 index 0000000..4eb5038 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/config/mail/MailConfigurerImpl.java @@ -0,0 +1,45 @@ +package top.ysoft.admin.system.config.mail; + +import cn.hutool.core.map.MapUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.system.enums.OptionCategoryEnum; +import top.ysoft.admin.system.service.OptionService; +import top.continew.starter.messaging.mail.core.MailConfig; +import top.continew.starter.messaging.mail.core.MailConfigurer; + +import java.util.Map; + +/** + * 邮件配置实现 + * + * @author Charles7c + * @since 2024/5/30 22:32 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class MailConfigurerImpl implements MailConfigurer { + + private final OptionService optionService; + + @Override + public MailConfig getMailConfig() { + // 查询邮件配置 + Map map = optionService.getByCategory(OptionCategoryEnum.MAIL); + // 封装邮件配置 + MailConfig mailConfig = new MailConfig(); + mailConfig.setProtocol(MapUtil.getStr(map, "MAIL_PROTOCOL")); + mailConfig.setHost(MapUtil.getStr(map, "MAIL_HOST")); + mailConfig.setPort(MapUtil.getInt(map, "MAIL_PORT")); + mailConfig.setUsername(MapUtil.getStr(map, "MAIL_USERNAME")); + mailConfig.setPassword(MapUtil.getStr(map, "MAIL_PASSWORD")); + mailConfig.setSslEnabled(SysConstants.YES.equals(MapUtil.getInt(map, "MAIL_SSL_ENABLED"))); + if (mailConfig.isSslEnabled()) { + mailConfig.setSslPort(MapUtil.getInt(map, "MAIL_SSL_PORT")); + } + return mailConfig; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/FileTypeEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/FileTypeEnum.java new file mode 100644 index 0000000..8a25630 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/FileTypeEnum.java @@ -0,0 +1,69 @@ +package top.ysoft.admin.system.enums; + +import cn.hutool.core.util.StrUtil; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * 文件类型枚举 + * + * @author Charles7c + * @since 2023/12/23 13:38 + */ +@Getter +@RequiredArgsConstructor +public enum FileTypeEnum implements BaseEnum { + + /** + * 目录 + */ + DIR(0, "目录", Collections.emptyList()), + + /** + * 其他 + */ + UNKNOWN(1, "其他", Collections.emptyList()), + + /** + * 图片 + */ + IMAGE(2, "图片", List + .of("jpg", "jpeg", "png", "gif", "bmp", "webp", "ico", "psd", "tiff", "dwg", "jxr", "apng", "xcf")), + + /** + * 文档 + */ + DOC(3, "文档", List.of("txt", "pdf", "doc", "xls", "ppt", "docx", "xlsx", "pptx")), + + /** + * 视频 + */ + VIDEO(4, "视频", List.of("mp4", "avi", "mkv", "flv", "webm", "wmv", "m4v", "mov", "mpg", "rmvb", "3gp")), + + /** + * 音频 + */ + AUDIO(5, "音频", List.of("mp3", "flac", "wav", "ogg", "midi", "m4a", "aac", "amr", "ac3", "aiff")),; + + private final Integer value; + private final String description; + private final List extensions; + + /** + * 根据扩展名查询 + * + * @param extension 扩展名 + * @return 文件类型 + */ + public static FileTypeEnum getByExtension(String extension) { + return Arrays.stream(FileTypeEnum.values()) + .filter(t -> t.getExtensions().contains(StrUtil.emptyIfNull(extension).toLowerCase())) + .findFirst() + .orElse(FileTypeEnum.UNKNOWN); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/ImportPolicyEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/ImportPolicyEnum.java new file mode 100644 index 0000000..d5b6077 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/ImportPolicyEnum.java @@ -0,0 +1,41 @@ +package top.ysoft.admin.system.enums; + +import cn.hutool.core.collection.CollUtil; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +import java.util.List; + +/** + * 数据导入策略 + * + * @author Kils + * @since 2024-06-17 18:33 + */ +@Getter +@RequiredArgsConstructor +public enum ImportPolicyEnum implements BaseEnum { + + /** + * 跳过该行 + */ + SKIP(1, "跳过该行"), + + /** + * 修改数据 + */ + UPDATE(2, "修改数据"), + + /** + * 停止导入 + */ + EXIT(3, "停止导入"); + + private final Integer value; + private final String description; + + public boolean validate(ImportPolicyEnum importPolicy, String data, List existList) { + return this == importPolicy && CollUtil.isNotEmpty(existList) && existList.contains(data); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/LogStatusEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/LogStatusEnum.java new file mode 100644 index 0000000..4cfd0e9 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/LogStatusEnum.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.system.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 操作状态枚举 + * + * @author Charles7c + * @since 2022/12/25 9:09 + */ +@Getter +@RequiredArgsConstructor +public enum LogStatusEnum implements BaseEnum { + + /** + * 成功 + */ + SUCCESS(1, "成功"), + + /** + * 失败 + */ + FAILURE(2, "失败"),; + + private final Integer value; + private final String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/MenuTypeEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/MenuTypeEnum.java new file mode 100644 index 0000000..26dacdb --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/MenuTypeEnum.java @@ -0,0 +1,34 @@ +package top.ysoft.admin.system.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 菜单类型枚举 + * + * @author Charles7c + * @since 2023/2/15 20:12 + */ +@Getter +@RequiredArgsConstructor +public enum MenuTypeEnum implements BaseEnum { + + /** + * 目录 + */ + DIR(1, "目录"), + + /** + * 菜单 + */ + MENU(2, "菜单"), + + /** + * 按钮 + */ + BUTTON(3, "按钮"),; + + private final Integer value; + private final String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/MessageTemplateEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/MessageTemplateEnum.java new file mode 100644 index 0000000..fd44037 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/MessageTemplateEnum.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.system.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * 消息模板枚举 + * + * @author Bull-BCLS + * @since 2023/10/15 19:51 + */ +@Getter +@RequiredArgsConstructor +public enum MessageTemplateEnum { + + /** + * 第三方登录 + */ + SOCIAL_REGISTER("欢迎注册 %s", "尊敬的 %s,欢迎注册使用,请及时配置您的密码。"); + + private final String title; + private final String content; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/MessageTypeEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/MessageTypeEnum.java new file mode 100644 index 0000000..a78173b --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/MessageTypeEnum.java @@ -0,0 +1,26 @@ +package top.ysoft.admin.system.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.ysoft.admin.common.constant.UiConstants; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 消息类型枚举 + * + * @author Charles7c + * @since 2023/11/2 20:08 + */ +@Getter +@RequiredArgsConstructor +public enum MessageTypeEnum implements BaseEnum { + + /** + * 安全消息 + */ + SECURITY(1, "安全消息", UiConstants.COLOR_PRIMARY),; + + private final Integer value; + private final String description; + private final String color; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/NoticeScopeEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/NoticeScopeEnum.java new file mode 100644 index 0000000..ed0ed6a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/NoticeScopeEnum.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.system.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 公告通知范围枚举 + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +@Getter +@RequiredArgsConstructor +public enum NoticeScopeEnum implements BaseEnum { + + /** + * 所有人 + */ + ALL(1, "所有人"), + + /** + * 指定用户 + */ + USER(2, "指定用户"),; + + private final Integer value; + private final String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/NoticeStatusEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/NoticeStatusEnum.java new file mode 100644 index 0000000..a06c154 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/NoticeStatusEnum.java @@ -0,0 +1,56 @@ +package top.ysoft.admin.system.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.ysoft.admin.common.constant.UiConstants; +import top.continew.starter.core.enums.BaseEnum; + +import java.time.LocalDateTime; + +/** + * 公告状态枚举 + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +@Getter +@RequiredArgsConstructor +public enum NoticeStatusEnum implements BaseEnum { + + /** + * 待发布 + */ + PENDING_RELEASE(1, "待发布", UiConstants.COLOR_PRIMARY), + + /** + * 已发布 + */ + PUBLISHED(2, "已发布", UiConstants.COLOR_SUCCESS), + + /** + * 已过期 + */ + EXPIRED(3, "已过期", UiConstants.COLOR_ERROR),; + + private final Integer value; + private final String description; + private final String color; + + /** + * 获取公告状态 + * + * @param effectiveTime 生效时间 + * @param terminateTime 终止时间 + * @return 公告状态 + */ + public static NoticeStatusEnum getStatus(LocalDateTime effectiveTime, LocalDateTime terminateTime) { + LocalDateTime now = LocalDateTime.now(); + if (effectiveTime != null && effectiveTime.isAfter(now)) { + return PENDING_RELEASE; + } + if (terminateTime != null && terminateTime.isBefore(now)) { + return EXPIRED; + } + return PUBLISHED; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/OptionCategoryEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/OptionCategoryEnum.java new file mode 100644 index 0000000..3021717 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/OptionCategoryEnum.java @@ -0,0 +1,30 @@ +package top.ysoft.admin.system.enums; + +/** + * 参数类别枚举 + * + * @author Charles7c + * @since 2024/11/14 20:00 + */ +public enum OptionCategoryEnum { + + /** + * 系统配置 + */ + SITE, + + /** + * 密码配置 + */ + PASSWORD, + + /** + * 邮箱配置 + */ + MAIL, + + /** + * 登录配置 + */ + LOGIN, +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/PasswordPolicyEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/PasswordPolicyEnum.java new file mode 100644 index 0000000..7dbc2c1 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/PasswordPolicyEnum.java @@ -0,0 +1,181 @@ +package top.ysoft.admin.system.enums; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.ysoft.admin.common.constant.RegexConstants; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.system.model.entity.UserDO; +import top.ysoft.admin.system.service.OptionService; +import top.ysoft.admin.system.service.UserPasswordHistoryService; +import top.continew.starter.core.validation.ValidationUtils; + +import java.util.Map; + +/** + * 密码策略枚举 + * + * @author Kils + * @author Charles7c + * @since 2024/5/9 11:25 + */ +@Getter +@RequiredArgsConstructor +public enum PasswordPolicyEnum { + + /** + * 密码错误锁定阈值 + */ + PASSWORD_ERROR_LOCK_COUNT("密码错误锁定阈值取值范围为 %d-%d", SysConstants.NO, 10, "密码错误已达 %d 次,账号锁定 %d 分钟"), + + /** + * 账号锁定时长(分钟) + */ + PASSWORD_ERROR_LOCK_MINUTES("账号锁定时长取值范围为 %d-%d 分钟", 1, 1440, "账号锁定 %d 分钟,请稍后再试"), + + /** + * 密码有效期(天) + */ + PASSWORD_EXPIRATION_DAYS("密码有效期取值范围为 %d-%d 天", SysConstants.NO, 999, null), + + /** + * 密码到期提醒(天) + */ + PASSWORD_EXPIRATION_WARNING_DAYS("密码到期提醒取值范围为 %d-%d 天", SysConstants.NO, 998, null) { + @Override + public void validateRange(int value, Map policyMap) { + if (CollUtil.isEmpty(policyMap)) { + super.validateRange(value, policyMap); + return; + } + Integer passwordExpirationDays = ObjectUtil.defaultIfNull(Convert.toInt(policyMap + .get(PASSWORD_EXPIRATION_DAYS.name())), SpringUtil.getBean(OptionService.class) + .getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name())); + if (passwordExpirationDays > SysConstants.NO) { + ValidationUtils.throwIf(value >= passwordExpirationDays, "密码到期提醒时间应小于密码有效期"); + return; + } + super.validateRange(value, policyMap); + } + }, + + /** + * 密码最小长度 + */ + PASSWORD_MIN_LENGTH("密码最小长度取值范围为 %d-%d", 8, 32, "密码最小长度为 %d 个字符") { + @Override + public void validate(String password, int value, UserDO user) { + // 最小长度校验 + ValidationUtils.throwIf(StrUtil.length(password) < value, this.getMsg().formatted(value)); + // 完整校验 + int passwordMaxLength = this.getMax(); + ValidationUtils.throwIf(!ReUtil.isMatch(RegexConstants.PASSWORD_TEMPLATE + .formatted(value, passwordMaxLength), password), "密码长度为 {}-{} 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字", value, passwordMaxLength); + } + }, + + /** + * 密码是否必须包含特殊字符 + */ + PASSWORD_REQUIRE_SYMBOLS("密码是否必须包含特殊字符取值只能为是(%d)或否(%d)", SysConstants.NO, SysConstants.YES, "密码必须包含特殊字符") { + @Override + public void validateRange(int value, Map policyMap) { + ValidationUtils.throwIf(value != SysConstants.YES && value != SysConstants.NO, this.getDescription() + .formatted(SysConstants.YES, SysConstants.NO)); + } + + @Override + public void validate(String password, int value, UserDO user) { + ValidationUtils.throwIf(value == SysConstants.YES && !ReUtil + .isMatch(RegexConstants.SPECIAL_CHARACTER, password), this.getMsg()); + } + }, + + /** + * 密码是否允许包含用户名 + */ + PASSWORD_ALLOW_CONTAIN_USERNAME("密码是否允许包含用户名取值只能为是(%d)或否(%d)", SysConstants.NO, SysConstants.YES, "密码不允许包含正反序用户名") { + @Override + public void validateRange(int value, Map policyMap) { + ValidationUtils.throwIf(value != SysConstants.YES && value != SysConstants.NO, this.getDescription() + .formatted(SysConstants.YES, SysConstants.NO)); + } + + @Override + public void validate(String password, int value, UserDO user) { + if (value <= SysConstants.NO) { + String username = user.getUsername(); + ValidationUtils.throwIf(StrUtil.containsAnyIgnoreCase(password, username, StrUtil + .reverse(username)), this.getMsg()); + } + } + }, + + /** + * 历史密码重复校验次数 + */ + PASSWORD_REPETITION_TIMES("历史密码重复校验次数取值范围为 %d-%d", 3, 32, "新密码不得与历史前 %d 次密码重复") { + @Override + public void validate(String password, int value, UserDO user) { + UserPasswordHistoryService userPasswordHistoryService = SpringUtil + .getBean(UserPasswordHistoryService.class); + ValidationUtils.throwIf(userPasswordHistoryService.isPasswordReused(user.getId(), password, value), this + .getMsg() + .formatted(value)); + } + },; + + /** + * 描述 + */ + private final String description; + + /** + * 最小值 + */ + private final Integer min; + + /** + * 最大值 + */ + private final Integer max; + + /** + * 提示信息 + */ + private final String msg; + + /** + * 策略类别 + */ + public static final OptionCategoryEnum CATEGORY = OptionCategoryEnum.PASSWORD; + + /** + * 校验取值范围 + * + * @param value 值 + * @param policyMap 策略集合 + */ + public void validateRange(int value, Map policyMap) { + Integer minValue = this.getMin(); + Integer maxValue = this.getMax(); + ValidationUtils.throwIf(value < minValue || value > maxValue, this.getDescription() + .formatted(minValue, maxValue)); + } + + /** + * 校验 + * + * @param password 密码 + * @param value 策略值 + * @param user 用户信息 + */ + public void validate(String password, int value, UserDO user) { + // 无需校验 + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/SocialSourceEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/SocialSourceEnum.java new file mode 100644 index 0000000..3b62a2f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/SocialSourceEnum.java @@ -0,0 +1,27 @@ +package top.ysoft.admin.system.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * 第三方账号平台枚举 + * + * @author Charles7c + * @since 2023/10/19 21:22 + */ +@Getter +@RequiredArgsConstructor +public enum SocialSourceEnum { + + /** + * 码云 + */ + GITEE("码云"), + + /** + * GitHub + */ + GITHUB("GitHub"),; + + private final String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/enums/StorageTypeEnum.java b/wms-module-system/src/main/java/top/wms/admin/system/enums/StorageTypeEnum.java new file mode 100644 index 0000000..0419803 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/enums/StorageTypeEnum.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.system.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 存储类型枚举 + * + * @author Charles7c + * @since 2023/12/27 21:45 + */ +@Getter +@RequiredArgsConstructor +public enum StorageTypeEnum implements BaseEnum { + + /** + * 本地存储 + */ + LOCAL(1, "本地存储"), + + /** + * 对象存储 + */ + OSS(2, "对象存储"); + + private final Integer value; + private final String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/ClientMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/ClientMapper.java new file mode 100644 index 0000000..18095d5 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/ClientMapper.java @@ -0,0 +1,13 @@ +package top.ysoft.admin.system.mapper; + +import top.ysoft.admin.system.model.entity.ClientDO; +import top.continew.starter.data.mp.base.BaseMapper; + +/** + * 终端 Mapper + * + * @author KAI + * @since 2024/12/03 16:04 + */ +public interface ClientMapper extends BaseMapper { +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/ConfigMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/ConfigMapper.java new file mode 100644 index 0000000..a44aa4f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/ConfigMapper.java @@ -0,0 +1,12 @@ +package top.ysoft.admin.system.mapper; + +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.system.model.entity.ConfigDO; + +/** + * 参数配置 Mapper + * + * @author zc + * @since 2025/12/30 12:44 + */ +public interface ConfigMapper extends BaseMapper {} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/DeptMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/DeptMapper.java new file mode 100644 index 0000000..d255321 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/DeptMapper.java @@ -0,0 +1,13 @@ +package top.ysoft.admin.system.mapper; + +import top.ysoft.admin.system.model.entity.DeptDO; +import top.continew.starter.data.mp.base.BaseMapper; + +/** + * 部门 Mapper + * + * @author Charles7c + * @since 2023/1/22 17:56 + */ +public interface DeptMapper extends BaseMapper { +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/DictItemMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/DictItemMapper.java new file mode 100644 index 0000000..3ff85b4 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/DictItemMapper.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.system.mapper; + +import com.alicp.jetcache.anno.Cached; +import org.apache.ibatis.annotations.Param; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.system.model.entity.DictItemDO; +import top.continew.starter.data.mp.base.BaseMapper; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; + +import java.util.List; + +/** + * 字典项 Mapper + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +public interface DictItemMapper extends BaseMapper { + + /** + * 根据字典编码查询 + * + * @param dictCode 字典编码 + * @return 字典项列表 + */ + @Cached(key = "#dictCode", name = CacheConstants.DICT_KEY_PREFIX) + List listByDictCode(@Param("dictCode") String dictCode); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/DictMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/DictMapper.java new file mode 100644 index 0000000..eb2c4c2 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/DictMapper.java @@ -0,0 +1,13 @@ +package top.ysoft.admin.system.mapper; + +import top.ysoft.admin.system.model.entity.DictDO; +import top.continew.starter.data.mp.base.BaseMapper; + +/** + * 字典 Mapper + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +public interface DictMapper extends BaseMapper { +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/FileMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/FileMapper.java new file mode 100644 index 0000000..dd29600 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/FileMapper.java @@ -0,0 +1,25 @@ +package top.ysoft.admin.system.mapper; + +import org.apache.ibatis.annotations.Select; +import top.ysoft.admin.system.model.entity.FileDO; +import top.ysoft.admin.system.model.resp.file.FileStatisticsResp; +import top.continew.starter.data.mp.base.BaseMapper; + +import java.util.List; + +/** + * 文件 Mapper + * + * @author Charles7c + * @since 2023/12/23 10:38 + */ +public interface FileMapper extends BaseMapper { + + /** + * 查询文件资源统计信息 + * + * @return 文件资源统计信息 + */ + @Select("SELECT type, COUNT(1) number, SUM(size) size FROM sys_file GROUP BY type") + List statistics(); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/LogMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/LogMapper.java new file mode 100644 index 0000000..dfc0a8d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/LogMapper.java @@ -0,0 +1,133 @@ +package top.ysoft.admin.system.mapper; + +import com.alicp.jetcache.anno.Cached; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.system.model.entity.LogDO; +import top.ysoft.admin.system.model.resp.dashboard.DashboardAccessTrendResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardChartCommonResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardOverviewCommonResp; +import top.ysoft.admin.system.model.resp.log.LogResp; +import top.continew.starter.data.mp.base.BaseMapper; + +import java.util.Date; +import java.util.List; + +/** + * 系统日志 Mapper + * + * @author Charles7c + * @since 2022/12/22 21:47 + */ +public interface LogMapper extends BaseMapper { + + /** + * 分页查询列表 + * + * @param page 分页条件 + * @param queryWrapper 查询条件 + * @return 分页列表信息 + */ + IPage selectLogPage(@Param("page") IPage page, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); + + /** + * 查询列表 + * + * @param queryWrapper 查询条件 + * @return 列表信息 + */ + List selectLogList(@Param(Constants.WRAPPER) QueryWrapper queryWrapper); + + /** + * 查询总数量 + * + * @return 总数量 + */ + @Select("SELECT COUNT(*) FROM sys_log") + Long selectTotalCount(); + + /** + * 查询仪表盘 PV 总览 + * + * @return 仪表盘 PV 总览 + */ + DashboardOverviewCommonResp selectDashboardOverviewPv(); + + /** + * 查询仪表盘 IP 总览 + * + * @return 仪表盘 IP 总览 + */ + DashboardOverviewCommonResp selectDashboardOverviewIp(); + + /** + * 查询仪表盘 PV 近 N 月各月份信息 + * + * @param months 近 N 月月份列表 + * @return 仪表盘 PV 近 N 月各月份信息 + */ + @Cached(key = "#months[0]", name = CacheConstants.DASHBOARD_KEY_PREFIX + "PV:") + List selectListDashboardAnalysisPv(@Param("months") List months); + + /** + * 查询仪表盘 IP 近 N 月各月份信息 + * + * @param months 近 N 月月份列表 + * @return 仪表盘 IP 近 N 月各月份信息 + */ + @Cached(key = "#months[0]", name = CacheConstants.DASHBOARD_KEY_PREFIX + "IP:") + List selectListDashboardAnalysisIp(@Param("months") List months); + + /** + * 查询仪表盘地域分析信息 + * + * @return 仪表盘地域分析信息 + */ + List selectListDashboardAnalysisGeo(); + + /** + * 查询仪表盘访问趋势信息 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 仪表盘访问趋势信息 + */ + List selectListDashboardAccessTrend(@Param("startTime") Date startTime, + @Param("endTime") Date endTime); + + /** + * 查询仪表盘访问时段分析信息 + * + * @return 仪表盘访问时段分析信息 + */ + List selectListDashboardAnalysisTimeslot(); + + /** + * 查询仪表盘模块分析信息 + * + * @param top 显示数量 + * @return 仪表盘模块分析信息 + */ + List selectListDashboardAnalysisModule(@Param("top") Integer top); + + /** + * 查询仪表盘终端分析信息 + * + * @param top 显示数量 + * @return 仪表盘终端分析信息 + */ + List selectListDashboardAnalysisOs(@Param("top") Integer top); + + /** + * 查询仪表盘浏览器分析信息 + * + * @param top 显示数量 + * @return 仪表盘浏览器分析信息 + */ + List selectListDashboardAnalysisBrowser(@Param("top") Integer top); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/MenuMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/MenuMapper.java new file mode 100644 index 0000000..d5244c6 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/MenuMapper.java @@ -0,0 +1,33 @@ +package top.ysoft.admin.system.mapper; + +import org.apache.ibatis.annotations.Param; +import top.ysoft.admin.system.model.entity.MenuDO; +import top.continew.starter.data.mp.base.BaseMapper; + +import java.util.List; +import java.util.Set; + +/** + * 菜单 Mapper + * + * @author Charles7c + * @since 2023/2/15 20:30 + */ +public interface MenuMapper extends BaseMapper { + + /** + * 根据用户 ID 查询权限码 + * + * @param userId 用户 ID + * @return 权限码集合 + */ + Set selectPermissionByUserId(@Param("userId") Long userId); + + /** + * 根据角色 ID 查询 + * + * @param roleId 角色 ID + * @return 菜单列表 + */ + List selectListByRoleId(@Param("roleId") Long roleId); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/MessageMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/MessageMapper.java new file mode 100644 index 0000000..4f7a41b --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/MessageMapper.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.system.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import top.ysoft.admin.system.model.entity.MessageDO; +import top.ysoft.admin.system.model.resp.message.MessageResp; +import top.continew.starter.data.mp.base.BaseMapper; + +/** + * 消息 Mapper + * + * @author Bull-BCLS + * @since 2023/10/15 19:05 + */ +public interface MessageMapper extends BaseMapper { + + /** + * 分页查询列表 + * + * @param page 分页查询条件 + * @param queryWrapper 查询条件 + * @return 分页信息 + */ + IPage selectPageByUserId(@Param("page") IPage page, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/MessageUserMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/MessageUserMapper.java new file mode 100644 index 0000000..c7e2217 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/MessageUserMapper.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.system.mapper; + +import org.apache.ibatis.annotations.Param; +import top.ysoft.admin.system.model.entity.MessageUserDO; +import top.continew.starter.data.mp.base.BaseMapper; + +/** + * 消息和用户 Mapper + * + * @author Bull-BCLS + * @since 2023/10/15 20:25 + */ +public interface MessageUserMapper extends BaseMapper { + + /** + * 根据用户 ID 和消息类型查询未读消息数量 + * + * @param userId 用户 ID + * @param type 消息类型 + * @return 未读消息信息 + */ + Long selectUnreadCountByUserIdAndType(@Param("userId") Long userId, @Param("type") Integer type); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/NoticeMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/NoticeMapper.java new file mode 100644 index 0000000..7f1034e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/NoticeMapper.java @@ -0,0 +1,25 @@ +package top.ysoft.admin.system.mapper; + +import org.apache.ibatis.annotations.Param; +import top.ysoft.admin.system.model.entity.NoticeDO; +import top.ysoft.admin.system.model.resp.dashboard.DashboardNoticeResp; +import top.continew.starter.data.mp.base.BaseMapper; + +import java.util.List; + +/** + * 公告 Mapper + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +public interface NoticeMapper extends BaseMapper { + + /** + * 查询仪表盘公告列表 + * + * @param userId 用户 ID + * @return 仪表盘公告列表 + */ + List selectDashboardList(@Param("userId") Long userId); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/OptionMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/OptionMapper.java new file mode 100644 index 0000000..18682fa --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/OptionMapper.java @@ -0,0 +1,26 @@ +package top.ysoft.admin.system.mapper; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import top.ysoft.admin.system.model.entity.OptionDO; +import top.continew.starter.data.mp.base.BaseMapper; + +import java.util.List; + +/** + * 参数 Mapper + * + * @author Bull-BCLS + * @since 2023/8/26 19:38 + */ +public interface OptionMapper extends BaseMapper { + + /** + * 根据类别查询 + * + * @param category 类别 + * @return 列表 + */ + @Select("SELECT code, value, default_value FROM sys_option WHERE category = #{category}") + List selectByCategory(@Param("category") String category); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/PeopleEquipmentMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/PeopleEquipmentMapper.java new file mode 100644 index 0000000..fb45f69 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/PeopleEquipmentMapper.java @@ -0,0 +1,16 @@ +package top.ysoft.admin.system.mapper; + +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.system.model.entity.PeopleEquipmentDO; + +/** + * 人员设备下发信息 Mapper + * + * @author zc + * @since 2025/03/27 14:59 + */ +@Repository +public interface PeopleEquipmentMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/RoleDeptMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/RoleDeptMapper.java new file mode 100644 index 0000000..6e9dbb7 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/RoleDeptMapper.java @@ -0,0 +1,26 @@ +package top.ysoft.admin.system.mapper; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import top.ysoft.admin.system.model.entity.RoleDeptDO; +import top.continew.starter.data.mp.base.BaseMapper; + +import java.util.List; + +/** + * 角色和部门 Mapper + * + * @author Charles7c + * @since 2023/2/18 21:57 + */ +public interface RoleDeptMapper extends BaseMapper { + + /** + * 根据角色 ID 查询 + * + * @param roleId 角色 ID + * @return 部门 ID 列表 + */ + @Select("SELECT dept_id FROM sys_role_dept WHERE role_id = #{roleId}") + List selectDeptIdByRoleId(@Param("roleId") Long roleId); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/RoleMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/RoleMapper.java new file mode 100644 index 0000000..c3c5d18 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/RoleMapper.java @@ -0,0 +1,13 @@ +package top.ysoft.admin.system.mapper; + +import top.ysoft.admin.system.model.entity.RoleDO; +import top.continew.starter.data.mp.base.BaseMapper; + +/** + * 角色 Mapper + * + * @author Charles7c + * @since 2023/2/8 23:17 + */ +public interface RoleMapper extends BaseMapper { +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/RoleMenuMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/RoleMenuMapper.java new file mode 100644 index 0000000..d58ca74 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/RoleMenuMapper.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.system.mapper; + +import top.ysoft.admin.system.model.entity.RoleMenuDO; +import top.continew.starter.data.mp.base.BaseMapper; + +import java.util.List; + +/** + * 角色和菜单 Mapper + * + * @author Charles7c + * @since 2023/2/15 20:30 + */ +public interface RoleMenuMapper extends BaseMapper { + + /** + * 根据角色 ID 列表查询 + * + * @param roleIds 角色 ID 列表 + * @return 菜单 ID 列表 + */ + List selectMenuIdByRoleIds(List roleIds); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/RuleRelationMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/RuleRelationMapper.java new file mode 100644 index 0000000..3a7e80e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/RuleRelationMapper.java @@ -0,0 +1,18 @@ +package top.ysoft.admin.system.mapper; + +import org.springframework.stereotype.Repository; +import top.continew.starter.data.mp.base.BaseMapper; +import top.ysoft.admin.system.model.entity.RuleRelationDO; + +/** + * 通行规则-设备关联 Mapper + * + * @author zc + * @since 2025/12/22 17:16 + */ +@Repository +public interface RuleRelationMapper extends BaseMapper { + + int deleteByRuleId(RuleRelationDO ruleRelation); + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/StorageMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/StorageMapper.java new file mode 100644 index 0000000..c3ffdae --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/StorageMapper.java @@ -0,0 +1,13 @@ +package top.ysoft.admin.system.mapper; + +import top.ysoft.admin.system.model.entity.StorageDO; +import top.continew.starter.data.mp.base.BaseMapper; + +/** + * 存储 Mapper + * + * @author Charles7c + * @since 2023/12/26 22:09 + */ +public interface StorageMapper extends BaseMapper { +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserMapper.java new file mode 100644 index 0000000..1b46979 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserMapper.java @@ -0,0 +1,97 @@ +package top.ysoft.admin.system.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import top.ysoft.admin.common.config.mybatis.DataPermissionMapper; +import top.ysoft.admin.system.model.entity.UserDO; +import top.ysoft.admin.system.model.resp.user.UserDetailResp; +import top.continew.starter.extension.datapermission.annotation.DataPermission; +import top.continew.starter.security.crypto.annotation.FieldEncrypt; + +import java.util.List; + +/** + * 用户 Mapper + * + * @author Charles7c + * @since 2022/12/22 21:47 + */ +public interface UserMapper extends DataPermissionMapper { + + /** + * 分页查询列表 + * + * @param page 分页条件 + * @param queryWrapper 查询条件 + * @return 分页列表信息 + */ + @DataPermission(tableAlias = "t1") + IPage selectUserPage(@Param("page") IPage page, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); + + /** + * 查询列表 + * + * @param queryWrapper 查询条件 + * @return 列表信息 + */ + @DataPermission(tableAlias = "t1") + List selectUserList(@Param(Constants.WRAPPER) QueryWrapper queryWrapper); + + /** + * 根据用户名查询 + * + * @param username 用户名 + * @return 用户信息 + */ + @Select("SELECT * FROM sys_user WHERE username = #{username}") + UserDO selectByUsername(@Param("username") String username); + + /** + * 根据手机号查询 + * + * @param phone 手机号 + * @return 用户信息 + */ + @Select("SELECT * FROM sys_user WHERE phone = #{phone}") + UserDO selectByPhone(@FieldEncrypt @Param("phone") String phone); + + /** + * 根据邮箱查询 + * + * @param email 邮箱 + * @return 用户信息 + */ + @Select("SELECT * FROM sys_user WHERE email = #{email}") + UserDO selectByEmail(@FieldEncrypt @Param("email") String email); + + /** + * 根据 ID 查询昵称 + * + * @param id ID + * @return 昵称 + */ + @Select("SELECT nickname FROM sys_user WHERE id = #{id}") + String selectNicknameById(@Param("id") Long id); + + /** + * 根据邮箱查询数量 + * + * @param email 邮箱 + * @param id ID + * @return 用户数量 + */ + Long selectCountByEmail(@FieldEncrypt @Param("email") String email, @Param("id") Long id); + + /** + * 根据手机号查询数量 + * + * @param phone 手机号 + * @param id ID + * @return 用户数量 + */ + Long selectCountByPhone(@FieldEncrypt @Param("phone") String phone, @Param("id") Long id); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserPasswordHistoryMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserPasswordHistoryMapper.java new file mode 100644 index 0000000..89abf13 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserPasswordHistoryMapper.java @@ -0,0 +1,22 @@ +package top.ysoft.admin.system.mapper; + +import org.apache.ibatis.annotations.Param; +import top.ysoft.admin.system.model.entity.UserPasswordHistoryDO; +import top.continew.starter.data.mp.base.BaseMapper; + +/** + * 用户历史密码 Mapper + * + * @author Charles7c + * @since 2024/5/16 21:58 + */ +public interface UserPasswordHistoryMapper extends BaseMapper { + + /** + * 删除过期历史密码 + * + * @param userId 用户 ID + * @param count 保留 N 个历史 + */ + void deleteExpired(@Param("userId") Long userId, @Param("count") int count); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserRoleMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserRoleMapper.java new file mode 100644 index 0000000..23da663 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserRoleMapper.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.system.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import top.ysoft.admin.system.model.entity.UserRoleDO; +import top.ysoft.admin.system.model.resp.role.RoleUserResp; +import top.continew.starter.data.mp.base.BaseMapper; + +/** + * 用户和角色 Mapper + * + * @author Charles7c + * @since 2023/2/13 23:13 + */ +public interface UserRoleMapper extends BaseMapper { + + /** + * 分页查询列表 + * + * @param page 分页条件 + * @param queryWrapper 查询条件 + * @return 分页列表信息 + */ + IPage selectUserPage(@Param("page") IPage page, + @Param(Constants.WRAPPER) QueryWrapper queryWrapper); + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserSocialMapper.java b/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserSocialMapper.java new file mode 100644 index 0000000..c84e991 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/mapper/UserSocialMapper.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.system.mapper; + +import org.apache.ibatis.annotations.Param; +import top.ysoft.admin.system.model.entity.UserSocialDO; +import top.continew.starter.data.mp.base.BaseMapper; + +/** + * 用户社会化关联 Mapper + * + * @author Charles7c + * @since 2023/10/11 22:10 + */ +public interface UserSocialMapper extends BaseMapper { + + /** + * 根据来源和开放 ID 查询 + * + * @param source 来源 + * @param openId 开放 ID + * @return 用户社会化关联信息 + */ + UserSocialDO selectBySourceAndOpenId(@Param("source") String source, @Param("openId") String openId); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/ClientDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/ClientDO.java new file mode 100644 index 0000000..46e7139 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/ClientDO.java @@ -0,0 +1,67 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; +import java.util.List; + +/** + * 终端实体 + * + * @author KAI + * @author Charles7c + * @since 2024/12/03 16:04 + */ +@Data +@TableName(value = "sys_client", autoResultMap = true) +public class ClientDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 终端 ID + */ + private String clientId; + + /** + * 终端 Key + */ + private String clientKey; + + /** + * 终端秘钥 + */ + private String clientSecret; + + /** + * 登录类型 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List authType; + + /** + * 终端类型 + */ + private String clientType; + + /** + * Token 最低活跃频率(单位:秒,-1:不限制,永不冻结) + */ + private Long activeTimeout; + + /** + * Token 有效期(单位:秒,-1:永不过期) + */ + private Long timeout; + + /** + * 状态 + */ + private DisEnableStatusEnum status; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/ConfigDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/ConfigDO.java new file mode 100644 index 0000000..766a378 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/ConfigDO.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.system.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 参数配置实体 + * + * @author zc + * @since 2025/12/30 12:44 + */ +@Data +@TableName("sys_config") +public class ConfigDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 参数名称 + */ + private String configName; + + /** + * 参数键名 + */ + private String configKey; + + /** + * 参数键值 + */ + private String configValue; + + /** + * 备注 + */ + private String remark; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/DeptDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/DeptDO.java new file mode 100644 index 0000000..0535706 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/DeptDO.java @@ -0,0 +1,57 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 部门实体 + * + * @author Charles7c + * @since 2023/1/22 13:50 + */ +@Data +@TableName("sys_dept") +public class DeptDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + private String name; + + /** + * 上级部门 ID + */ + private Long parentId; + + /** + * 祖级列表 + */ + private String ancestors; + + /** + * 描述 + */ + private String description; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态 + */ + private DisEnableStatusEnum status; + + /** + * 是否为系统内置数据 + */ + private Boolean isSystem; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/DictDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/DictDO.java new file mode 100644 index 0000000..0246c48 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/DictDO.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.continew.starter.extension.crud.annotation.DictField; +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 字典实体 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Data +@DictField(valueKey = "code") +@TableName("sys_dict") +public class DictDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 描述 + */ + private String description; + + /** + * 是否为系统内置数据 + */ + private Boolean isSystem; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/DictItemDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/DictItemDO.java new file mode 100644 index 0000000..f0a045b --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/DictItemDO.java @@ -0,0 +1,57 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 字典项实体 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Data +@TableName("sys_dict_item") +public class DictItemDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标签 + */ + private String label; + + /** + * 值 + */ + private String value; + + /** + * 标签颜色 + */ + private String color; + + /** + * 排序 + */ + private Integer sort; + + /** + * 描述 + */ + private String description; + + /** + * 状态 + */ + private DisEnableStatusEnum status; + + /** + * 字典ID + */ + private Long dictId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/FileDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/FileDO.java new file mode 100644 index 0000000..fe5ed64 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/FileDO.java @@ -0,0 +1,164 @@ +package top.ysoft.admin.system.model.entity; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.SneakyThrows; +import org.dromara.x.file.storage.core.FileInfo; +import top.ysoft.admin.common.model.entity.BaseDO; +import top.ysoft.admin.system.enums.FileTypeEnum; +import top.ysoft.admin.system.enums.StorageTypeEnum; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.util.StrUtils; + +import java.io.Serial; +import java.net.URL; +import java.util.Map; + +/** + * 文件实体 + * + * @author Charles7c + * @since 2023/12/23 10:38 + */ +@Data +@TableName("sys_file") +public class FileDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + private String name; + + /** + * 大小(字节) + */ + private Long size; + + /** + * URL + */ + private String url; + + /** + * 上级目录 + */ + private String parentPath; + + /** + * 绝对路径 + */ + private String absPath; + + /** + * 扩展名 + */ + private String extension; + + /** + * 内容类型 + */ + private String contentType; + + /** + * 类型 + */ + private FileTypeEnum type; + + /** + * MD5 值 + */ + private String md5; + + /** + * 元数据 + */ + private String metadata; + + /** + * 缩略图大小(字节) + */ + private Long thumbnailSize; + + /** + * 缩略图 URL + */ + private String thumbnailUrl; + + /** + * 缩略图元数据 + */ + private String thumbnailMetadata; + + /** + * 存储 ID + */ + private Long storageId; + + /** + * 转换为 X-File-Storage 文件信息对象 + * + * @param storageDO 存储桶信息 + * @return X-File-Storage 文件信息对象 + */ + public FileInfo toFileInfo(StorageDO storageDO) { + FileInfo fileInfo = new FileInfo(); + fileInfo.setUrl(this.url); + fileInfo.setSize(this.size); + fileInfo.setFilename(StrUtil.contains(this.url, StringConstants.SLASH) + ? StrUtil.subAfter(this.url, StringConstants.SLASH, true) + : this.url); + fileInfo.setOriginalFilename(StrUtils + .blankToDefault(this.extension, this.name, ex -> this.name + StringConstants.DOT + ex)); + fileInfo.setBasePath(StringConstants.EMPTY); + // 优化 path 处理 + fileInfo.setPath(extractRelativePath(this.url, storageDO)); + + fileInfo.setExt(this.extension); + fileInfo.setPlatform(storageDO.getCode()); + fileInfo.setThUrl(this.thumbnailUrl); + fileInfo.setThFilename(StrUtil.contains(this.thumbnailUrl, StringConstants.SLASH) + ? StrUtil.subAfter(this.thumbnailUrl, StringConstants.SLASH, true) + : this.thumbnailUrl); + fileInfo.setThSize(this.thumbnailSize); + if (StrUtil.isNotBlank(this.thumbnailMetadata)) { + fileInfo.setThMetadata(JSONUtil.toBean(this.thumbnailMetadata, Map.class)); + } + if (StrUtil.isNotBlank(this.metadata)) { + fileInfo.setMetadata(JSONUtil.toBean(this.metadata, Map.class)); + } + return fileInfo; + } + + /** + * 将文件路径处理成资源路径 + * 例如: + * http://domain.cn/bucketName/2024/11/27/6746ec3b2907f0de80afdd70.png => 2024/11/27/ + * http://bucketName.domain.cn/2024/11/27/6746ec3b2907f0de80afdd70.png => 2024/11/27/ + * + * @param url 文件路径 + * @param storageDO 存储桶信息 + * @return + */ + @SneakyThrows + private static String extractRelativePath(String url, StorageDO storageDO) { + url = StrUtil.subBefore(url, StringConstants.SLASH, true) + StringConstants.SLASH; + if (storageDO.getType().equals(StorageTypeEnum.LOCAL)) { + return url; + } + // 提取 URL 中的路径部分 + String fullPath = new URL(url).getPath(); + // 移除开头的斜杠 + String relativePath = fullPath.startsWith(StringConstants.SLASH) ? fullPath.substring(1) : fullPath; + // 如果路径以 bucketName 开头,则移除 bucketName 例如: bucketName/2024/11/27/ -> 2024/11/27/ + if (relativePath.startsWith(storageDO.getBucketName())) { + return StrUtil.split(relativePath, storageDO.getBucketName()).get(1); + } + return relativePath; + } + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/LogDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/LogDO.java new file mode 100644 index 0000000..ccf95e1 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/LogDO.java @@ -0,0 +1,125 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.system.enums.LogStatusEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 系统日志实体 + * + * @author Charles7c + * @since 2022/12/25 9:11 + */ +@Data +@TableName("sys_log") +public class LogDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId + private Long id; + + /** + * 链路 ID + */ + private String traceId; + + /** + * 日志描述 + */ + private String description; + + /** + * 所属模块 + */ + private String module; + + /** + * 请求 URL + */ + private String requestUrl; + + /** + * 请求方式 + */ + private String requestMethod; + + /** + * 请求头 + */ + private String requestHeaders; + + /** + * 请求体 + */ + private String requestBody; + + /** + * 状态码 + */ + private Integer statusCode; + + /** + * 响应头 + */ + private String responseHeaders; + + /** + * 响应体 + */ + private String responseBody; + + /** + * 耗时(ms) + */ + private Long timeTaken; + + /** + * IP + */ + private String ip; + + /** + * IP 归属地 + */ + private String address; + + /** + * 浏览器 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 状态 + */ + private LogStatusEnum status; + + /** + * 错误信息 + */ + private String errorMsg; + + /** + * 创建人 + */ + private Long createUser; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/MenuDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/MenuDO.java new file mode 100644 index 0000000..86ef454 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/MenuDO.java @@ -0,0 +1,93 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.MenuTypeEnum; +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 菜单实体 + * + * @author Charles7c + * @since 2023/2/15 20:14 + */ +@Data +@TableName("sys_menu") +public class MenuDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标题 + */ + private String title; + + /** + * 上级菜单 ID + */ + private Long parentId; + + /** + * 类型 + */ + private MenuTypeEnum type; + + /** + * 路由地址 + */ + private String path; + + /** + * 组件名称 + */ + private String name; + + /** + * 组件路径 + */ + private String component; + + /** + * 重定向地址 + */ + private String redirect; + + /** + * 图标 + */ + private String icon; + + /** + * 是否外链 + */ + private Boolean isExternal; + + /** + * 是否缓存 + */ + private Boolean isCache; + + /** + * 是否隐藏 + */ + private Boolean isHidden; + + /** + * 权限标识 + */ + private String permission; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态 + */ + private DisEnableStatusEnum status; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/MessageDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/MessageDO.java new file mode 100644 index 0000000..1786435 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/MessageDO.java @@ -0,0 +1,59 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.system.enums.MessageTypeEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 消息实体 + * + * @author Bull-BCLS + * @since 2023/10/15 19:05 + */ +@Data +@TableName("sys_message") +public class MessageDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 类型(1:系统消息) + */ + private MessageTypeEnum type; + + /** + * 创建人 + */ + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/MessageUserDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/MessageUserDO.java new file mode 100644 index 0000000..fe816a3 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/MessageUserDO.java @@ -0,0 +1,42 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 消息和用户关联实体 + * + * @author Bull-BCLS + * @since 2023/10/15 20:25 + */ +@Data +@TableName("sys_message_user") +public class MessageUserDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 消息 ID + */ + private Long messageId; + + /** + * 用户 ID + */ + private Long userId; + + /** + * 是否已读 + */ + private Boolean isRead; + + /** + * 读取时间 + */ + private LocalDateTime readTime; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/NoticeDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/NoticeDO.java new file mode 100644 index 0000000..dea42d5 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/NoticeDO.java @@ -0,0 +1,62 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; +import top.ysoft.admin.common.model.entity.BaseDO; +import top.ysoft.admin.system.enums.NoticeScopeEnum; + +import java.io.Serial; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 公告实体 + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +@Data +@TableName(value = "sys_notice", autoResultMap = true) +public class NoticeDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 类型 + */ + private String type; + + /** + * 生效时间 + */ + private LocalDateTime effectiveTime; + + /** + * 终止时间 + */ + private LocalDateTime terminateTime; + + /** + * 通知范围 + */ + private NoticeScopeEnum noticeScope; + + /** + * 通知用户 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List noticeUsers; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/OptionDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/OptionDO.java new file mode 100644 index 0000000..e75f8b7 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/OptionDO.java @@ -0,0 +1,51 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.model.entity.BaseUpdateDO; + +import java.io.Serial; + +/** + * 参数实体 + * + * @author Bull-BCLS + * @since 2023/8/26 19:20 + */ +@Data +@TableName("sys_option") +public class OptionDO extends BaseUpdateDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 类别 + */ + private String category; + + /** + * 名称 + */ + private String name; + + /** + * 键 + */ + private String code; + + /** + * 值 + */ + private String value; + + /** + * 默认值 + */ + private String defaultValue; + + /** + * 描述 + */ + private String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/PeopleEquipmentDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/PeopleEquipmentDO.java new file mode 100644 index 0000000..5ff7470 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/PeopleEquipmentDO.java @@ -0,0 +1,53 @@ +package top.ysoft.admin.system.model.entity; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.continew.starter.extension.crud.model.entity.BaseIdDO; + +import java.io.Serial; + +/** + * 人员设备下发信息实体 + * + * @author zc + * @since 2025/03/27 14:59 + */ +@Data +@TableName("sys_people_equipment") +public class PeopleEquipmentDO extends BaseIdDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 人员Id + */ + private Long peopleId; + + /** + * 设备Id + */ + private Long equipmentId; + + /** + * 人员编号对接 + */ + private String guid; + + /** + * 人像对接 + */ + private String faceGuid; + + /** + * 访客Id + */ + private Long visitorId; + + /** + * 三方人员id + */ + private String otherId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RoleDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RoleDO.java new file mode 100644 index 0000000..bb877ad --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RoleDO.java @@ -0,0 +1,64 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.enums.DataScopeEnum; +import top.continew.starter.extension.crud.annotation.DictField; +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; + +/** + * 角色实体 + * + * @author Charles7c + * @since 2023/2/8 22:54 + */ +@Data +@DictField +@TableName("sys_role") +public class RoleDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 数据权限 + */ + private DataScopeEnum dataScope; + + /** + * 描述 + */ + private String description; + + /** + * 排序 + */ + private Integer sort; + + /** + * 是否为系统内置数据 + */ + private Boolean isSystem; + + /** + * 菜单选择是否父子节点关联 + */ + private Boolean menuCheckStrictly; + + /** + * 部门选择是否父子节点关联 + */ + private Boolean deptCheckStrictly; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RoleDeptDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RoleDeptDO.java new file mode 100644 index 0000000..47a2c9b --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RoleDeptDO.java @@ -0,0 +1,38 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 角色和部门实体 + * + * @author Charles7c + * @since 2023/2/18 21:57 + */ +@Data +@NoArgsConstructor +@TableName("sys_role_dept") +public class RoleDeptDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 角色 ID + */ + private Long roleId; + + /** + * 部门 ID + */ + private Long deptId; + + public RoleDeptDO(Long roleId, Long deptId) { + this.roleId = roleId; + this.deptId = deptId; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RoleMenuDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RoleMenuDO.java new file mode 100644 index 0000000..79289b0 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RoleMenuDO.java @@ -0,0 +1,38 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 角色和菜单实体 + * + * @author Charles7c + * @since 2023/2/15 20:20 + */ +@Data +@NoArgsConstructor +@TableName("sys_role_menu") +public class RoleMenuDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 角色 ID + */ + private Long roleId; + + /** + * 菜单 ID + */ + private Long menuId; + + public RoleMenuDO(Long roleId, Long menuId) { + this.roleId = roleId; + this.menuId = menuId; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RuleRelationDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RuleRelationDO.java new file mode 100644 index 0000000..e410b8c --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/RuleRelationDO.java @@ -0,0 +1,37 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 通行规则-设备关联实体 + * + * @author zc + * @since 2025/12/22 17:16 + */ +@Data +@TableName("equipment_rule_relation") +public class RuleRelationDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 平台侧规则ID(关联规则表主键) + */ + private Long ruleId; + + /** + * 设备ID(关联设备表主键) + */ + private Long equipmentId; + + /** + * 设备侧的规则ID(设备本地存储的规则标识) + */ + private String equipmentRuleId; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/StorageDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/StorageDO.java new file mode 100644 index 0000000..160b09c --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/StorageDO.java @@ -0,0 +1,86 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.StorageTypeEnum; +import top.ysoft.admin.common.model.entity.BaseDO; +import top.continew.starter.security.crypto.annotation.FieldEncrypt; + +import java.io.Serial; + +/** + * 存储实体 + * + * @author Charles7c + * @since 2023/12/26 22:09 + */ +@Data +@TableName("sys_storage") +public class StorageDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 类型 + */ + private StorageTypeEnum type; + + /** + * Access Key + */ + @FieldEncrypt + private String accessKey; + + /** + * Secret Key + */ + @FieldEncrypt + private String secretKey; + + /** + * Endpoint + */ + private String endpoint; + + /** + * Bucket + */ + private String bucketName; + + /** + * 域名 + */ + private String domain; + + /** + * 描述 + */ + private String description; + + /** + * 是否为默认存储 + */ + private Boolean isDefault; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态 + */ + private DisEnableStatusEnum status; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserDO.java new file mode 100644 index 0000000..c58c069 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserDO.java @@ -0,0 +1,100 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.enums.GenderEnum; +import top.continew.starter.extension.crud.annotation.DictField; +import top.ysoft.admin.common.model.entity.BaseDO; +import top.continew.starter.security.crypto.annotation.FieldEncrypt; + +import java.io.Serial; +import java.time.LocalDateTime; + +/** + * 用户实体 + * + * @author Charles7c + * @since 2022/12/21 20:42 + */ +@Data +@DictField(labelKey = "nickname", extraKeys = {"username"}) +@TableName("sys_user") +public class UserDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户名 + */ + private String username; + + /** + * 昵称 + */ + private String nickname; + + /** + * 密码 + */ + // @FieldEncrypt(encryptor = BCryptEncryptor.class) + private String password; + + /** + * 性别 + */ + private GenderEnum gender; + + /** + * 邮箱 + */ + @FieldEncrypt + @TableField(insertStrategy = FieldStrategy.NOT_EMPTY) + private String email; + + /** + * 手机号码 + */ + @FieldEncrypt + @TableField(insertStrategy = FieldStrategy.NOT_EMPTY) + private String phone; + + /** + * 头像地址 + */ + private String avatar; + + /** + * 描述 + */ + private String description; + + /** + * 状态 + */ + private DisEnableStatusEnum status; + + /** + * 是否为系统内置数据 + */ + private Boolean isSystem; + + /** + * 最后一次修改密码时间 + */ + private LocalDateTime pwdResetTime; + + /** + * 部门 ID + */ + private Long deptId; + + /** + * 设备ID + */ + @TableField(exist = false) + private Long equipmentId; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserPasswordHistoryDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserPasswordHistoryDO.java new file mode 100644 index 0000000..bf10d6a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserPasswordHistoryDO.java @@ -0,0 +1,51 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户历史密码实体 + * + * @author Charles7c + * @since 2024/5/16 21:58 + */ +@Data +@NoArgsConstructor +@TableName("sys_user_password_history") +public class UserPasswordHistoryDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 用户 ID + */ + private Long userId; + + /** + * 密码 + */ + private String password; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + public UserPasswordHistoryDO(Long userId, String password) { + this.userId = userId; + this.password = password; + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserRoleDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserRoleDO.java new file mode 100644 index 0000000..81fe159 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserRoleDO.java @@ -0,0 +1,45 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户和角色实体 + * + * @author Charles7c + * @since 2023/2/13 23:13 + */ +@Data +@NoArgsConstructor +@TableName("sys_user_role") +public class UserRoleDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId + private Long id; + + /** + * 用户 ID + */ + private Long userId; + + /** + * 角色 ID + */ + private Long roleId; + + public UserRoleDO(Long userId, Long roleId) { + this.userId = userId; + this.roleId = roleId; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserSocialDO.java b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserSocialDO.java new file mode 100644 index 0000000..81637d8 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/entity/UserSocialDO.java @@ -0,0 +1,62 @@ +package top.ysoft.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户社会化关联实体 + * + * @author Charles7c + * @since 2023/10/11 22:10 + */ +@Data +@TableName("sys_user_social") +public class UserSocialDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId + private Long id; + + /** + * 用户 ID + */ + private Long userId; + + /** + * 来源 + */ + private String source; + + /** + * 开放 ID + */ + private String openId; + + /** + * 附加信息 + */ + private String metaJson; + + /** + * 最后登录时间 + */ + private LocalDateTime lastLoginTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/ClientQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/ClientQuery.java new file mode 100644 index 0000000..4294129 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/ClientQuery.java @@ -0,0 +1,57 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 终端查询条件 + * + * @author KAI + * @author Charles7c + * @since 2024/12/03 16:04 + */ +@Data +@Schema(description = "终端查询条件") +public class ClientQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 终端 Key + */ + @Schema(description = "终端 Key", example = "PC") + private String clientKey; + + /** + * 终端秘钥 + */ + @Schema(description = "终端秘钥", example = "dd77ab1e353a027e0d60ce3b151e8642") + private String clientSecret; + + /** + * 认证类型 + */ + @Schema(description = "认证类型", example = "ACCOUNT") + @Query(type = QueryType.IN) + private List authType; + + /** + * 终端类型 + */ + @Schema(description = "终端类型", example = "PC") + private String clientType; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/ConfigQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/ConfigQuery.java new file mode 100644 index 0000000..8e00289 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/ConfigQuery.java @@ -0,0 +1,53 @@ +package top.ysoft.admin.system.model.query; + +import cn.hutool.core.date.DatePattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.springframework.format.annotation.DateTimeFormat; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; +import java.util.Date; +import java.util.List; + +/** + * 参数配置查询条件 + * + * @author zc + * @since 2025/12/30 12:44 + */ +@Data +@Schema(description = "参数配置查询条件") +public class ConfigQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 参数名称 + */ + @Schema(description = "参数名称") + @Query(type = QueryType.LIKE) + private String configName; + + /** + * 参数键名 + */ + @Schema(description = "参数键名") + @Query(type = QueryType.EQ) + private String configKey; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08,2023-08-08") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + @Size(max = 2, message = "创建时间必须是一个范围") + private List createTime; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/DeptQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/DeptQuery.java new file mode 100644 index 0000000..b24e3c5 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/DeptQuery.java @@ -0,0 +1,37 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 部门查询条件 + * + * @author Charles7c + * @since 2023/1/22 17:52 + */ +@Data +@Schema(description = "部门查询条件") +public class DeptQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 关键词 + */ + @Schema(description = "关键词", example = "测试部") + @Query(columns = {"name", "description"}, type = QueryType.LIKE) + private String description; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/DictItemQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/DictItemQuery.java new file mode 100644 index 0000000..722c19d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/DictItemQuery.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 字典项查询条件 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Data +@Schema(description = "字典项查询条件") +public class DictItemQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 关键词 + */ + @Schema(description = "关键词") + @Query(columns = {"label", "description"}, type = QueryType.LIKE) + private String description; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 字典 ID + */ + @Schema(description = "字典 ID") + private Long dictId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/DictQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/DictQuery.java new file mode 100644 index 0000000..9c1975e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/DictQuery.java @@ -0,0 +1,30 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 字典查询条件 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Data +@Schema(description = "字典查询条件") +public class DictQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 关键词 + */ + @Schema(description = "关键词") + @Query(columns = {"name", "code", "description"}, type = QueryType.LIKE) + private String description; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/FileQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/FileQuery.java new file mode 100644 index 0000000..55f3639 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/FileQuery.java @@ -0,0 +1,44 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.system.enums.FileTypeEnum; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 文件查询条件 + * + * @author Charles7c + * @since 2023/12/23 10:38 + */ +@Data +@Schema(description = "文件查询条件") +public class FileQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "图片") + @Query(type = QueryType.LIKE) + private String name; + + /** + * 绝对路径 + */ + @Schema(description = "绝对路径", example = "/2025") + @Query(type = QueryType.EQ) + private String absPath; + + /** + * 类型 + */ + @Schema(description = "类型", example = "2") + private FileTypeEnum type; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/LogQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/LogQuery.java new file mode 100644 index 0000000..e6fc518 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/LogQuery.java @@ -0,0 +1,65 @@ +package top.ysoft.admin.system.model.query; + +import cn.hutool.core.date.DatePattern; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 日志查询条件 + * + * @author Charles7c + * @since 2023/1/15 11:43 + */ +@Data +@Schema(description = "日志查询条件") +public class LogQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 日志描述 + */ + @Schema(description = "日志描述", example = "新增数据") + private String description; + + /** + * 所属模块 + */ + @Schema(description = "所属模块", example = "所属模块") + private String module; + + /** + * IP + */ + @Schema(description = "IP", example = "") + private String ip; + + /** + * 操作人 + */ + @Schema(description = "操作人", example = "admin") + private String createUserString; + + /** + * 操作时间 + */ + @Schema(description = "操作时间", example = "2023-08-08 00:00:00,2023-08-08 23:59:59") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + @Size(max = 2, message = "操作时间必须是一个范围") + private List createTime; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/MenuQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/MenuQuery.java new file mode 100644 index 0000000..528a8f5 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/MenuQuery.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.NoArgsConstructor; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 菜单查询条件 + * + * @author Charles7c + * @since 2023/2/15 20:21 + */ +@Data +@NoArgsConstructor +@Schema(description = "菜单查询条件") +public class MenuQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标题 + */ + @Schema(description = "标题", example = "用户管理") + @Query(type = QueryType.LIKE) + private String title; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + public MenuQuery(DisEnableStatusEnum status) { + this.status = status; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/MessageQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/MessageQuery.java new file mode 100644 index 0000000..ae14885 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/MessageQuery.java @@ -0,0 +1,58 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.system.enums.MessageTypeEnum; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.annotation.QueryIgnore; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 消息查询条件 + * + * @author Bull-BCLS + * @since 2023/10/15 19:05 + */ +@Data +@Schema(description = "消息查询条件") +public class MessageQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 标题 + */ + @Schema(description = "标题", example = "欢迎注册 xxx") + @Query(type = QueryType.LIKE) + private String title; + + /** + * 类型 + */ + @Schema(description = "类型", example = "1") + private MessageTypeEnum type; + + /** + * 是否已读 + */ + @Schema(description = "是否已读", example = "true") + @QueryIgnore + private Boolean isRead; + + /** + * 用户 ID + */ + @Schema(hidden = true) + @QueryIgnore + private Long userId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/NoticeQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/NoticeQuery.java new file mode 100644 index 0000000..13bd2fa --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/NoticeQuery.java @@ -0,0 +1,36 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 公告查询条件 + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +@Data +@Schema(description = "公告查询条件") +public class NoticeQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标题 + */ + @Schema(description = "标题", example = "这是公告标题") + @Query(type = QueryType.LIKE) + private String title; + + /** + * 类型 + */ + @Schema(description = "类型", example = "1") + private String type; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/OptionQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/OptionQuery.java new file mode 100644 index 0000000..8cb54e2 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/OptionQuery.java @@ -0,0 +1,40 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.system.enums.OptionCategoryEnum; +import top.continew.starter.core.validation.constraints.EnumValue; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 参数查询条件 + * + * @author Bull-BCLS + * @since 2023/8/26 19:38 + */ +@Data +@Schema(description = "参数查询条件") +public class OptionQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 键列表 + */ + @Schema(description = "键列表", example = "SITE_TITLE,SITE_COPYRIGHT") + @Query(type = QueryType.IN) + private List code; + + /** + * 类别 + */ + @Schema(description = "类别", example = "SITE") + @EnumValue(value = OptionCategoryEnum.class, message = "类别非法") + private String category; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/PeopleEquipmentQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/PeopleEquipmentQuery.java new file mode 100644 index 0000000..de00309 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/PeopleEquipmentQuery.java @@ -0,0 +1,22 @@ +package top.ysoft.admin.system.model.query; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 人员设备下发信息查询条件 + * + * @author zc + * @since 2025/03/27 14:59 + */ +@Data +@Schema(description = "人员设备下发信息查询条件") +public class PeopleEquipmentQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/RoleQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/RoleQuery.java new file mode 100644 index 0000000..969e999 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/RoleQuery.java @@ -0,0 +1,30 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 角色查询条件 + * + * @author Charles7c + * @since 2023/2/8 23:04 + */ +@Data +@Schema(description = "角色查询条件") +public class RoleQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 关键词 + */ + @Schema(description = "关键词", example = "测试人员") + @Query(columns = {"name", "code", "description"}, type = QueryType.LIKE) + private String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/RoleUserQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/RoleUserQuery.java new file mode 100644 index 0000000..328a63e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/RoleUserQuery.java @@ -0,0 +1,33 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 角色关联用户查询条件 + * + * @author Charles7c + * @since 2025/2/5 22:01 + */ +@Data +@Schema(description = "角色关联用户查询条件") +public class RoleUserQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 角色 ID + */ + @Schema(description = "角色 ID", example = "1") + private Long roleId; + + /** + * 关键词 + */ + @Schema(description = "关键词", example = "zhangsan") + private String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/StorageQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/StorageQuery.java new file mode 100644 index 0000000..885646a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/StorageQuery.java @@ -0,0 +1,44 @@ +package top.ysoft.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.StorageTypeEnum; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 存储查询条件 + * + * @author Charles7c + * @since 2023/12/26 22:09 + */ +@Data +@Schema(description = "存储查询条件") +public class StorageQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 关键词 + */ + @Schema(description = "关键词", example = "本地存储") + @Query(columns = {"name", "code", "description"}, type = QueryType.LIKE) + private String description; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 类型 + */ + @Schema(description = "类型", example = "2") + private StorageTypeEnum type; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/query/UserQuery.java b/wms-module-system/src/main/java/top/wms/admin/system/model/query/UserQuery.java new file mode 100644 index 0000000..5111a68 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/query/UserQuery.java @@ -0,0 +1,72 @@ +package top.ysoft.admin.system.model.query; + +import cn.hutool.core.date.DatePattern; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 用户查询条件 + * + * @author Charles7c + * @since 2023/2/20 21:01 + */ +@Data +@Schema(description = "用户查询条件") +public class UserQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 关键词 + */ + @Schema(description = "关键词", example = "zhangsan") + private String description; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 00:00:00,2023-08-08 23:59:59") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + @Size(max = 2, message = "创建时间必须是一个范围") + private List createTime; + + /** + * 部门 ID + */ + @Schema(description = "部门 ID", example = "1") + private Long deptId; + + /** + * 用户 ID 列表 + */ + @Schema(description = "用户 ID 列表", example = "[1,2,3]") + private List userIds; + + /** + * 角色 ID + *

用于在角色授权用户时,过滤掉已经分配给该角色的用户

+ */ + @Schema(description = "角色 ID", example = "1") + private Long roleId; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + private Long equipmentId; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/ClientReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/ClientReq.java new file mode 100644 index 0000000..0412d8f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/ClientReq.java @@ -0,0 +1,82 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 创建或修改终端参数 + * + * @author KAI + * @author Charles7c + * @since 2024/12/03 16:04 + */ +@Data +@Schema(description = "创建或修改终端参数") +public class ClientReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 终端 Key + */ + @Schema(description = "终端 Key", example = "PC") + @NotBlank(message = "终端Key不能为空") + @Length(max = 32, message = "终端Key长度不能超过 {max} 个字符") + private String clientKey; + + /** + * 终端秘钥 + */ + @Schema(description = "终端秘钥", example = "dd77ab1e353a027e0d60ce3b151e8642") + @NotBlank(message = "终端秘钥不能为空") + @Length(max = 255, message = "终端秘钥长度不能超过 {max} 个字符") + private String clientSecret; + + /** + * 认证类型 + */ + @Schema(description = "认证类型", example = "ACCOUNT") + @NotEmpty(message = "认证类型不能为空") + private List authType; + + /** + * 终端类型 + */ + @Schema(description = "终端类型", example = "PC") + @NotBlank(message = "终端类型不能为空") + @Length(max = 32, message = "终端类型长度不能超过 {max} 个字符") + private String clientType; + + /** + * Token 最低活跃频率(单位:秒,-1:不限制,永不冻结) + */ + @Schema(description = "Token 最低活跃频率(单位:秒,-1:不限制,永不冻结)", example = "1800") + private Long activeTimeout; + + /** + * Token 有效期(单位:秒,-1:永不过期) + */ + @Schema(description = "Token 有效期(单位:秒,-1:永不过期)", example = "86400") + private Long timeout; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 终端 ID + */ + @Schema(hidden = true) + private String clientId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/ConfigReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/ConfigReq.java new file mode 100644 index 0000000..2fd1515 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/ConfigReq.java @@ -0,0 +1,57 @@ +package top.ysoft.admin.system.model.req; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.time.*; + +/** + * 创建或修改参数配置参数 + * + * @author zc + * @since 2025/12/30 12:44 + */ +@Data +@Schema(description = "创建或修改参数配置参数") +public class ConfigReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 参数名称 + */ + @Schema(description = "参数名称") + @NotNull + @Length(max = 100, message = "参数名称长度不能超过 {max} 个字符") + private String configName; + + /** + * 参数键名 + */ + @Schema(description = "参数键名") + @NotNull + @Length(max = 100, message = "参数键名长度不能超过 {max} 个字符") + private String configKey; + + /** + * 参数值 + */ + @Schema(description = "参数值") + @NotNull + @Length(max = 100, message = "参数值长度不能超过 {max} 个字符") + private String configValue; + + /** + * 备注 + */ + @Schema(description = "备注") + @Length(max = 255, message = "备注长度不能超过 {max} 个字符") + private String remark; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/DeptReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/DeptReq.java new file mode 100644 index 0000000..11d0bc3 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/DeptReq.java @@ -0,0 +1,67 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 创建或修改部门参数 + * + * @author Charles7c + * @since 2023/1/24 00:21 + */ +@Data +@Schema(description = "创建或修改部门参数") +public class DeptReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 上级部门 ID + */ + @Schema(description = "上级部门 ID", example = "2") + @NotNull(message = "上级部门不能为空") + private Long parentId; + + /** + * 名称 + */ + @Schema(description = "名称", example = "测试部") + @NotBlank(message = "名称不能为空") + @Length(max = 30, message = "名称长度不能超过 {max} 个字符") + private String name; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + @Min(value = 1, message = "排序最小值为 {value}") + private Integer sort; + + /** + * 描述 + */ + @Schema(description = "描述", example = "测试部描述信息") + @Length(max = 200, message = "描述长度不能超过 {max} 个字符") + private String description; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 祖级列表 + */ + @Schema(hidden = true) + private String ancestors; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/DictItemReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/DictItemReq.java new file mode 100644 index 0000000..7fc0d40 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/DictItemReq.java @@ -0,0 +1,76 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 创建或修改字典项参数 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Data +@Schema(description = "创建或修改字典项参数") +public class DictItemReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标签 + */ + @Schema(description = "标签", example = "通知") + @NotBlank(message = "标签不能为空") + @Length(max = 30, message = "标签长度不能超过 {max} 个字符") + private String label; + + /** + * 值 + */ + @Schema(description = "值", example = "1") + @NotBlank(message = "值不能为空") + @Length(max = 30, message = "值长度不能超过 {max} 个字符") + private String value; + + /** + * 标签颜色 + */ + @Schema(description = "标签颜色", example = "blue") + @Length(max = 30, message = "标签颜色长度不能超过 {max} 个字符") + private String color; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + @Min(value = 1, message = "排序最小值为 {value}") + private Integer sort; + + /** + * 描述 + */ + @Schema(description = "描述", example = "通知描述信息") + @Length(max = 200, message = "描述长度不能超过 {max} 个字符") + private String description; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 所属字典 + */ + @Schema(description = "所属字典", example = "1") + @NotNull(message = "所属字典不能为空") + private Long dictId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/DictReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/DictReq.java new file mode 100644 index 0000000..99e31fd --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/DictReq.java @@ -0,0 +1,48 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.common.constant.RegexConstants; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 创建或修改字典参数 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Data +@Schema(description = "创建或修改字典参数") +public class DictReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "公告类型") + @NotBlank(message = "名称不能为空") + @Pattern(regexp = RegexConstants.GENERAL_NAME, message = "名称长度为 2-30 个字符,支持中文、字母、数字、下划线,短横线") + private String name; + + /** + * 编码 + */ + @Schema(description = "编码", example = "notice_type") + @NotBlank(message = "编码不能为空") + @Pattern(regexp = RegexConstants.GENERAL_CODE, message = "编码长度为 2-30 个字符,支持大小写字母、数字、下划线,以字母开头") + private String code; + + /** + * 描述 + */ + @Schema(description = "描述", example = "公告类型描述信息") + @Length(max = 200, message = "描述长度不能超过 {max} 个字符") + private String description; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/FileReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/FileReq.java new file mode 100644 index 0000000..a9c9f68 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/FileReq.java @@ -0,0 +1,31 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 修改文件参数 + * + * @author Charles7c + * @since 2023/12/23 10:38 + */ +@Data +@Schema(description = "修改文件参数") +public class FileReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "test123") + @NotBlank(message = "文件名称不能为空") + @Length(max = 255, message = "文件名称长度不能超过 {max} 个字符") + private String name; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/MenuReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/MenuReq.java new file mode 100644 index 0000000..691641e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/MenuReq.java @@ -0,0 +1,122 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.MenuTypeEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 创建或修改菜单参数 + * + * @author Charles7c + * @since 2023/2/15 20:21 + */ +@Data +@Schema(description = "创建或修改菜单参数") +public class MenuReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 类型 + */ + @Schema(description = "类型", example = "2") + @NotNull(message = "类型非法") + private MenuTypeEnum type; + + /** + * 图标 + */ + @Schema(description = "图标", example = "user") + @Length(max = 50, message = "图标长度不能超过 {max} 个字符") + private String icon; + + /** + * 标题 + */ + @Schema(description = "标题", example = "用户管理") + @NotBlank(message = "标题不能为空") + @Length(max = 30, message = "标题长度不能超过 {max} 个字符") + private String title; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + @NotNull(message = "排序不能为空") + @Min(value = 1, message = "排序最小值为 {value}") + private Integer sort; + + /** + * 权限标识 + */ + @Schema(description = "权限标识", example = "system:user:list") + @Length(max = 100, message = "权限标识长度不能超过 {max} 个字符") + private String permission; + + /** + * 路由地址 + */ + @Schema(description = "路由地址", example = "/system/user") + @Length(max = 255, message = "路由地址长度不能超过 {max} 个字符") + private String path; + + /** + * 组件名称 + */ + @Schema(description = "组件名称", example = "User") + @Length(max = 50, message = "组件名称长度不能超过 {max} 个字符") + private String name; + + /** + * 组件路径 + */ + @Schema(description = "组件路径", example = "/system/user/index") + @Length(max = 255, message = "组件路径长度不能超过 {max} 个字符") + private String component; + + /** + * 重定向地址 + */ + @Schema(description = "重定向地址") + private String redirect; + + /** + * 是否外链 + */ + @Schema(description = "是否外链", example = "false") + private Boolean isExternal; + + /** + * 是否缓存 + */ + @Schema(description = "是否缓存", example = "false") + private Boolean isCache; + + /** + * 是否隐藏 + */ + @Schema(description = "是否隐藏", example = "false") + private Boolean isHidden; + + /** + * 上级菜单 ID + */ + @Schema(description = "上级菜单 ID", example = "1000") + private Long parentId; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + @NotNull(message = "状态非法") + private DisEnableStatusEnum status; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/MessageReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/MessageReq.java new file mode 100644 index 0000000..6922f6f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/MessageReq.java @@ -0,0 +1,48 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.system.enums.MessageTypeEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 创建消息参数 + * + * @author Bull-BCLS + * @since 2023/10/15 19:05 + */ +@Data +@Schema(description = "创建消息参数") +public class MessageReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标题 + */ + @Schema(description = "标题", example = "欢迎注册 xxx") + @NotBlank(message = "标题不能为空") + @Length(max = 50, message = "标题长度不能超过 {max} 个字符") + private String title; + + /** + * 内容 + */ + @Schema(description = "内容", example = "尊敬的 xx,欢迎注册使用,请及时配置您的密码。") + @NotBlank(message = "内容不能为空") + @Length(max = 255, message = "内容长度不能超过 {max} 个字符") + private String content; + + /** + * 类型 + */ + @Schema(description = "类型(1:系统消息)", example = "1") + @NotNull(message = "类型非法") + private MessageTypeEnum type; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/NoticeReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/NoticeReq.java new file mode 100644 index 0000000..d5d811e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/NoticeReq.java @@ -0,0 +1,77 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.system.enums.NoticeScopeEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 创建或修改公告参数 + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +@Data +@Schema(description = "创建或修改公告参数") +public class NoticeReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标题 + */ + @Schema(description = "标题", example = "这是公告标题") + @NotBlank(message = "标题不能为空") + @Length(max = 150, message = "标题长度不能超过 {max} 个字符") + private String title; + + /** + * 内容 + */ + @Schema(description = "内容", example = "这是公告内容") + @NotBlank(message = "内容不能为空") + private String content; + + /** + * 类型(取值于字典 notice_type) + */ + @Schema(description = "类型(取值于字典 notice_type)", example = "1") + @NotBlank(message = "类型不能为空") + @Length(max = 30, message = "类型长度不能超过 {max} 个字符") + private String type; + + /** + * 生效时间 + */ + @Schema(description = "生效时间", example = "2023-08-08 00:00:00", type = "string") + private LocalDateTime effectiveTime; + + /** + * 终止时间 + */ + @Schema(description = "终止时间", example = "2023-08-08 23:59:59", type = "string") + @Future(message = "终止时间必须是未来时间") + private LocalDateTime terminateTime; + + /** + * 通知范围 + */ + @Schema(description = "通知范围", example = "2") + @NotNull(message = "通知范围不能为空") + private NoticeScopeEnum noticeScope; + + /** + * 指定用户 + */ + @Schema(description = "指定用户", example = "[1,2,3]") + private List noticeUsers; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/OptionReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/OptionReq.java new file mode 100644 index 0000000..f09203e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/OptionReq.java @@ -0,0 +1,45 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 修改参数参数 + * + * @author Bull-BCLS + * @since 2023/8/26 19:38 + */ +@Data +@Schema(description = "修改参数参数") +public class OptionReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + @NotNull(message = "ID不能为空") + private Long id; + + /** + * 键 + */ + @Schema(description = "键", example = "site_title") + @NotBlank(message = "键不能为空") + @Length(max = 100, message = "键长度不能超过 {max} 个字符") + private String code; + + /** + * 值 + */ + @Schema(description = "值", example = "Ysoft Admin") + private String value; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/OptionResetValueReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/OptionResetValueReq.java new file mode 100644 index 0000000..a38f329 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/OptionResetValueReq.java @@ -0,0 +1,34 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 参数重置请求参数 + * + * @author Bull-BCLS + * @since 2023/9/21 23:10 + */ +@Data +@Schema(description = "参数重置请求参数") +public class OptionResetValueReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 键列表 + */ + @Schema(description = "键列表", example = "SITE_TITLE,SITE_COPYRIGHT") + private List code; + + /** + * 类别 + */ + @Schema(description = "类别", example = "SITE") + private String category; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/PeopleEquipmentReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/PeopleEquipmentReq.java new file mode 100644 index 0000000..f12e707 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/PeopleEquipmentReq.java @@ -0,0 +1,69 @@ +package top.ysoft.admin.system.model.req; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 创建或修改人员设备下发信息参数 + * + * @author zc + * @since 2025/03/27 14:59 + */ +@Data +@Schema(description = "创建或修改人员设备下发信息参数") +public class PeopleEquipmentReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * Id + */ + @Schema(description = "Id") + private Long id; + + /** + * 人员Id + */ + @Schema(description = "人员Id") + private Long peopleId; + + /** + * 设备Id + */ + @Schema(description = "设备Id") + private Long equipmentId; + + /** + * 人员编号对接 + */ + @Schema(description = "人员编号对接") + @Length(max = 200, message = "人员编号对接长度不能超过 {max} 个字符") + private String guid; + + /** + * 人像对接 + */ + @Schema(description = "人像对接") + @Length(max = 200, message = "人像对接长度不能超过 {max} 个字符") + private String faceGuid; + + /** + * 访客Id + */ + @Schema(description = "访客Id") + private Long visitorId; + + /** + * 三方人员id + */ + @Schema(description = "三方人员id") + @Length(max = 36, message = "三方人员id长度不能超过 {max} 个字符") + private String otherId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/RoleReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/RoleReq.java new file mode 100644 index 0000000..96921b5 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/RoleReq.java @@ -0,0 +1,77 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.common.constant.RegexConstants; +import top.ysoft.admin.common.enums.DataScopeEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 创建或修改角色参数 + * + * @author Charles7c + * @since 2023/2/8 23:12 + */ +@Data +@Schema(description = "创建或修改角色参数") +public class RoleReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "测试人员") + @NotBlank(message = "名称不能为空") + @Pattern(regexp = RegexConstants.GENERAL_NAME, message = "名称长度为 2-30 个字符,支持中文、字母、数字、下划线,短横线") + private String name; + + /** + * 编码 + */ + @Schema(description = "编码", example = "test") + @NotBlank(message = "编码不能为空") + @Pattern(regexp = RegexConstants.GENERAL_CODE, message = "编码长度为 2-30 个字符,支持大小写字母、数字、下划线,以字母开头") + private String code; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + @Min(value = 1, message = "排序最小值为 {value}") + private Integer sort; + + /** + * 描述 + */ + @Schema(description = "描述", example = "测试人员描述信息") + @Length(max = 200, message = "描述长度不能超过 {max} 个字符") + private String description; + + /** + * 数据权限 + */ + @Schema(description = "数据权限", example = "5") + private DataScopeEnum dataScope; + + /** + * 权限范围:部门 ID 列表 + */ + @Schema(description = "权限范围:部门 ID 列表", example = "5") + private List deptIds = new ArrayList<>(); + + /** + * 部门选择是否父子节点关联 + */ + @Schema(description = "部门选择是否父子节点关联", example = "false") + private Boolean deptCheckStrictly; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/RoleUpdatePermissionReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/RoleUpdatePermissionReq.java new file mode 100644 index 0000000..aed0b19 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/RoleUpdatePermissionReq.java @@ -0,0 +1,41 @@ +package top.ysoft.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 修改角色功能权限参数 + * + * @author Charles7c + * @since 2025/2/5 21:00 + */ +@Data +@Schema(description = "修改角色功能权限参数") +public class RoleUpdatePermissionReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 角色 ID + */ + @Schema(description = "角色 ID", example = "1") + private Long roleId; + + /** + * 功能权限:菜单 ID 列表 + */ + @Schema(description = "功能权限:菜单 ID 列表", example = "1000,1010,1011,1012,1013,1014") + private List menuIds = new ArrayList<>(); + + /** + * 菜单选择是否父子节点关联 + */ + @Schema(description = "菜单选择是否父子节点关联", example = "false") + private Boolean menuCheckStrictly; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/RuleRelationReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/RuleRelationReq.java new file mode 100644 index 0000000..755cd0d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/RuleRelationReq.java @@ -0,0 +1,52 @@ +package top.ysoft.admin.system.model.req; + +import jakarta.validation.constraints.*; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 创建或修改通行规则-设备关联参数 + * + * @author zc + * @since 2025/12/22 17:16 + */ +@Data +@Schema(description = "创建或修改通行规则-设备关联参数") +public class RuleRelationReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 平台侧规则ID(关联规则表主键) + */ + @Schema(description = "平台侧规则ID(关联规则表主键)") + @NotNull(message = "平台侧规则ID(关联规则表主键)不能为空") + private Long ruleId; + + /** + * 设备ID(关联设备表主键) + */ + @Schema(description = "设备ID(关联设备表主键)") + @NotNull(message = "设备ID(关联设备表主键)不能为空") + private Long equipmentId; + + /** + * 设备侧的规则ID(设备本地存储的规则标识) + */ + @Schema(description = "设备侧的规则ID(设备本地存储的规则标识)") + @NotBlank(message = "设备侧的规则ID(设备本地存储的规则标识)不能为空") + @Length(max = 64, message = "设备侧的规则ID(设备本地存储的规则标识)长度不能超过 {max} 个字符") + private String equipmentRuleId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/StorageReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/StorageReq.java new file mode 100644 index 0000000..1bf9049 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/StorageReq.java @@ -0,0 +1,120 @@ +package top.ysoft.admin.system.model.req; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.common.constant.RegexConstants; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.StorageTypeEnum; +import top.ysoft.admin.system.validation.ValidationGroup; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 存储请求参数 + * + * @author Charles7c + * @since 2023/12/26 22:09 + */ +@Data +@Schema(description = "存储请求参数") +public class StorageReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "存储1") + @NotBlank(message = "名称不能为空") + @Length(max = 100, message = "名称长度不能超过 {max} 个字符") + private String name; + + /** + * 编码 + */ + @Schema(description = "编码", example = "local") + @NotBlank(message = "编码不能为空") + @Pattern(regexp = RegexConstants.GENERAL_CODE, message = "编码长度为 2-30 个字符,支持大小写字母、数字、下划线,以字母开头") + private String code; + + /** + * 类型 + */ + @Schema(description = "类型", example = "2") + @NotNull(message = "类型非法") + private StorageTypeEnum type; + + /** + * Access Key + */ + @Schema(description = "Access Key", example = "") + @Length(max = 255, message = "Access Key长度不能超过 {max} 个字符") + @NotBlank(message = "Access Key不能为空", groups = ValidationGroup.Storage.OSS.class) + private String accessKey; + + /** + * Secret Key + */ + @Schema(description = "Secret Key", example = "") + @NotBlank(message = "Secret Key不能为空", groups = ValidationGroup.Storage.OSS.class) + private String secretKey; + + /** + * Endpoint + */ + @Schema(description = "Endpoint", example = "") + @Length(max = 255, message = "Endpoint长度不能超过 {max} 个字符") + @NotBlank(message = "Endpoint不能为空", groups = ValidationGroup.Storage.OSS.class) + private String endpoint; + + /** + * Bucket/存储路径 + */ + @Schema(description = "Bucket/存储路径", example = "C:/ysoft-admin/data/file/") + @Length(max = 255, message = "Bucket长度不能超过 {max} 个字符", groups = ValidationGroup.Storage.OSS.class) + @Length(max = 255, message = "存储路径长度不能超过 {max} 个字符", groups = ValidationGroup.Storage.Local.class) + @NotBlank(message = "Bucket不能为空", groups = ValidationGroup.Storage.OSS.class) + @NotBlank(message = "存储路径不能为空", groups = ValidationGroup.Storage.Local.class) + private String bucketName; + + /** + * 域名/访问路径 + */ + @Schema(description = "域名/访问路径", example = "http://localhost:8000/file") + @Length(max = 255, message = "域名长度不能超过 {max} 个字符", groups = ValidationGroup.Storage.OSS.class) + @Length(max = 255, message = "访问路径长度不能超过 {max} 个字符", groups = ValidationGroup.Storage.Local.class) + @NotBlank(message = "访问路径不能为空", groups = ValidationGroup.Storage.Local.class) + private String domain; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + private Integer sort; + + /** + * 描述 + */ + @Schema(description = "描述", example = "存储描述") + @Length(max = 200, message = "描述长度不能超过 {max} 个字符") + private String description; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 是否为默认存储 + */ + @JsonIgnore + private Boolean isDefault; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserBasicInfoUpdateReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserBasicInfoUpdateReq.java new file mode 100644 index 0000000..a5a6ddd --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserBasicInfoUpdateReq.java @@ -0,0 +1,41 @@ +package top.ysoft.admin.system.model.req.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import top.ysoft.admin.common.constant.RegexConstants; +import top.ysoft.admin.common.enums.GenderEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户基础信息修改参数 + * + * @author Charles7c + * @since 2023/1/7 23:08 + */ +@Data +@Schema(description = "用户基础信息修改参数") +public class UserBasicInfoUpdateReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 昵称 + */ + @Schema(description = "昵称", example = "张三") + @NotBlank(message = "昵称不能为空") + @Pattern(regexp = RegexConstants.GENERAL_NAME, message = "昵称长度为 2-30 个字符,支持中文、字母、数字、下划线,短横线") + private String nickname; + + /** + * 性别 + */ + @Schema(description = "性别", example = "1") + @NotNull(message = "性别非法") + private GenderEnum gender; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserEmailUpdateRequest.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserEmailUpdateRequest.java new file mode 100644 index 0000000..8ce25d1 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserEmailUpdateRequest.java @@ -0,0 +1,48 @@ +package top.ysoft.admin.system.model.req.user; + +import cn.hutool.core.lang.RegexPool; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户邮箱修改参数 + * + * @author Charles7c + * @since 2023/1/12 20:18 + */ +@Data +@Schema(description = "用户邮箱修改参数") +public class UserEmailUpdateRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 新邮箱 + */ + @Schema(description = "新邮箱", example = "123456789@qq.com") + @NotBlank(message = "新邮箱不能为空") + @Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误") + private String email; + + /** + * 验证码 + */ + @Schema(description = "验证码", example = "888888") + @NotBlank(message = "验证码不能为空") + @Length(max = 6, message = "验证码非法") + private String captcha; + + /** + * 当前密码(加密) + */ + @Schema(description = "当前密码(加密)", example = "SYRLSszQGcMv4kP2Yolou9zf28B9GDakR9u91khxmR7V++i5A384kwnNZxqgvT6bjT4zqpIDuMFLWSt92hQJJA==") + @NotBlank(message = "当前密码不能为空") + private String oldPassword; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserImportReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserImportReq.java new file mode 100644 index 0000000..4a218ae --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserImportReq.java @@ -0,0 +1,59 @@ +package top.ysoft.admin.system.model.req.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.ImportPolicyEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户导入参数 + * + * @author Kils + * @since 2024-6-17 16:42 + */ +@Data +@Schema(description = "用户导入参数") +public class UserImportReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 导入会话KEY + */ + @Schema(description = "导入会话KEY", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed") + @NotBlank(message = "导入已过期,请重新上传") + private String importKey; + + /** + * 用户重复策略 + */ + @Schema(description = "重复用户策略", example = "1") + @NotNull(message = "重复用户策略不能为空") + private ImportPolicyEnum duplicateUser; + + /** + * 重复邮箱策略 + */ + @Schema(description = "重复邮箱策略", example = "1") + @NotNull(message = "重复邮箱策略不能为空") + private ImportPolicyEnum duplicateEmail; + + /** + * 重复手机策略 + */ + @Schema(description = "重复手机策略", example = "1") + @NotNull(message = "重复手机策略不能为空") + private ImportPolicyEnum duplicatePhone; + + /** + * 默认状态 + */ + @Schema(description = "默认状态", example = "1") + private DisEnableStatusEnum defaultStatus; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserImportRowReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserImportRowReq.java new file mode 100644 index 0000000..b15ce3f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserImportRowReq.java @@ -0,0 +1,82 @@ +package top.ysoft.admin.system.model.req.user; + +import cn.hutool.core.lang.RegexPool; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.common.constant.RegexConstants; +import top.continew.starter.extension.crud.validation.CrudValidationGroup; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户导入行数据 + * + * @author Kils + * @since 2024-6-17 16:42 + */ +@Data +@Schema(description = "用户导入行数据") +public class UserImportRowReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户名 + */ + @NotBlank(message = "用户名不能为空") + @Pattern(regexp = RegexConstants.USERNAME, message = "用户名长度为 4-64 个字符,支持大小写字母、数字、下划线,以字母开头") + private String username; + + /** + * 昵称 + */ + @NotBlank(message = "昵称不能为空") + @Pattern(regexp = RegexConstants.GENERAL_NAME, message = "昵称长度为 2-30 个字符,支持中文、字母、数字、下划线,短横线") + private String nickname; + + /** + * 密码 + */ + @NotBlank(message = "密码不能为空", groups = CrudValidationGroup.Add.class) + private String password; + + /** + * 部门名称 + */ + @NotBlank(message = "所属部门不能为空") + private String deptName; + + /** + * 角色 + */ + @NotBlank(message = "所属角色不能为空") + private String roleName; + + /** + * 性别 + */ + private String gender; + + /** + * 邮箱 + */ + @Pattern(regexp = "^$|" + RegexPool.EMAIL, message = "邮箱格式错误") + @Length(max = 255, message = "邮箱长度不能超过 {max} 个字符") + private String email; + + /** + * 手机号码 + */ + @Pattern(regexp = "^$|" + RegexPool.MOBILE, message = "手机号码格式错误") + private String phone; + + /** + * 描述 + */ + private String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserPasswordResetReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserPasswordResetReq.java new file mode 100644 index 0000000..8767ba6 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserPasswordResetReq.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.system.model.req.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户密码重置参数 + * + * @author Charles7c + * @since 2024/2/2 22:50 + */ +@Data +@Schema(description = "用户密码重置参数") +public class UserPasswordResetReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 新密码(加密) + */ + @Schema(description = "新密码(加密)", example = "Gzc78825P5baH190lRuZFb9KJxRt/psN2jiyOMPoc5WRcCvneCwqDm3Q33BZY56EzyyVy7vQu7jQwYTK4j1+5w==") + @NotBlank(message = "新密码不能为空") + private String newPassword; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserPasswordUpdateReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserPasswordUpdateReq.java new file mode 100644 index 0000000..1f9ab23 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserPasswordUpdateReq.java @@ -0,0 +1,35 @@ +package top.ysoft.admin.system.model.req.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户密码修改参数 + * + * @author Charles7c + * @since 2023/1/9 23:28 + */ +@Data +@Schema(description = "用户密码修改参数") +public class UserPasswordUpdateReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 当前密码(加密) + */ + @Schema(description = "当前密码(加密)", example = "E7c72TH+LDxKTwavjM99W1MdI9Lljh79aPKiv3XB9MXcplhm7qJ1BJCj28yaflbdVbfc366klMtjLIWQGqb0qw==") + private String oldPassword; + + /** + * 新密码(加密) + */ + @Schema(description = "新密码(加密)", example = "Gzc78825P5baH190lRuZFb9KJxRt/psN2jiyOMPoc5WRcCvneCwqDm3Q33BZY56EzyyVy7vQu7jQwYTK4j1+5w==") + @NotBlank(message = "新密码不能为空") + private String newPassword; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserPhoneUpdateReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserPhoneUpdateReq.java new file mode 100644 index 0000000..ea5b476 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserPhoneUpdateReq.java @@ -0,0 +1,48 @@ +package top.ysoft.admin.system.model.req.user; + +import cn.hutool.core.lang.RegexPool; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户手机号修改参数 + * + * @author Charles7c + * @since 2023/10/27 20:11 + */ +@Data +@Schema(description = "用户手机号修改参数") +public class UserPhoneUpdateReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 新手机号 + */ + @Schema(description = "新手机号", example = "13811111111") + @NotBlank(message = "新手机号不能为空") + @Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误") + private String phone; + + /** + * 验证码 + */ + @Schema(description = "验证码", example = "8888") + @NotBlank(message = "验证码不能为空") + @Length(max = 4, message = "验证码非法") + private String captcha; + + /** + * 当前密码(加密) + */ + @Schema(description = "当前密码(加密)", example = "SYRLSszQGcMv4kP2Yolou9zf28B9GDakR9u91khxmR7V++i5A384kwnNZxqgvT6bjT4zqpIDuMFLWSt92hQJJA==") + @NotBlank(message = "当前密码不能为空") + private String oldPassword; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserReq.java new file mode 100644 index 0000000..21e76db --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserReq.java @@ -0,0 +1,110 @@ +package top.ysoft.admin.system.model.req.user; + +import cn.hutool.core.lang.RegexPool; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.common.constant.RegexConstants; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.enums.GenderEnum; +import top.continew.starter.extension.crud.validation.CrudValidationGroup; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 创建或修改用户参数 + * + * @author Charles7c + * @since 2023/2/20 21:03 + */ +@Data +@Schema(description = "创建或修改用户参数") +public class UserReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户名 + */ + @Schema(description = "用户名", example = "zhangsan") + @NotBlank(message = "用户名不能为空") + @Pattern(regexp = RegexConstants.USERNAME, message = "用户名长度为 4-64 个字符,支持大小写字母、数字、下划线,以字母开头") + private String username; + + /** + * 昵称 + */ + @Schema(description = "昵称", example = "张三") + @NotBlank(message = "昵称不能为空") + @Pattern(regexp = RegexConstants.GENERAL_NAME, message = "昵称长度为 2-30 个字符,支持中文、字母、数字、下划线,短横线") + private String nickname; + + /** + * 密码(加密) + */ + @Schema(description = "密码(加密)", example = "E7c72TH+LDxKTwavjM99W1MdI9Lljh79aPKiv3XB9MXcplhm7qJ1BJCj28yaflbdVbfc366klMtjLIWQGqb0qw==") + @NotBlank(message = "密码不能为空", groups = CrudValidationGroup.Add.class) + private String password; + + /** + * 邮箱 + */ + @Schema(description = "邮箱", example = "123456789@qq.com") + @Pattern(regexp = "^$|" + RegexPool.EMAIL, message = "邮箱格式错误") + @Length(max = 255, message = "邮箱长度不能超过 {max} 个字符") + private String email; + + /** + * 手机号码 + */ + @Schema(description = "手机号码", example = "13811111111") + @Pattern(regexp = "^$|" + RegexPool.MOBILE, message = "手机号码格式错误") + private String phone; + + /** + * 性别 + */ + @Schema(description = "性别", example = "1") + @NotNull(message = "性别非法") + private GenderEnum gender; + + /** + * 所属部门 + */ + @Schema(description = "所属部门", example = "5") + @NotNull(message = "所属部门不能为空") + private Long deptId; + + /** + * 所属角色 + */ + @Schema(description = "所属角色", example = "2") + @NotEmpty(message = "所属角色不能为空") + private List roleIds; + + /** + * 描述 + */ + @Schema(description = "描述", example = "张三描述信息") + @Length(max = 200, message = "描述长度不能超过 {max} 个字符") + private String description; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + private Long equipmentId; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserRoleUpdateReq.java b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserRoleUpdateReq.java new file mode 100644 index 0000000..d503547 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/req/user/UserRoleUpdateReq.java @@ -0,0 +1,30 @@ +package top.ysoft.admin.system.model.req.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 用户角色修改参数 + * + * @author Charles7c + * @since 2023/2/24 23:05 + */ +@Data +@Schema(description = "用户角色修改参数") +public class UserRoleUpdateReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 角色 ID 列表 + */ + @Schema(description = "所属角色", example = "1,2") + @NotEmpty(message = "所属角色不能为空") + private List roleIds; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/AvatarResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/AvatarResp.java new file mode 100644 index 0000000..abf5eb0 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/AvatarResp.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.system.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 头像信息 + * + * @author Charles7c + * @since 2023/1/2 16:29 + */ +@Data +@Builder +@Schema(description = "头像信息") +public class AvatarResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 头像地址 + */ + @Schema(description = "头像地址", example = "https://himg.bdimg.com/sys/portrait/item/public.1.81ac9a9e.rf1ix17UfughLQjNo7XQ_w.jpg") + private String avatar; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/ClientResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/ClientResp.java new file mode 100644 index 0000000..d1fb7c4 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/ClientResp.java @@ -0,0 +1,72 @@ +package top.ysoft.admin.system.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; + +import java.io.Serial; +import java.util.List; + +/** + * 终端信息 + * + * @author KAI + * @author Charles7c + * @since 2024/12/03 16:04 + */ +@Data +@Schema(description = "终端信息") +public class ClientResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 终端 ID + */ + @Schema(description = "终端 ID", example = "ef51c9a3e9046c4f2ea45142c8a8344a") + private String clientId; + + /** + * 终端 Key + */ + @Schema(description = "终端 Key", example = "PC") + private String clientKey; + + /** + * 终端秘钥 + */ + @Schema(description = "终端秘钥", example = "dd77ab1e353a027e0d60ce3b151e8642") + private String clientSecret; + + /** + * 认证类型 + */ + @Schema(description = "认证类型", example = "ACCOUNT") + private List authType; + + /** + * 终端类型 + */ + @Schema(description = "终端类型", example = "PC") + private String clientType; + + /** + * Token 最低活跃频率(单位:秒,-1:不限制,永不冻结) + */ + @Schema(description = "Token 最低活跃频率(单位:秒,-1:不限制,永不冻结)", example = "1800") + private Long activeTimeout; + + /** + * Token 有效期(单位:秒,-1:永不过期) + */ + @Schema(description = "Token 有效期(单位:秒,-1:永不过期)", example = "86400") + private Long timeout; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/ConfigDetailResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/ConfigDetailResp.java new file mode 100644 index 0000000..a904c28 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/ConfigDetailResp.java @@ -0,0 +1,56 @@ +package top.ysoft.admin.system.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; + +/** + * 参数配置详情信息 + * + * @author zc + * @since 2025/12/30 12:44 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "参数配置详情信息") +public class ConfigDetailResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 参数名称 + */ + @Schema(description = "参数名称") + @ExcelProperty(value = "参数名称") + private String configName; + + /** + * 参数键名 + */ + @Schema(description = "参数键名") + @ExcelProperty(value = "参数键名") + private String configKey; + + /** + * 参数键值 + */ + @Schema(description = "参数键值") + @ExcelProperty(value = "参数键值") + private String configValue; + + /** + * 备注 + */ + @Schema(description = "备注") + @ExcelProperty(value = "备注") + private String remark; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/ConfigResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/ConfigResp.java new file mode 100644 index 0000000..fcdd5f9 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/ConfigResp.java @@ -0,0 +1,48 @@ +package top.ysoft.admin.system.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.time.*; + +/** + * 参数配置信息 + * + * @author zc + * @since 2025/12/30 12:44 + */ +@Data +@Schema(description = "参数配置信息") +public class ConfigResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 参数名称 + */ + @Schema(description = "参数名称") + private String configName; + + /** + * 参数键名 + */ + @Schema(description = "参数键名") + private String configKey; + + /** + * 参数键值 + */ + @Schema(description = "参数键值") + private String configValue; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/DeptResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/DeptResp.java new file mode 100644 index 0000000..3ce847e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/DeptResp.java @@ -0,0 +1,70 @@ +package top.ysoft.admin.system.model.resp; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.extension.crud.annotation.TreeField; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; + +import java.io.Serial; + +/** + * 部门信息 + * + * @author Charles7c + * @since 2023/1/22 13:53 + */ +@Data +@ExcelIgnoreUnannotated +@TreeField(value = "id", nameKey = "name") +@Schema(description = "部门信息") +public class DeptResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "测试部") + @ExcelProperty(value = "名称", order = 2) + private String name; + + /** + * 上级部门 ID + */ + @Schema(description = "上级部门 ID", example = "2") + @ExcelProperty(value = "上级部门 ID", order = 3) + private Long parentId; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + @ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class, order = 5) + private DisEnableStatusEnum status; + + /** + * 排序 + */ + @Schema(description = "排序", example = "3") + @ExcelProperty(value = "排序", order = 6) + private Integer sort; + + /** + * 是否为系统内置数据 + */ + @Schema(description = "是否为系统内置数据", example = "false") + @ExcelProperty(value = "系统内置", order = 7) + private Boolean isSystem; + + /** + * 描述 + */ + @Schema(description = "描述", example = "测试部描述信息") + @ExcelProperty(value = "描述", order = 8) + private String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/DictItemResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/DictItemResp.java new file mode 100644 index 0000000..3f2a4e4 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/DictItemResp.java @@ -0,0 +1,67 @@ +package top.ysoft.admin.system.model.resp; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; + +import java.io.Serial; + +/** + * 字典项信息 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Data +@Schema(description = "字典项信息") +public class DictItemResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标签 + */ + @Schema(description = "标签", example = "通知") + private String label; + + /** + * 值 + */ + @Schema(description = "值", example = "1") + private String value; + + /** + * 标签颜色 + */ + @Schema(description = "标签颜色", example = "blue") + private String color; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + @ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class) + private DisEnableStatusEnum status; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + private Integer sort; + + /** + * 描述 + */ + @Schema(description = "描述", example = "通知描述信息") + private String description; + + /** + * 字典 ID + */ + @Schema(description = "字典 ID", example = "1") + private Long dictId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/DictResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/DictResp.java new file mode 100644 index 0000000..95e6454 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/DictResp.java @@ -0,0 +1,45 @@ +package top.ysoft.admin.system.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; + +/** + * 字典信息 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Data +@Schema(description = "字典信息") +public class DictResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "公告类型") + private String name; + + /** + * 编码 + */ + @Schema(description = "编码", example = "notice_type") + private String code; + + /** + * 描述 + */ + @Schema(description = "描述", example = "公告类型描述信息") + private String description; + + /** + * 是否为系统内置数据 + */ + @Schema(description = "是否为系统内置数据", example = "true") + private Boolean isSystem; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/MenuResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/MenuResp.java new file mode 100644 index 0000000..6d098ec --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/MenuResp.java @@ -0,0 +1,109 @@ +package top.ysoft.admin.system.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseResp; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.MenuTypeEnum; +import top.continew.starter.extension.crud.annotation.TreeField; + +import java.io.Serial; + +/** + * 菜单信息 + * + * @author Charles7c + * @since 2023/2/15 20:23 + */ +@Data +@TreeField(value = "id") +@Schema(description = "菜单信息") +public class MenuResp extends BaseResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标题 + */ + @Schema(description = "标题", example = "用户管理") + private String title; + + /** + * 上级菜单 ID + */ + @Schema(description = "上级菜单 ID", example = "1000") + private Long parentId; + + /** + * 类型 + */ + @Schema(description = "类型", example = "2") + private MenuTypeEnum type; + + /** + * 路由地址 + */ + @Schema(description = "路由地址", example = "/system/user") + private String path; + + /** + * 组件名称 + */ + @Schema(description = "组件名称", example = "User") + private String name; + + /** + * 组件路径 + */ + @Schema(description = "组件路径", example = "/system/user/index") + private String component; + + /** + * 重定向地址 + */ + @Schema(description = "重定向地址") + private String redirect; + + /** + * 图标 + */ + @Schema(description = "图标", example = "user") + private String icon; + + /** + * 是否外链 + */ + @Schema(description = "是否外链", example = "false") + private Boolean isExternal; + + /** + * 是否缓存 + */ + @Schema(description = "是否缓存", example = "false") + private Boolean isCache; + + /** + * 是否隐藏 + */ + @Schema(description = "是否隐藏", example = "false") + private Boolean isHidden; + + /** + * 权限标识 + */ + @Schema(description = "权限标识", example = "system:user:list") + private String permission; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + private Integer sort; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/NoticeDetailResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/NoticeDetailResp.java new file mode 100644 index 0000000..9d6f611 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/NoticeDetailResp.java @@ -0,0 +1,74 @@ +package top.ysoft.admin.system.model.resp; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.system.enums.NoticeScopeEnum; + +import java.io.Serial; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 公告详情信息 + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "公告详情信息") +public class NoticeDetailResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标题 + */ + @Schema(description = "标题", example = "这是公告标题") + @ExcelProperty(value = "标题") + private String title; + + /** + * 内容 + */ + @Schema(description = "内容", example = "这是公告内容") + @ExcelProperty(value = "内容") + private String content; + + /** + * 类型(取值于字典 notice_type) + */ + @Schema(description = "类型(取值于字典 notice_type)", example = "1") + @ExcelProperty(value = "类型") + private String type; + + /** + * 生效时间 + */ + @Schema(description = "生效时间", example = "2023-08-08 00:00:00", type = "string") + @ExcelProperty(value = "生效时间") + private LocalDateTime effectiveTime; + + /** + * 终止时间 + */ + @Schema(description = "终止时间", example = "2023-08-08 23:59:59", type = "string") + @ExcelProperty(value = "终止时间") + private LocalDateTime terminateTime; + + /** + * 通知范围 + */ + @Schema(description = "通知范围", example = "2") + private NoticeScopeEnum noticeScope; + + /** + * 指定用户 + */ + @Schema(description = "指定用户", example = "[1,2,3]") + private List noticeUsers; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/NoticeResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/NoticeResp.java new file mode 100644 index 0000000..c555410 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/NoticeResp.java @@ -0,0 +1,71 @@ +package top.ysoft.admin.system.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseResp; +import top.ysoft.admin.system.enums.NoticeScopeEnum; +import top.ysoft.admin.system.enums.NoticeStatusEnum; + +import java.io.Serial; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 公告信息 + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +@Data +@Schema(description = "公告信息") +public class NoticeResp extends BaseResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 标题 + */ + @Schema(description = "标题", example = "这是公告标题") + private String title; + + /** + * 类型(取值于字典 notice_type) + */ + @Schema(description = "类型(取值于字典 notice_type)", example = "1") + private String type; + + /** + * 生效时间 + */ + @Schema(description = "生效时间", example = "2023-08-08 00:00:00", type = "string") + private LocalDateTime effectiveTime; + + /** + * 终止时间 + */ + @Schema(description = "终止时间", example = "2023-08-08 23:59:59", type = "string") + private LocalDateTime terminateTime; + + /** + * 状态 + * + * @return 公告状态 + */ + @Schema(description = "状态", example = "1") + public NoticeStatusEnum getStatus() { + return NoticeStatusEnum.getStatus(effectiveTime, terminateTime); + } + + /** + * 通知范围 + */ + @Schema(description = "通知范围(1.所有人 2.指定用户)", example = "1") + private NoticeScopeEnum noticeScope; + + /** + * 指定用户 + */ + @Schema(description = "指定用户", example = "[1,2,3]") + private List noticeUsers; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/OptionResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/OptionResp.java new file mode 100644 index 0000000..c597bf5 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/OptionResp.java @@ -0,0 +1,63 @@ +package top.ysoft.admin.system.model.resp; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 参数信息 + * + * @author Bull-BCLS + * @since 2023/8/26 19:38 + */ +@Data +@Schema(description = "参数信息") +public class OptionResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 名称 + */ + @Schema(description = "名称", example = "系统标题") + private String name; + + /** + * 键 + */ + @Schema(description = "键", example = "site_title") + private String code; + + /** + * 值 + */ + @Schema(description = "值", example = "Ysoft Admin") + private String value; + + /** + * 默认值 + */ + @JsonIgnore + private String defaultValue; + + /** + * 描述 + */ + @Schema(description = "描述", example = "用于显示登录页面的系统标题。") + private String description; + + public String getValue() { + return StrUtil.nullToDefault(value, defaultValue); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/PeopleEquipmentResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/PeopleEquipmentResp.java new file mode 100644 index 0000000..7a5d988 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/PeopleEquipmentResp.java @@ -0,0 +1,59 @@ +package top.ysoft.admin.system.model.resp; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; + +/** + * 人员设备下发信息信息 + * + * @author zc + * @since 2025/03/27 14:59 + */ +@Data +@Schema(description = "人员设备下发信息信息") +public class PeopleEquipmentResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 人员Id + */ + @Schema(description = "人员Id") + private Long peopleId; + + /** + * 设备Id + */ + @Schema(description = "设备Id") + private Long equipmentId; + + /** + * 人员编号对接 + */ + @Schema(description = "人员编号对接") + private String guid; + + /** + * 人像对接 + */ + @Schema(description = "人像对接") + private String faceGuid; + + /** + * 访客Id + */ + @Schema(description = "访客Id") + private Long visitorId; + + /** + * 三方人员id + */ + @Schema(description = "三方人员id") + private String otherId; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/StorageResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/StorageResp.java new file mode 100644 index 0000000..ed92ec7 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/StorageResp.java @@ -0,0 +1,103 @@ +package top.ysoft.admin.system.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.StorageTypeEnum; +import top.continew.starter.security.mask.annotation.JsonMask; + +import java.io.Serial; + +/** + * 存储响应信息 + * + * @author Charles7c + * @since 2023/12/26 22:09 + */ +@Data +@Schema(description = "存储响应信息") +public class StorageResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "存储1") + private String name; + + /** + * 编码 + */ + @Schema(description = "编码", example = "local") + private String code; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 类型 + */ + @Schema(description = "类型", example = "2") + private StorageTypeEnum type; + + /** + * Access Key + */ + @Schema(description = "Access Key", example = "") + private String accessKey; + + /** + * Secret Key + */ + @Schema(description = "Secret Key", example = "") + @JsonMask(left = 4, right = 3) + private String secretKey; + + /** + * Endpoint + */ + @Schema(description = "Endpoint", example = "") + private String endpoint; + + /** + * Bucket/存储路径 + */ + @Schema(description = "Bucket/存储路径", example = "C:/ysoft-admin/data/file/") + private String bucketName; + + /** + * 域名/访问路径 + */ + @Schema(description = "域名", example = "http://localhost:8000/file") + private String domain; + + /** + * 描述 + */ + @Schema(description = "描述", example = "存储描述") + private String description; + + /** + * 是否为默认存储 + */ + @Schema(description = "是否为默认存储", example = "true") + private Boolean isDefault; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + private Integer sort; + + @Override + public Boolean getDisabled() { + return this.getIsDefault(); + } + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardAccessTrendResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardAccessTrendResp.java new file mode 100644 index 0000000..070c01e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardAccessTrendResp.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.system.model.resp.dashboard; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 仪表盘-访问趋势信息 + * + * @author Charles7c + * @since 2023/9/9 20:20 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "仪表盘-访问趋势信息") +public class DashboardAccessTrendResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 日期 + */ + @Schema(description = "日期", example = "2023-08-08") + private String date; + + /** + * 浏览量(PV) + */ + @Schema(description = "浏览量(PV)", example = "1000") + private Long pvCount; + + /** + * IP 数 + */ + @Schema(description = "IP 数", example = "500") + private Long ipCount; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardChartCommonResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardChartCommonResp.java new file mode 100644 index 0000000..f3bf0c1 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardChartCommonResp.java @@ -0,0 +1,37 @@ +package top.ysoft.admin.system.model.resp.dashboard; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 仪表盘-通用图表信息 + * + * @author Charles7c + * @since 2024/10/17 21:37 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "仪表盘-通用图表信息") +public class DashboardChartCommonResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "Windows 10") + private String name; + + /** + * 数量 + */ + @Schema(description = "数量", example = "1234") + private Long value; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardNoticeResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardNoticeResp.java new file mode 100644 index 0000000..2674ca5 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardNoticeResp.java @@ -0,0 +1,39 @@ +package top.ysoft.admin.system.model.resp.dashboard; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 仪表盘-公告信息 + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +@Data +@Schema(description = "仪表盘-公告信息") +public class DashboardNoticeResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 标题 + */ + @Schema(description = "标题", example = "这是公告标题") + private String title; + + /** + * 类型(取值于字典 notice_type) + */ + @Schema(description = "类型(取值于字典 notice_type)", example = "1") + private String type; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardOverviewCommonResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardOverviewCommonResp.java new file mode 100644 index 0000000..61fe7b8 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardOverviewCommonResp.java @@ -0,0 +1,58 @@ +package top.ysoft.admin.system.model.resp.dashboard; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 仪表盘-通用总览信息 + * + * @author Charles7c + * @since 2024/10/19 12:19 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "仪表盘-通用总览信息") +public class DashboardOverviewCommonResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 总数 + */ + @Schema(description = "总数", example = "888888") + private Long total; + + /** + * 今日数量 + */ + @Schema(description = "今日数量", example = "888") + private Long today; + + /** + * 较昨日新增(百分比) + */ + @Schema(description = "较昨日新增(百分比)", example = "23.4") + private BigDecimal growth; + + /** + * 图表数据 + */ + @Schema(description = "图表数据") + private List dataList; + + /** + * 昨日数量 + */ + @JsonIgnore + private Long yesterday; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardTotalResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardTotalResp.java new file mode 100644 index 0000000..aac6518 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/dashboard/DashboardTotalResp.java @@ -0,0 +1,53 @@ +package top.ysoft.admin.system.model.resp.dashboard; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 仪表盘-总计信息 + * + * @author Charles7c + * @since 2023/9/8 21:32 + */ +@Data +@Schema(description = "仪表盘-总计信息") +public class DashboardTotalResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 浏览量(PV) + */ + @Schema(description = "浏览量(PV)", example = "88888") + private Long pvCount; + + /** + * IP 数 + */ + @Schema(description = "IP 数", example = "66666") + private Long ipCount; + + /** + * 今日浏览量(PV) + */ + @Schema(description = "今日浏览量(PV)", example = "1234") + private Long todayPvCount; + + /** + * 较昨日新增 PV(百分比) + */ + @Schema(description = "较昨日新增(百分比)", example = "23.4") + private BigDecimal newPvFromYesterday; + + /** + * 昨日浏览量(PV) + */ + @JsonIgnore + private Long yesterdayPvCount; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/file/FileResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/file/FileResp.java new file mode 100644 index 0000000..428fdc2 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/file/FileResp.java @@ -0,0 +1,114 @@ +package top.ysoft.admin.system.model.resp.file; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.system.enums.FileTypeEnum; + +import java.io.Serial; + +/** + * 文件信息 + * + * @author Charles7c + * @since 2023/12/23 10:38 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "文件详情信息") +public class FileResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "example") + private String name; + + /** + * 大小(字节) + */ + @Schema(description = "大小(字节)", example = "4096") + private Long size; + + /** + * URL + */ + @Schema(description = "URL", example = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/example/example.jpg") + private String url; + + /** + * 上级目录 + */ + @Schema(description = "上级目录", example = "25") + private String parentPath; + + /** + * 绝对路径 + */ + @Schema(description = "绝对路径", example = "/2025/2/25") + private String absPath; + + /** + * 扩展名 + */ + @Schema(description = "扩展名", example = "jpg") + private String extension; + + /** + * 内容类型 + */ + @Schema(description = "内容类型", example = "image/jpeg") + private String contentType; + + /** + * 类型 + */ + @Schema(description = "类型", example = "2") + private FileTypeEnum type; + + /** + * MD5 值 + */ + @Schema(description = "MD5值", example = "193572f83684128a0d0f993e97100f8a") + private String md5; + + /** + * 元数据 + */ + @Schema(description = "元数据", example = "{width:1024,height:1024}") + private String metadata; + + /** + * 缩略图大小(字节) + */ + @Schema(description = "缩略图大小(字节)", example = "1024") + private Long thumbnailSize; + + /** + * 缩略图 URL + */ + @Schema(description = "缩略图 URL", example = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/example/example.jpg.min.jpg") + private String thumbnailUrl; + + /** + * 缩略图元数据 + */ + @Schema(description = "缩略图文件元数据", example = "{width:100,height:100}") + private String thumbnailMetadata; + + /** + * 存储 ID + */ + @Schema(description = "存储 ID", example = "1") + private Long storageId; + + /** + * 存储名称 + */ + @Schema(description = "存储名称", example = "MinIO") + private String storageName; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/file/FileStatisticsResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/file/FileStatisticsResp.java new file mode 100644 index 0000000..c2dc782 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/file/FileStatisticsResp.java @@ -0,0 +1,49 @@ +package top.ysoft.admin.system.model.resp.file; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.system.enums.FileTypeEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 文件资源统计信息 + * + * @author Kils + * @since 2024/4/30 14:30 + */ +@Data +@Schema(description = "文件资源统计信息") +@JsonInclude(JsonInclude.Include.NON_NULL) +public class FileStatisticsResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 文件类型 + */ + @Schema(description = "类型", example = "2") + private FileTypeEnum type; + + /** + * 大小(字节) + */ + @Schema(description = "大小(字节)", example = "4096") + private Long size; + + /** + * 数量 + */ + @Schema(description = "数量", example = "1000") + private Long number; + + /** + * 分类数据 + */ + @Schema(description = "分类数据") + private List data; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/file/FileUploadResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/file/FileUploadResp.java new file mode 100644 index 0000000..920369a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/file/FileUploadResp.java @@ -0,0 +1,48 @@ +package top.ysoft.admin.system.model.resp.file; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Map; + +/** + * 文件上传响应信息 + * + * @author Charles7c + * @since 2024/3/6 22:26 + */ +@Data +@Builder +@Schema(description = "文件上传响应信息") +public class FileUploadResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 文件 id + */ + @Schema(description = "文件 id", example = "1897293810343682049") + private String id; + + /** + * 文件 URL + */ + @Schema(description = "文件 URL", example = "http://localhost:8000/file/65e87dc3fb377a6fb58bdece.jpg") + private String url; + + /** + * 缩略图文件 URL + */ + @Schema(description = "缩略图文件 URL", example = "http://localhost:8000/file/65e87dc3fb377a6fb58bdece.jpg") + private String thUrl; + + /** + * 元数据 + */ + @Schema(description = "元数据") + private Map metadata; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/LogDetailResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/LogDetailResp.java new file mode 100644 index 0000000..26f6e27 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/LogDetailResp.java @@ -0,0 +1,155 @@ +package top.ysoft.admin.system.model.resp.log; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.annotation.condition.ConditionOnPropertyNotNull; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.system.enums.LogStatusEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 日志详情信息 + * + * @author Charles7c + * @since 2023/1/18 20:19 + */ +@Data +@Schema(description = "日志详情信息") +public class LogDetailResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 链路 ID + */ + @Schema(description = "链路 ID", example = "904846526308876288") + private String traceId; + + /** + * 日志描述 + */ + @Schema(description = "日志描述", example = "新增数据") + private String description; + + /** + * 所属模块 + */ + @Schema(description = "所属模块", example = "部门管理") + private String module; + + /** + * 请求 URL + */ + @Schema(description = "请求 URL", example = "http://api.ysoft.top/system/dept") + private String requestUrl; + + /** + * 请求方式 + */ + @Schema(description = "请求方式", example = "POST") + private String requestMethod; + + /** + * 请求头 + */ + @Schema(description = "请求头", example = "{\"Origin\": [\"https://admin.ysoft.top\"],...}") + private String requestHeaders; + + /** + * 请求体 + */ + @Schema(description = "请求体", example = "{\"name\": \"测试部\",...}") + private String requestBody; + + /** + * 状态码 + */ + @Schema(description = "状态码", example = "200") + private Integer statusCode; + + /** + * 响应头 + */ + @Schema(description = "响应头", example = "{\"Content-Type\": [\"application/json\"],...}") + private String responseHeaders; + + /** + * 响应体 + */ + @Schema(description = "响应体", example = "{\"success\":true},...") + private String responseBody; + + /** + * 耗时(ms) + */ + @Schema(description = "耗时(ms)", example = "58") + private Long timeTaken; + + /** + * IP + */ + @Schema(description = "IP", example = "") + private String ip; + + /** + * IP 归属地 + */ + @Schema(description = "IP 归属地", example = "中国北京北京市") + private String address; + + /** + * 浏览器 + */ + @Schema(description = "浏览器", example = "Chrome 115.0.0.0") + private String browser; + + /** + * 操作系统 + */ + @Schema(description = "操作系统", example = "Windows 10") + private String os; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private LogStatusEnum status; + + /** + * 错误信息 + */ + @Schema(description = "错误信息") + private String errorMsg; + + /** + * 创建人 + */ + @JsonIgnore + @ConditionOnPropertyNotNull + @Assemble(prop = ":createUserString", container = ContainerConstants.USER_NICKNAME) + private Long createUser; + + /** + * 创建人 + */ + @Schema(description = "创建人", example = "张三") + private String createUserString; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") + private LocalDateTime createTime; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/LogResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/LogResp.java new file mode 100644 index 0000000..be18016 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/LogResp.java @@ -0,0 +1,102 @@ +package top.ysoft.admin.system.model.resp.log; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.system.enums.LogStatusEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 日志信息 + * + * @author Charles7c + * @since 2023/1/14 18:27 + */ +@Data +@Schema(description = "日志信息") +public class LogResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 日志描述 + */ + @Schema(description = "日志描述", example = "新增数据") + private String description; + + /** + * 所属模块 + */ + @Schema(description = "所属模块", example = "部门管理") + private String module; + + /** + * 耗时(ms) + */ + @Schema(description = "耗时(ms)", example = "58") + private Long timeTaken; + + /** + * IP + */ + @Schema(description = "IP", example = "") + private String ip; + + /** + * IP 归属地 + */ + @Schema(description = "IP 归属地", example = "中国北京北京市") + private String address; + + /** + * 浏览器 + */ + @Schema(description = "浏览器", example = "Chrome 115.0.0.0") + private String browser; + + /** + * 操作系统 + */ + @Schema(description = "操作系统", example = "Windows 10") + private String os; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private LogStatusEnum status; + + /** + * 错误信息 + */ + @Schema(description = "错误信息") + private String errorMsg; + + /** + * 创建人 + */ + @JsonIgnore + private Long createUser; + + /** + * 创建人 + */ + @Schema(description = "创建人", example = "张三") + private String createUserString; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") + private LocalDateTime createTime; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/LoginLogExportResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/LoginLogExportResp.java new file mode 100644 index 0000000..ad4f472 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/LoginLogExportResp.java @@ -0,0 +1,90 @@ +package top.ysoft.admin.system.model.resp.log; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.system.enums.LogStatusEnum; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 登录日志导出信息 + * + * @author Charles7c + * @since 2023/1/14 18:27 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "登录日志导出信息") +public class LoginLogExportResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + @ExcelProperty(value = "ID") + private Long id; + + /** + * 登录时间 + */ + @Schema(description = "登录时间", example = "2023-08-08 08:08:08", type = "string") + @ExcelProperty(value = "登录时间") + private LocalDateTime createTime; + + /** + * 用户昵称 + */ + @Schema(description = "用户昵称", example = "张三") + @ExcelProperty(value = "用户昵称") + private String createUserString; + + /** + * 登录行为 + */ + @Schema(description = "登录行为", example = "账号登录") + @ExcelProperty(value = "登录行为") + private String description; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + @ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class) + private LogStatusEnum status; + + /** + * 登录 IP + */ + @Schema(description = "登录 IP", example = "") + @ExcelProperty(value = "登录 IP") + private String ip; + + /** + * 登录地点 + */ + @Schema(description = "登录地点", example = "中国北京北京市") + @ExcelProperty(value = "登录地点") + private String address; + + /** + * 浏览器 + */ + @Schema(description = "浏览器", example = "Chrome 115.0.0.0") + @ExcelProperty(value = "浏览器") + private String browser; + + /** + * 终端系统 + */ + @Schema(description = "终端系统", example = "Windows 10") + @ExcelProperty(value = "终端系统") + private String os; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/OperationLogExportResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/OperationLogExportResp.java new file mode 100644 index 0000000..f162d8e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/log/OperationLogExportResp.java @@ -0,0 +1,104 @@ +package top.ysoft.admin.system.model.resp.log; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.system.enums.LogStatusEnum; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 操作日志导出信息 + * + * @author Charles7c + * @since 2023/1/14 18:27 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "操作日志导出信息") +public class OperationLogExportResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + @ExcelProperty(value = "ID") + private Long id; + + /** + * 操作时间 + */ + @Schema(description = "操作时间", example = "2023-08-08 08:08:08", type = "string") + @ExcelProperty(value = "操作时间") + private LocalDateTime createTime; + + /** + * 操作人 + */ + @Schema(description = "操作人", example = "张三") + @ExcelProperty(value = "操作人") + private String createUserString; + + /** + * 操作内容 + */ + @Schema(description = "操作内容", example = "账号登录") + @ExcelProperty(value = "操作内容") + private String description; + + /** + * 所属模块 + */ + @Schema(description = "所属模块", example = "部门管理") + @ExcelProperty(value = "所属模块") + private String module; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + @ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class) + private LogStatusEnum status; + + /** + * 操作 IP + */ + @Schema(description = "操作 IP", example = "") + @ExcelProperty(value = "操作 IP") + private String ip; + + /** + * 操作地点 + */ + @Schema(description = "操作地点", example = "中国北京北京市") + @ExcelProperty(value = "操作地点") + private String address; + + /** + * 耗时(ms) + */ + @Schema(description = "耗时(ms)", example = "58") + @ExcelProperty(value = "耗时(ms)") + private Long timeTaken; + + /** + * 浏览器 + */ + @Schema(description = "浏览器", example = "Chrome 115.0.0.0") + @ExcelProperty(value = "浏览器") + private String browser; + + /** + * 终端系统 + */ + @Schema(description = "终端系统", example = "Windows 10") + @ExcelProperty(value = "终端系统") + private String os; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/message/MessageResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/message/MessageResp.java new file mode 100644 index 0000000..ef74662 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/message/MessageResp.java @@ -0,0 +1,81 @@ +package top.ysoft.admin.system.model.resp.message; + +import cn.crane4j.annotation.Assemble; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.system.enums.MessageTypeEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 消息信息 + * + * @author Bull-BCLS + * @since 2023/10/15 19:05 + */ +@Data +@Schema(description = "消息信息") +public class MessageResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 标题 + */ + @Schema(description = "标题", example = "欢迎注册 xxx") + private String title; + + /** + * 内容 + */ + @Schema(description = "内容", example = "尊敬的 xx,欢迎注册使用,请及时配置您的密码。") + private String content; + + /** + * 类型 + */ + @Schema(description = "类型(1:系统消息)", example = "1") + private MessageTypeEnum type; + + /** + * 是否已读 + */ + @Schema(description = "是否已读", example = "true") + private Boolean isRead; + + /** + * 读取时间 + */ + @Schema(description = "读取时间", example = "2023-08-08 23:59:59", type = "string") + private LocalDateTime readTime; + + /** + * 创建人 + */ + @JsonIgnore + @Assemble(prop = ":createUserString", container = ContainerConstants.USER_NICKNAME) + private Long createUser; + + /** + * 创建人 + */ + @Schema(description = "创建人", example = "超级管理员") + private String createUserString; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") + private LocalDateTime createTime; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/message/MessageTypeUnreadResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/message/MessageTypeUnreadResp.java new file mode 100644 index 0000000..795b03f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/message/MessageTypeUnreadResp.java @@ -0,0 +1,34 @@ +package top.ysoft.admin.system.model.resp.message; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.system.enums.MessageTypeEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 各类型未读消息信息 + * + * @author Charles7c + * @since 2023/11/2 23:00 + */ +@Data +@Schema(description = "各类型未读消息信息") +public class MessageTypeUnreadResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 类型 + */ + @Schema(description = "类型(1:系统消息)", example = "1") + private MessageTypeEnum type; + + /** + * 数量 + */ + @Schema(description = "数量", example = "10") + private Long count; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/message/MessageUnreadResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/message/MessageUnreadResp.java new file mode 100644 index 0000000..e02c668 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/message/MessageUnreadResp.java @@ -0,0 +1,36 @@ +package top.ysoft.admin.system.model.resp.message; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 未读消息信息 + * + * @author Charles7c + * @since 2023/11/2 23:00 + */ +@Data +@Schema(description = "未读消息信息") +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class MessageUnreadResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 未读消息数量 + */ + @Schema(description = "未读消息数量", example = "20") + private Long total; + + /** + * 各类型未读消息数量 + */ + @Schema(description = "各类型未读消息数量") + private List details; +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/role/RoleDetailResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/role/RoleDetailResp.java new file mode 100644 index 0000000..bf9f496 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/role/RoleDetailResp.java @@ -0,0 +1,103 @@ +package top.ysoft.admin.system.model.resp.role; + +import cn.crane4j.annotation.AssembleMethod; +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.common.enums.DataScopeEnum; +import top.ysoft.admin.system.service.RoleDeptService; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; + +import java.io.Serial; +import java.util.List; + +/** + * 角色详情信息 + * + * @author Charles7c + * @since 2023/2/1 22:19 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "角色详情信息") +@AssembleMethod(key = "id", prop = ":deptIds", targetType = RoleDeptService.class, method = @ContainerMethod(bindMethod = "listDeptIdByRoleId", type = MappingType.ORDER_OF_KEYS)) +public class RoleDetailResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "测试人员") + @ExcelProperty(value = "名称") + private String name; + + /** + * 编码 + */ + @Schema(description = "编码", example = "test") + @ExcelProperty(value = "编码") + private String code; + + /** + * 数据权限 + */ + @Schema(description = "数据权限", example = "5") + @ExcelProperty(value = "数据权限", converter = ExcelBaseEnumConverter.class) + private DataScopeEnum dataScope; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + @ExcelProperty(value = "排序") + private Integer sort; + + /** + * 是否为系统内置数据 + */ + @Schema(description = "是否为系统内置数据", example = "false") + @ExcelProperty(value = "系统内置") + private Boolean isSystem; + + /** + * 菜单选择是否父子节点关联 + */ + @Schema(description = "菜单选择是否父子节点关联", example = "false") + private Boolean menuCheckStrictly; + + /** + * 部门选择是否父子节点关联 + */ + @Schema(description = "部门选择是否父子节点关联", example = "false") + private Boolean deptCheckStrictly; + + /** + * 描述 + */ + @Schema(description = "描述", example = "测试人员描述信息") + @ExcelProperty(value = "描述") + private String description; + + /** + * 功能权限:菜单 ID 列表 + */ + @Schema(description = "功能权限:菜单 ID 列表", example = "1000,1010,1011,1012,1013,1014") + private List menuIds; + + /** + * 权限范围:部门 ID 列表 + */ + @Schema(description = "权限范围:部门 ID 列表", example = "5") + private List deptIds; + + @Override + public Boolean getDisabled() { + return this.getIsSystem(); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/role/RoleResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/role/RoleResp.java new file mode 100644 index 0000000..eecd514 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/role/RoleResp.java @@ -0,0 +1,58 @@ +package top.ysoft.admin.system.model.resp.role; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.common.enums.DataScopeEnum; + +import java.io.Serial; + +/** + * 角色信息 + * + * @author Charles7c + * @since 2023/2/8 23:05 + */ +@Data +@Schema(description = "角色信息") +public class RoleResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "测试人员") + private String name; + + /** + * 编码 + */ + @Schema(description = "编码", example = "test") + private String code; + + /** + * 数据权限 + */ + @Schema(description = "数据权限", example = "5") + private DataScopeEnum dataScope; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + private Integer sort; + + /** + * 是否为系统内置数据 + */ + @Schema(description = "是否为系统内置数据", example = "false") + private Boolean isSystem; + + /** + * 描述 + */ + @Schema(description = "描述", example = "测试人员描述信息") + private String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/role/RoleUserResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/role/RoleUserResp.java new file mode 100644 index 0000000..54fecd0 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/role/RoleUserResp.java @@ -0,0 +1,113 @@ +package top.ysoft.admin.system.model.resp.role; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.core.executor.handler.ManyToManyAssembleOperationHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.enums.GenderEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * 角色关联用户信息 + * + * @author Charles7c + * @since 2025/2/5 22:01 + */ +@Data +@Schema(description = "角色关联用户信息") +public class RoleUserResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 角色 ID + */ + @Schema(description = "角色 ID", example = "1") + private Long roleId; + + /** + * 用户 ID + */ + @Schema(description = "用户 ID", example = "1") + @Assemble(prop = ":roleIds", sort = 0, container = ContainerConstants.USER_ROLE_ID_LIST) + private Long userId; + + /** + * 用户名 + */ + @Schema(description = "用户名", example = "zhangsan") + private String username; + + /** + * 昵称 + */ + @Schema(description = "昵称", example = "张三") + private String nickname; + + /** + * 性别 + */ + @Schema(description = "性别", example = "1") + private GenderEnum gender; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 是否为系统内置数据 + */ + @Schema(description = "是否为系统内置数据", example = "false") + private Boolean isSystem; + + /** + * 描述 + */ + @Schema(description = "描述", example = "测试人员描述信息") + private String description; + + /** + * 部门 ID + */ + @Schema(description = "部门 ID", example = "5") + private Long deptId; + + /** + * 所属部门 + */ + @Schema(description = "所属部门", example = "测试部") + private String deptName; + + /** + * 角色 ID 列表 + */ + @Schema(description = "角色 ID 列表", example = "2") + @Assemble(prop = ":roleNames", container = ContainerConstants.USER_ROLE_NAME_LIST, handlerType = ManyToManyAssembleOperationHandler.class) + private List roleIds; + + /** + * 角色名称列表 + */ + @Schema(description = "角色名称列表", example = "测试人员") + private List roleNames; + + public Boolean getDisabled() { + return this.getIsSystem() && Objects.equals(roleId, SysConstants.SUPER_ROLE_ID); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserDetailResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserDetailResp.java new file mode 100644 index 0000000..b8f3433 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserDetailResp.java @@ -0,0 +1,156 @@ +package top.ysoft.admin.system.model.resp.user; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.annotation.AssembleMethod; +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.Mapping; +import cn.crane4j.annotation.condition.ConditionOnExpression; +import cn.crane4j.core.executor.handler.ManyToManyAssembleOperationHandler; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.enums.GenderEnum; +import top.ysoft.admin.system.model.resp.DeptResp; +import top.ysoft.admin.system.service.DeptService; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; +import top.continew.starter.file.excel.converter.ExcelListConverter; +import top.continew.starter.security.crypto.annotation.FieldEncrypt; + +import java.io.Serial; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +/** + * 用户详情信息 + * + * @author Charles7c + * @since 2023/2/20 21:11 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "用户详情信息") +@Assemble(key = "id", prop = ":roleIds", sort = 0, container = ContainerConstants.USER_ROLE_ID_LIST) +public class UserDetailResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户名 + */ + @Schema(description = "用户名", example = "zhangsan") + @ExcelProperty(value = "用户名", order = 2) + private String username; + + /** + * 昵称 + */ + @Schema(description = "昵称", example = "张三") + @ExcelProperty(value = "昵称", order = 3) + private String nickname; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + @ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class, order = 4) + private DisEnableStatusEnum status; + + /** + * 性别 + */ + @Schema(description = "性别", example = "1") + @ExcelProperty(value = "性别", converter = ExcelBaseEnumConverter.class, order = 5) + private GenderEnum gender; + + /** + * 部门 ID + */ + @Schema(description = "部门 ID", example = "5") + @ConditionOnExpression("#target.deptName == null") + @AssembleMethod(props = @Mapping(src = "name", ref = "deptName"), targetType = DeptService.class, method = @ContainerMethod(bindMethod = "get", resultType = DeptResp.class)) + @ExcelProperty(value = "部门 ID", order = 6) + private Long deptId; + + /** + * 所属部门 + */ + @Schema(description = "所属部门", example = "测试部") + @ExcelProperty(value = "所属部门", order = 7) + private String deptName; + + /** + * 角色 ID 列表 + */ + @Schema(description = "角色 ID 列表", example = "2") + @Assemble(prop = ":roleNames", container = ContainerConstants.USER_ROLE_NAME_LIST, handlerType = ManyToManyAssembleOperationHandler.class) + @ExcelProperty(value = "角色 ID 列表", converter = ExcelListConverter.class, order = 8) + private List roleIds; + + /** + * 角色名称列表 + */ + @Schema(description = "角色名称列表", example = "测试人员") + @ExcelProperty(value = "角色", converter = ExcelListConverter.class, order = 9) + private List roleNames; + + /** + * 手机号码 + */ + @Schema(description = "手机号码", example = "13811111111") + @ExcelProperty(value = "手机号码", order = 10) + @FieldEncrypt + private String phone; + + /** + * 邮箱 + */ + @Schema(description = "邮箱", example = "123456789@qq.com") + @ExcelProperty(value = "邮箱", order = 11) + @FieldEncrypt + private String email; + + /** + * 是否为系统内置数据 + */ + @Schema(description = "系统内置", example = "false") + @ExcelProperty(value = "系统内置", order = 12) + private Boolean isSystem; + + /** + * 描述 + */ + @Schema(description = "描述", example = "张三描述信息") + @ExcelProperty(value = "描述", order = 13) + private String description; + + /** + * 头像地址 + */ + @Schema(description = "头像地址", example = "https://himg.bdimg.com/sys/portrait/item/public.1.81ac9a9e.rf1ix17UfughLQjNo7XQ_w.jpg") + @ExcelProperty(value = "头像地址", order = 14) + private String avatar; + + /** + * 最后一次修改密码时间 + */ + @Schema(description = "最后一次修改密码时间", example = "2023-08-08 08:08:08", type = "string") + private LocalDateTime pwdResetTime; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + private Long equipmentId; + + @Override + public Boolean getDisabled() { + return this.getIsSystem() || Objects.equals(this.getId(), UserContextHolder.getUserId()); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserImportParseResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserImportParseResp.java new file mode 100644 index 0000000..3b8058a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserImportParseResp.java @@ -0,0 +1,61 @@ +package top.ysoft.admin.system.model.resp.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户导入解析结果 + * + * @author kils + * @since 2024/6/18 14:37 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "用户导入解析结果") +public class UserImportParseResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 导入会话 Key + */ + @Schema(description = "导入会话Key", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed") + private String importKey; + + /** + * 总计行数 + */ + @Schema(description = "总计行数", example = "100") + private Integer totalRows; + + /** + * 有效行数 + */ + @Schema(description = "有效行数", example = "100") + private Integer validRows; + + /** + * 重复行数 + */ + @Schema(description = "重复行数", example = "100") + private Integer duplicateUserRows; + + /** + * 重复邮箱行数 + */ + @Schema(description = "重复邮箱行数", example = "100") + private Integer duplicateEmailRows; + + /** + * 重复手机行数 + */ + @Schema(description = "重复手机行数", example = "100") + private Integer duplicatePhoneRows; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserImportResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserImportResp.java new file mode 100644 index 0000000..a426aa2 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserImportResp.java @@ -0,0 +1,39 @@ +package top.ysoft.admin.system.model.resp.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户导入结果 + * + * @author kils + * @since 2024-06-18 14:37 + */ +@Data +@Schema(description = "用户导入结果") +@AllArgsConstructor +@NoArgsConstructor +public class UserImportResp { + + private static final long serialVersionUID = 1L; + + /** + * 总计行数 + */ + @Schema(description = "总计行数", example = "100") + private Integer totalRows; + + /** + * 新增行数 + */ + @Schema(description = "新增行数", example = "100") + private Integer insertRows; + + /** + * 修改行数 + */ + @Schema(description = "修改行数", example = "100") + private Integer updateRows; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserResp.java new file mode 100644 index 0000000..1db7dd7 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserResp.java @@ -0,0 +1,118 @@ +package top.ysoft.admin.system.model.resp.user; + +import cn.crane4j.annotation.Assemble; +import cn.crane4j.core.executor.handler.ManyToManyAssembleOperationHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.enums.GenderEnum; +import top.continew.starter.security.mask.annotation.JsonMask; +import top.continew.starter.security.mask.enums.MaskType; + +import java.io.Serial; +import java.util.List; +import java.util.Objects; + +/** + * 用户信息 + * + * @author Charles7c + * @since 2023/2/20 21:08 + */ +@Data +@Schema(description = "用户信息") +@Assemble(key = "id", prop = ":roleIds", sort = 0, container = ContainerConstants.USER_ROLE_ID_LIST) +public class UserResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户名 + */ + @Schema(description = "用户名", example = "zhangsan") + private String username; + + /** + * 昵称 + */ + @Schema(description = "昵称", example = "张三") + private String nickname; + + /** + * 性别 + */ + @Schema(description = "性别", example = "1") + private GenderEnum gender; + + /** + * 头像地址 + */ + @Schema(description = "头像地址", example = "https://himg.bdimg.com/sys/portrait/item/public.1.81ac9a9e.rf1ix17UfughLQjNo7XQ_w.jpg") + private String avatar; + + /** + * 邮箱 + */ + @Schema(description = "邮箱", example = "c*******@126.com") + @JsonMask(MaskType.EMAIL) + private String email; + + /** + * 手机号码 + */ + @Schema(description = "手机号码", example = "188****8888") + @JsonMask(MaskType.MOBILE_PHONE) + private String phone; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 是否为系统内置数据 + */ + @Schema(description = "是否为系统内置数据", example = "false") + private Boolean isSystem; + + /** + * 描述 + */ + @Schema(description = "描述", example = "张三描述信息") + private String description; + + /** + * 部门 ID + */ + @Schema(description = "部门 ID", example = "5") + private Long deptId; + + /** + * 所属部门 + */ + @Schema(description = "所属部门", example = "测试部") + private String deptName; + + /** + * 角色 ID 列表 + */ + @Schema(description = "角色 ID 列表", example = "2") + @Assemble(prop = ":roleNames", container = ContainerConstants.USER_ROLE_NAME_LIST, handlerType = ManyToManyAssembleOperationHandler.class) + private List roleIds; + + /** + * 角色名称列表 + */ + @Schema(description = "角色名称列表", example = "测试人员") + private List roleNames; + + @Override + public Boolean getDisabled() { + return this.getIsSystem() || Objects.equals(this.getId(), UserContextHolder.getUserId()); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserSocialBindResp.java b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserSocialBindResp.java new file mode 100644 index 0000000..bb118bb --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/model/resp/user/UserSocialBindResp.java @@ -0,0 +1,33 @@ +package top.ysoft.admin.system.model.resp.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 第三方账号绑定信息 + * + * @author Charles7c + * @since 2023/10/19 21:29 + */ +@Data +@Schema(description = "第三方账号绑定信息") +public class UserSocialBindResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 来源 + */ + @Schema(description = "来源", example = "GITEE") + private String source; + + /** + * 描述 + */ + @Schema(description = "描述", example = "码云") + private String description; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/ClientService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/ClientService.java new file mode 100644 index 0000000..63ed1ca --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/ClientService.java @@ -0,0 +1,24 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.system.model.query.ClientQuery; +import top.ysoft.admin.system.model.req.ClientReq; +import top.ysoft.admin.system.model.resp.ClientResp; +import top.continew.starter.extension.crud.service.BaseService; + +/** + * 终端业务接口 + * + * @author KAI + * @author Charles7c + * @since 2024/12/03 16:04 + */ +public interface ClientService extends BaseService { + + /** + * 根据终端 ID 查詢 + * + * @param clientId 终端 ID + * @return 终端信息 + */ + ClientResp getByClientId(String clientId); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/ConfigService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/ConfigService.java new file mode 100644 index 0000000..a3de6e8 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/ConfigService.java @@ -0,0 +1,22 @@ +package top.ysoft.admin.system.service; + +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.system.model.query.ConfigQuery; +import top.ysoft.admin.system.model.req.ConfigReq; +import top.ysoft.admin.system.model.resp.ConfigResp; + +/** + * 参数配置业务接口 + * + * @author zc + * @since 2025/12/30 12:44 + */ +public interface ConfigService extends BaseService { + /** + * 获取参数配置值 + * + * @param configKey 参数键名 + * @return 参数配置值 + */ + String getConfigValue(String configKey); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/DashboardService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/DashboardService.java new file mode 100644 index 0000000..d25a6d6 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/DashboardService.java @@ -0,0 +1,83 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.system.model.resp.dashboard.DashboardAccessTrendResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardChartCommonResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardNoticeResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardOverviewCommonResp; + +import java.io.IOException; +import java.util.List; + +/** + * 仪表盘业务接口 + * + * @author Charles7c + * @since 2023/9/8 21:32 + */ +public interface DashboardService { + + /** + * 查询公告列表 + * + * @return 公告列表 + */ + List listNotice(); + + /** + * 查询 PV 总览 + * + * @return PV 总览 + */ + DashboardOverviewCommonResp getOverviewPv(); + + /** + * 查询 IP 总览 + * + * @return IP 总览 + */ + DashboardOverviewCommonResp getOverviewIp(); + + /** + * 查询地域分析信息 + * + * @return 地域分析信息 + * @throws IOException / + */ + List getAnalysisGeo() throws IOException; + + /** + * 查询访问趋势信息 + * + * @param days 日期数 + * @return 访问趋势信息 + */ + List listAccessTrend(Integer days); + + /** + * 查询访问时段分析信息 + * + * @return 访问时段分析信息 + */ + List getAnalysisTimeslot(); + + /** + * 查询模块分析信息 + * + * @return 模块分析信息 + */ + List getAnalysisModule(); + + /** + * 查询终端分析信息 + * + * @return 终端分析信息 + */ + List getAnalysisOs(); + + /** + * 查询浏览器分析信息 + * + * @return 浏览器分析信息 + */ + List getAnalysisBrowser(); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/DeptService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/DeptService.java new file mode 100644 index 0000000..f8e25a2 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/DeptService.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.system.model.entity.DeptDO; +import top.ysoft.admin.system.model.query.DeptQuery; +import top.ysoft.admin.system.model.req.DeptReq; +import top.ysoft.admin.system.model.resp.DeptResp; +import top.continew.starter.data.mp.service.IService; +import top.continew.starter.extension.crud.service.BaseService; + +import java.util.List; + +/** + * 部门业务接口 + * + * @author Charles7c + * @since 2023/1/22 17:54 + */ +public interface DeptService extends BaseService, IService { + + /** + * 查询子部门列表 + * + * @param id ID + * @return 子部门列表 + */ + List listChildren(Long id); + + /** + * 通过名称查询部门 + * + * @param list 名称列表 + * @return 部门列表 + */ + List listByNames(List list); + + /** + * 通过名称查询部门数量 + * + * @param deptNames 名称列表 + * @return 部门数量 + */ + int countByNames(List deptNames); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/DictItemService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/DictItemService.java new file mode 100644 index 0000000..4b1e4c1 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/DictItemService.java @@ -0,0 +1,42 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.system.model.entity.DictItemDO; +import top.ysoft.admin.system.model.query.DictItemQuery; +import top.ysoft.admin.system.model.req.DictItemReq; +import top.ysoft.admin.system.model.resp.DictItemResp; +import top.continew.starter.data.mp.service.IService; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.service.BaseService; + +import java.util.List; + +/** + * 字典项业务接口 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +public interface DictItemService extends BaseService, IService { + + /** + * 根据字典编码查询 + * + * @param dictCode 字典编码 + * @return 字典项列表 + */ + List listByDictCode(String dictCode); + + /** + * 根据字典 ID 列表删除 + * + * @param dictIds 字典 ID 列表 + */ + void deleteByDictIds(List dictIds); + + /** + * 查询枚举字典名称列表 + * + * @return 枚举字典名称列表 + */ + List listEnumDictNames(); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/DictService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/DictService.java new file mode 100644 index 0000000..6679cfc --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/DictService.java @@ -0,0 +1,27 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.system.model.entity.DictDO; +import top.ysoft.admin.system.model.query.DictQuery; +import top.ysoft.admin.system.model.req.DictReq; +import top.ysoft.admin.system.model.resp.DictResp; +import top.continew.starter.data.mp.service.IService; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.service.BaseService; + +import java.util.List; + +/** + * 字典业务接口 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +public interface DictService extends BaseService, IService { + + /** + * 查询枚举字典 + * + * @return 枚举字典列表 + */ + List listEnumDict(); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/FileService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/FileService.java new file mode 100644 index 0000000..b60e399 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/FileService.java @@ -0,0 +1,92 @@ +package top.ysoft.admin.system.service; + +import org.dromara.x.file.storage.core.FileInfo; +import org.springframework.web.multipart.MultipartFile; +import top.ysoft.admin.system.model.entity.FileDO; +import top.ysoft.admin.system.model.query.FileQuery; +import top.ysoft.admin.system.model.req.FileReq; +import top.ysoft.admin.system.model.resp.file.FileResp; +import top.ysoft.admin.system.model.resp.file.FileStatisticsResp; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.data.mp.service.IService; +import top.continew.starter.extension.crud.service.BaseService; + +import java.time.LocalDate; +import java.util.List; + +/** + * 文件业务接口 + * + * @author Charles7c + * @since 2023/12/23 10:38 + */ +public interface FileService extends BaseService, IService { + + /** + * 上传到默认存储 + * + * @param file 文件信息 + * @return 文件信息 + */ + default FileInfo upload(MultipartFile file, Boolean createMin) { + return upload(file, getDefaultFilePath(), null, createMin, true); + } + + /** + * 上传到默认存储 + * + * @param file 文件信息 + * @param path 文件路径 + * @return 文件信息 + */ + default FileInfo upload(MultipartFile file, String path) { + return upload(file, path, null, true, true); + } + + /** + * 上传到默认存储 + * + * @param file 文件信息 + * @param storageCode 存储编码 + * @return 文件信息 + */ + default FileInfo upload(MultipartFile file, String storageCode, Boolean createMin, Boolean store) { + return upload(file, getDefaultFilePath(), storageCode, createMin, store); + } + + /** + * 上传到指定存储 + * + * @param file 文件信息 + * @param path 文件路径 + * @param storageCode 存储编码 + * @return 文件信息 + */ + FileInfo upload(MultipartFile file, String path, String storageCode, Boolean createMin, Boolean store); + + /** + * 根据存储 ID 列表查询 + * + * @param storageIds 存储 ID 列表 + * @return 文件数量 + */ + Long countByStorageIds(List storageIds); + + /** + * 查询文件资源统计信息 + * + * @return 资源统计信息 + */ + FileStatisticsResp statistics(); + + /** + * 获取默认文件路径 + * + * @return 默认文件路径 + */ + default String getDefaultFilePath() { + LocalDate today = LocalDate.now(); + return today.getYear() + StringConstants.SLASH + today.getMonthValue() + StringConstants.SLASH + today + .getDayOfMonth() + StringConstants.SLASH; + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/ImportExcelService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/ImportExcelService.java new file mode 100644 index 0000000..3fd783a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/ImportExcelService.java @@ -0,0 +1,19 @@ +package top.ysoft.admin.system.service; + +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public interface ImportExcelService { + + /** + * 下载导入模板 + * + * @param response 响应对象 + * @param fileName 文件名 + * @param fileNameZH 文件名中文 + * @throws IOException / + */ + void downloadImportTemplate(HttpServletResponse response, String fileName, String fileNameZH) throws IOException; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/LogService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/LogService.java new file mode 100644 index 0000000..ff3bde5 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/LogService.java @@ -0,0 +1,53 @@ +package top.ysoft.admin.system.service; + +import jakarta.servlet.http.HttpServletResponse; +import top.ysoft.admin.system.model.query.LogQuery; +import top.ysoft.admin.system.model.resp.log.LogDetailResp; +import top.ysoft.admin.system.model.resp.log.LogResp; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.query.SortQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; + +/** + * 系统日志业务接口 + * + * @author Charles7c + * @since 2022/12/23 20:12 + */ +public interface LogService { + + /** + * 分页查询列表 + * + * @param query 查询条件 + * @param pageQuery 分页查询条件 + * @return 分页列表信息 + */ + PageResp page(LogQuery query, PageQuery pageQuery); + + /** + * 查询详情 + * + * @param id ID + * @return 详情信息 + */ + LogDetailResp get(Long id); + + /** + * 导出登录日志 + * + * @param query 查询条件 + * @param sortQuery 排序查询条件 + * @param response 响应对象 + */ + void exportLoginLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response); + + /** + * 导出操作日志 + * + * @param query 查询条件 + * @param sortQuery 排序查询条件 + * @param response 响应对象 + */ + void exportOperationLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/MenuService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/MenuService.java new file mode 100644 index 0000000..2ca1673 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/MenuService.java @@ -0,0 +1,36 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.system.model.entity.MenuDO; +import top.ysoft.admin.system.model.query.MenuQuery; +import top.ysoft.admin.system.model.req.MenuReq; +import top.ysoft.admin.system.model.resp.MenuResp; +import top.continew.starter.data.mp.service.IService; +import top.continew.starter.extension.crud.service.BaseService; + +import java.util.List; +import java.util.Set; + +/** + * 菜单业务接口 + * + * @author Charles7c + * @since 2023/2/15 20:30 + */ +public interface MenuService extends BaseService, IService { + + /** + * 根据用户 ID 查询 + * + * @param userId 用户 ID + * @return 权限码集合 + */ + Set listPermissionByUserId(Long userId); + + /** + * 根据角色 ID 查询 + * + * @param roleId 角色 ID + * @return 菜单列表 + */ + List listByRoleId(Long roleId); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/MessageService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/MessageService.java new file mode 100644 index 0000000..f61690d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/MessageService.java @@ -0,0 +1,42 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.system.model.query.MessageQuery; +import top.ysoft.admin.system.model.req.MessageReq; +import top.ysoft.admin.system.model.resp.message.MessageResp; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.util.List; + +/** + * 消息业务接口 + * + * @author Bull-BCLS + * @since 2023/10/15 19:05 + */ +public interface MessageService { + + /** + * 分页查询列表 + * + * @param query 查询条件 + * @param pageQuery 分页查询条件 + * @return 分页列表信息 + */ + PageResp page(MessageQuery query, PageQuery pageQuery); + + /** + * 新增 + * + * @param req 新增信息 + * @param userIdList 接收人列表 + */ + void add(MessageReq req, List userIdList); + + /** + * 删除 + * + * @param ids ID 列表 + */ + void delete(List ids); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/MessageUserService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/MessageUserService.java new file mode 100644 index 0000000..04f54ad --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/MessageUserService.java @@ -0,0 +1,45 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.system.model.resp.message.MessageUnreadResp; + +import java.util.List; + +/** + * 消息和用户关联业务接口 + * + * @author Bull-BCLS + * @since 2023/10/15 19:05 + */ +public interface MessageUserService { + + /** + * 根据用户 ID 查询未读消息数量 + * + * @param userId 用户 ID + * @param isDetail 是否查询详情 + * @return 未读消息信息 + */ + MessageUnreadResp countUnreadMessageByUserId(Long userId, Boolean isDetail); + + /** + * 新增 + * + * @param messageId 消息 ID + * @param userIdList 用户 ID 列表 + */ + void add(Long messageId, List userIdList); + + /** + * 将消息标记已读 + * + * @param ids 消息ID(为空则将所有消息标记已读) + */ + void readMessage(List ids); + + /** + * 根据消息 ID 删除 + * + * @param messageIds 消息 ID 列表 + */ + void deleteByMessageIds(List messageIds); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/NoticeService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/NoticeService.java new file mode 100644 index 0000000..a88c43c --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/NoticeService.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.system.model.entity.NoticeDO; +import top.ysoft.admin.system.model.query.NoticeQuery; +import top.ysoft.admin.system.model.req.NoticeReq; +import top.ysoft.admin.system.model.resp.NoticeDetailResp; +import top.ysoft.admin.system.model.resp.NoticeResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardNoticeResp; +import top.continew.starter.data.mp.service.IService; +import top.continew.starter.extension.crud.service.BaseService; + +import java.util.List; + +/** + * 公告业务接口 + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +public interface NoticeService extends BaseService, IService { + + /** + * 查询仪表盘公告列表 + * + * @return 仪表盘公告列表 + */ + List listDashboard(); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/OptionService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/OptionService.java new file mode 100644 index 0000000..436d184 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/OptionService.java @@ -0,0 +1,67 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.system.enums.OptionCategoryEnum; +import top.ysoft.admin.system.model.query.OptionQuery; +import top.ysoft.admin.system.model.req.OptionReq; +import top.ysoft.admin.system.model.req.OptionResetValueReq; +import top.ysoft.admin.system.model.resp.OptionResp; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * 参数业务接口 + * + * @author Bull-BCLS + * @since 2023/8/26 19:38 + */ +public interface OptionService { + + /** + * 查询列表 + * + * @param query 查询条件 + * @return 列表信息 + */ + List list(OptionQuery query); + + /** + * 根据类别查询 + * + * @param category 类别 + * @return 参数信息 + */ + Map getByCategory(OptionCategoryEnum category); + + /** + * 修改参数 + * + * @param options 参数列表 + */ + void update(List options); + + /** + * 重置参数 + * + * @param req 重置信息 + */ + void resetValue(OptionResetValueReq req); + + /** + * 根据编码查询参数值 + * + * @param code 编码 + * @return 参数值(自动转换为 int 类型) + */ + int getValueByCode2Int(String code); + + /** + * 根据编码查询参数值 + * + * @param code 编码 + * @param mapper 转换方法 e.g.:value -> Integer.parseInt(value) + * @return 参数值 + */ + T getValueByCode(String code, Function mapper); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/PeopleEquipmentService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/PeopleEquipmentService.java new file mode 100644 index 0000000..9f1a703 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/PeopleEquipmentService.java @@ -0,0 +1,14 @@ +package top.ysoft.admin.system.service; + +import top.continew.starter.extension.crud.service.BaseService; +import top.ysoft.admin.system.model.query.PeopleEquipmentQuery; +import top.ysoft.admin.system.model.req.PeopleEquipmentReq; +import top.ysoft.admin.system.model.resp.PeopleEquipmentResp; + +/** + * 人员设备下发信息业务接口 + * + * @author zc + * @since 2025/03/27 14:59 + */ +public interface PeopleEquipmentService extends BaseService {} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/RoleDeptService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/RoleDeptService.java new file mode 100644 index 0000000..0d8b838 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/RoleDeptService.java @@ -0,0 +1,43 @@ +package top.ysoft.admin.system.service; + +import java.util.List; + +/** + * 角色和部门业务接口 + * + * @author Charles7c + * @since 2023/2/19 10:40 + */ +public interface RoleDeptService { + + /** + * 新增 + * + * @param deptIds 部门 ID 列表 + * @param roleId 角色 ID + * @return 是否新增成功(true:成功;false:无变更/失败) + */ + boolean add(List deptIds, Long roleId); + + /** + * 根据角色 ID 删除 + * + * @param roleIds 角色 ID 列表 + */ + void deleteByRoleIds(List roleIds); + + /** + * 根据部门 ID 删除 + * + * @param deptIds 部门 ID 列表 + */ + void deleteByDeptIds(List deptIds); + + /** + * 根据角色 ID 查询 + * + * @param roleId 角色 ID + * @return 部门 ID 列表 + */ + List listDeptIdByRoleId(Long roleId); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/RoleMenuService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/RoleMenuService.java new file mode 100644 index 0000000..5543afa --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/RoleMenuService.java @@ -0,0 +1,36 @@ +package top.ysoft.admin.system.service; + +import java.util.List; + +/** + * 角色和菜单业务接口 + * + * @author Charles7c + * @since 2023/2/19 10:40 + */ +public interface RoleMenuService { + + /** + * 新增 + * + * @param menuIds 菜单 ID 列表 + * @param roleId 角色 ID + * @return 是否新增成功(true:成功;false:无变更/失败) + */ + boolean add(List menuIds, Long roleId); + + /** + * 根据角色 ID 删除 + * + * @param roleIds 角色 ID 列表 + */ + void deleteByRoleIds(List roleIds); + + /** + * 根据角色 ID 查询 + * + * @param roleIds 角色 ID 列表 + * @return 菜单 ID 列表 + */ + List listMenuIdByRoleIds(List roleIds); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/RoleService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/RoleService.java new file mode 100644 index 0000000..14796cb --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/RoleService.java @@ -0,0 +1,95 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.common.context.RoleContext; +import top.ysoft.admin.system.model.entity.RoleDO; +import top.ysoft.admin.system.model.query.RoleQuery; +import top.ysoft.admin.system.model.req.RoleReq; +import top.ysoft.admin.system.model.req.RoleUpdatePermissionReq; +import top.ysoft.admin.system.model.resp.role.RoleDetailResp; +import top.ysoft.admin.system.model.resp.role.RoleResp; +import top.continew.starter.data.mp.service.IService; +import top.continew.starter.extension.crud.service.BaseService; + +import java.util.List; +import java.util.Set; + +/** + * 角色业务接口 + * + * @author Charles7c + * @since 2023/2/8 23:15 + */ +public interface RoleService extends BaseService, IService { + + /** + * 修改角色权限 + * + * @param id 角色 ID + * @param req 参数 + */ + void updatePermission(Long id, RoleUpdatePermissionReq req); + + /** + * 分配角色给用户 + * + * @param id 角色 ID + * @param userIds 用户 ID 列表 + */ + void assignToUsers(Long id, List userIds); + + /** + * 根据用户 ID 查询权限码 + * + * @param userId 用户 ID + * @return 权限码集合 + */ + Set listPermissionByUserId(Long userId); + + /** + * 根据 ID 列表查询 + * + * @param ids ID 列表 + * @return 名称列表 + */ + List listNameByIds(List ids); + + /** + * 根据用户 ID 查询角色编码 + * + * @param userId 用户 ID + * @return 角色编码集合 + */ + Set listCodeByUserId(Long userId); + + /** + * 根据用户 ID 查询角色 + * + * @param userId 用户 ID + * @return 角色集合 + */ + Set listByUserId(Long userId); + + /** + * 根据角色编码查询 + * + * @param code 角色编码 + * @return 角色信息 + */ + RoleDO getByCode(String code); + + /** + * 根据角色名称查询 + * + * @param list 名称列表 + * @return 角色列表 + */ + List listByNames(List list); + + /** + * 根据角色名称查询数量 + * + * @param roleNames 名称列表 + * @return 角色数量 + */ + int countByNames(List roleNames); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/StorageService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/StorageService.java new file mode 100644 index 0000000..edca157 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/StorageService.java @@ -0,0 +1,62 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.common.model.req.CommonStatusUpdateReq; +import top.ysoft.admin.system.model.entity.StorageDO; +import top.ysoft.admin.system.model.query.StorageQuery; +import top.ysoft.admin.system.model.req.StorageReq; +import top.ysoft.admin.system.model.resp.StorageResp; +import top.continew.starter.data.mp.service.IService; +import top.continew.starter.extension.crud.service.BaseService; + +/** + * 存储业务接口 + * + * @author Charles7c + * @since 2023/12/26 22:09 + */ +public interface StorageService extends BaseService, IService { + + /** + * 修改状态 + * + * @param req 状态信息 + * @param id ID + */ + void updateStatus(CommonStatusUpdateReq req, Long id); + + /** + * 设置默认存储 + * + * @param id ID + */ + void setDefault(Long id); + + /** + * 查询默认存储 + * + * @return 存储信息 + */ + StorageDO getDefaultStorage(); + + /** + * 根据编码查询 + * + * @param code 编码 + * @return 存储信息 + */ + StorageDO getByCode(String code); + + /** + * 加载存储 + * + * @param req 存储信息 + */ + void load(StorageReq req); + + /** + * 卸载存储 + * + * @param req 存储信息 + */ + void unload(StorageReq req); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/UserPasswordHistoryService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/UserPasswordHistoryService.java new file mode 100644 index 0000000..06e7b10 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/UserPasswordHistoryService.java @@ -0,0 +1,38 @@ +package top.ysoft.admin.system.service; + +import java.util.List; + +/** + * 用户历史密码业务接口 + * + * @author Charles7c + * @since 2024/5/16 21:58 + */ +public interface UserPasswordHistoryService { + + /** + * 新增 + * + * @param userId 用户 ID + * @param password 密码 + * @param count 保留 N 个历史 + */ + void add(Long userId, String password, int count); + + /** + * 根据用户 ID 删除 + * + * @param userIds 用户 ID 列表 + */ + void deleteByUserIds(List userIds); + + /** + * 密码是否为重复使用 + * + * @param userId 用户 ID + * @param password 密码 + * @param count 最近 N 次 + * @return 是否为重复使用 + */ + boolean isPasswordReused(Long userId, String password, int count); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/UserRoleService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/UserRoleService.java new file mode 100644 index 0000000..f76ae94 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/UserRoleService.java @@ -0,0 +1,90 @@ +package top.ysoft.admin.system.service; + +import top.ysoft.admin.system.model.entity.UserRoleDO; +import top.ysoft.admin.system.model.query.RoleUserQuery; +import top.ysoft.admin.system.model.resp.role.RoleUserResp; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.util.List; + +/** + * 用户和角色业务接口 + * + * @author Charles7c + * @since 2023/2/20 21:30 + */ +public interface UserRoleService { + + /** + * 分页查询角色关联用户列表 + * + * @param query 查询条件 + * @param pageQuery 分页查询条件 + * @return 分页信息 + */ + PageResp pageUser(RoleUserQuery query, PageQuery pageQuery); + + /** + * 批量分配角色给指定用户 + * + * @param roleIds 角色 ID 列表 + * @param userId 用户 ID + * @return 是否成功(true:成功;false:无变更/失败) + */ + boolean assignRolesToUser(List roleIds, Long userId); + + /** + * 批量分配角色给用户 + * + * @param roleId 角色 ID + * @param userIds 用户 ID 列表 + * @return 是否成功(true:成功;false:无变更/失败) + */ + boolean assignRoleToUsers(Long roleId, List userIds); + + /** + * 根据 ID 删除 + * + * @param ids ID 列表 + */ + void deleteByIds(List ids); + + /** + * 根据用户 ID 删除 + * + * @param userIds 用户 ID 列表 + */ + void deleteByUserIds(List userIds); + + /** + * 批量插入 + * + * @param list 数据集 + */ + void saveBatch(List list); + + /** + * 根据用户 ID 查询 + * + * @param userId 用户 ID + * @return 角色 ID 列表 + */ + List listRoleIdByUserId(Long userId); + + /** + * 根据角色 ID 查询 + * + * @param roleId 角色 ID + * @return 用户 ID 列表 + */ + List listUserIdByRoleId(Long roleId); + + /** + * 根据角色 ID 判断是否已被用户关联 + * + * @param roleIds 角色 ID 列表 + * @return 是否已关联(true:已关联;false:未关联) + */ + boolean isRoleIdExists(List roleIds); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/UserService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/UserService.java new file mode 100644 index 0000000..9f91962 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/UserService.java @@ -0,0 +1,146 @@ +package top.ysoft.admin.system.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.multipart.MultipartFile; +import top.ysoft.admin.system.model.entity.UserDO; +import top.ysoft.admin.system.model.query.UserQuery; +import top.ysoft.admin.system.model.req.user.*; +import top.ysoft.admin.system.model.resp.user.UserDetailResp; +import top.ysoft.admin.system.model.resp.user.UserImportParseResp; +import top.ysoft.admin.system.model.resp.user.UserImportResp; +import top.ysoft.admin.system.model.resp.user.UserResp; +import top.continew.starter.data.mp.service.IService; +import top.continew.starter.extension.crud.service.BaseService; + +import java.io.IOException; +import java.util.List; + +/** + * 用户业务接口 + * + * @author Charles7c + * @since 2022/12/21 21:48 + */ +public interface UserService extends BaseService, IService { + + /** + * 下载导入模板 + * + * @param response 响应对象 + * @throws IOException / + */ + void downloadImportTemplate(HttpServletResponse response) throws IOException; + + /** + * 解析导入数据 + * + * @param file 导入文件 + * @return 解析结果 + */ + UserImportParseResp parseImport(MultipartFile file); + + /** + * 导入数据 + * + * @param req 导入信息 + * @return 导入结果 + */ + UserImportResp importUser(UserImportReq req); + + /** + * 重置密码 + * + * @param req 重置信息 + * @param id ID + */ + void resetPassword(UserPasswordResetReq req, Long id); + + /** + * 修改角色 + * + * @param updateReq 修改信息 + * @param id ID + */ + void updateRole(UserRoleUpdateReq updateReq, Long id); + + /** + * 上传头像 + * + * @param avatar 头像文件 + * @param id ID + * @return 新头像路径 + * @throws IOException / + */ + String updateAvatar(MultipartFile avatar, Long id) throws IOException; + + /** + * 修改基础信息 + * + * @param req 修改信息 + * @param id ID + */ + void updateBasicInfo(UserBasicInfoUpdateReq req, Long id); + + /** + * 修改密码 + * + * @param oldPassword 当前密码 + * @param newPassword 新密码 + * @param id ID + */ + void updatePassword(String oldPassword, String newPassword, Long id); + + /** + * 修改手机号 + * + * @param newPhone 新手机号 + * @param oldPassword 当前密码 + * @param id ID + */ + void updatePhone(String newPhone, String oldPassword, Long id); + + /** + * 修改邮箱 + * + * @param newEmail 新邮箱 + * @param oldPassword 当前密码 + * @param id ID + */ + void updateEmail(String newEmail, String oldPassword, Long id); + + /** + * 根据用户名查询 + * + * @param username 用户名 + * @return 用户信息 + */ + UserDO getByUsername(String username); + + /** + * 根据手机号查询 + * + * @param phone 手机号 + * @return 用户信息 + */ + UserDO getByPhone(String phone); + + /** + * 根据邮箱查询 + * + * @param email 邮箱 + * @return 用户信息 + */ + UserDO getByEmail(String email); + + /** + * 根据部门 ID 列表查询 + * + * @param deptIds 部门 ID 列表 + * @return 用户数量 + */ + Long countByDeptIds(List deptIds); + + List listNameByIds(List ids); + + String userNameByIds(Long id); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/UserSocialService.java b/wms-module-system/src/main/java/top/wms/admin/system/service/UserSocialService.java new file mode 100644 index 0000000..6d0cf9d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/UserSocialService.java @@ -0,0 +1,55 @@ +package top.ysoft.admin.system.service; + +import me.zhyd.oauth.model.AuthUser; +import top.ysoft.admin.system.model.entity.UserSocialDO; + +import java.util.List; + +/** + * 用户社会化关联业务接口 + * + * @author Charles7c + * @since 2023/10/11 22:10 + */ +public interface UserSocialService { + + /** + * 根据来源和开放 ID 查询 + * + * @param source 来源 + * @param openId 开放 ID + * @return 用户社会化关联信息 + */ + UserSocialDO getBySourceAndOpenId(String source, String openId); + + /** + * 保存 + * + * @param userSocial 用户社会化关联信息 + */ + void saveOrUpdate(UserSocialDO userSocial); + + /** + * 根据用户 ID 查询 + * + * @param userId 用户 ID + * @return 用户社会化关联信息 + */ + List listByUserId(Long userId); + + /** + * 绑定 + * + * @param authUser 三方账号信息 + * @param userId 用户 ID + */ + void bind(AuthUser authUser, Long userId); + + /** + * 根据来源和用户 ID 删除 + * + * @param source 来源 + * @param userId 用户 ID + */ + void deleteBySourceAndUserId(String source, Long userId); +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/ClientServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/ClientServiceImpl.java new file mode 100644 index 0000000..8fbb375 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/ClientServiceImpl.java @@ -0,0 +1,59 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.crypto.digest.DigestUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.auth.model.query.OnlineUserQuery; +import top.ysoft.admin.auth.service.OnlineUserService; +import top.ysoft.admin.system.mapper.ClientMapper; +import top.ysoft.admin.system.model.entity.ClientDO; +import top.ysoft.admin.system.model.query.ClientQuery; +import top.ysoft.admin.system.model.req.ClientReq; +import top.ysoft.admin.system.model.resp.ClientResp; +import top.ysoft.admin.system.service.ClientService; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.extension.crud.service.BaseServiceImpl; + +import java.util.List; + +/** + * 终端业务实现 + * + * @author KAI + * @author Charles7c + * @since 2024/12/03 16:04 + */ +@Service +@RequiredArgsConstructor +public class ClientServiceImpl extends BaseServiceImpl implements ClientService { + + private final OnlineUserService onlineUserService; + + @Override + public void beforeAdd(ClientReq req) { + String clientId = DigestUtil.md5Hex(req.getClientKey() + StringConstants.COLON + req.getClientSecret()); + req.setClientId(clientId); + } + + @Override + public void beforeDelete(List ids) { + // 如果还存在在线用户,则不能删除 + OnlineUserQuery query = new OnlineUserQuery(); + for (Long id : ids) { + ClientDO client = this.getById(id); + query.setClientId(client.getClientId()); + CheckUtils.throwIfNotEmpty(onlineUserService.list(query), "终端 [{}] 还存在在线用户,不能删除", client.getClientKey()); + } + } + + @Override + public ClientResp getByClientId(String clientId) { + return baseMapper.lambdaQuery() + .eq(ClientDO::getClientId, clientId) + .oneOpt() + .map(client -> BeanUtil.copyProperties(client, ClientResp.class)) + .orElse(null); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/ConfigServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/ConfigServiceImpl.java new file mode 100644 index 0000000..a5870de --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/ConfigServiceImpl.java @@ -0,0 +1,63 @@ +package top.ysoft.admin.system.service.impl; + +import com.alicp.jetcache.anno.Cached; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.system.mapper.ConfigMapper; +import top.ysoft.admin.system.model.entity.ConfigDO; +import top.ysoft.admin.system.model.query.ConfigQuery; +import top.ysoft.admin.system.model.req.ConfigReq; +import top.ysoft.admin.system.model.resp.ConfigResp; +import top.ysoft.admin.system.service.ConfigService; + +import java.time.Duration; +import java.util.List; + +/** + * 参数配置业务实现 + * + * @author zc + * @since 2025/12/30 12:44 + */ +@Service +@RequiredArgsConstructor +public class ConfigServiceImpl extends BaseServiceImpl implements ConfigService { + + @Override + @Transactional(rollbackFor = Exception.class) + public void afterAdd(ConfigReq configReq, ConfigDO configDO) { + RedisUtils.set(CacheConstants.SYS_CONFIG_KEY + configDO.getConfigKey(), configDO.getConfigValue(), Duration + .ofDays(30)); + } + + @Override + @Cached(name = CacheConstants.SYS_CONFIG_KEY, key = "#configKey") + public String getConfigValue(String configKey) { + ConfigDO configDO = baseMapper.selectOne(new LambdaQueryWrapper() + .eq(ConfigDO::getConfigKey, configKey)); + return configDO.getConfigValue(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void afterUpdate(ConfigReq configReq, ConfigDO configDO) { + // 先删除缓存 + RedisUtils.delete(CacheConstants.SYS_CONFIG_KEY + configReq.getConfigKey()); + // 再设置缓存 + RedisUtils.set(CacheConstants.SYS_CONFIG_KEY + configDO.getConfigKey(), configDO.getConfigValue(), Duration + .ofDays(30)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void beforeDelete(List Ids) { + List configDOS = baseMapper.selectByIds(Ids); + configDOS.forEach(config -> RedisUtils.delete(CacheConstants.SYS_CONFIG_KEY + config.getConfigKey())); + } + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DashboardServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DashboardServiceImpl.java new file mode 100644 index 0000000..4e5ab7c --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DashboardServiceImpl.java @@ -0,0 +1,207 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; +import top.ysoft.admin.system.mapper.LogMapper; +import top.ysoft.admin.system.model.resp.dashboard.DashboardAccessTrendResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardChartCommonResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardNoticeResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardOverviewCommonResp; +import top.ysoft.admin.system.service.DashboardService; +import top.ysoft.admin.system.service.NoticeService; +import top.continew.starter.core.constant.StringConstants; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.*; + +/** + * 仪表盘业务实现 + * + * @author Charles7c + * @since 2023/9/8 21:32 + */ +@Service +@RequiredArgsConstructor +public class DashboardServiceImpl implements DashboardService { + + private final LogMapper logMapper; + private final NoticeService noticeService; + + @Override + public List listNotice() { + return noticeService.listDashboard(); + } + + @Override + public DashboardOverviewCommonResp getOverviewPv() { + DashboardOverviewCommonResp resp = logMapper.selectDashboardOverviewPv(); + resp.setGrowth(this.calcGrowthFromYesterday(resp.getToday(), resp.getYesterday())); + List last12MonthList = this.getLast12Months(); + List dataList = logMapper.selectListDashboardAnalysisPv(last12MonthList); + if (dataList.size() < 12) { + // 填充缺失的数据 + this.fillMissingDateData(last12MonthList, dataList); + } + resp.setDataList(dataList); + return resp; + } + + @Override + public DashboardOverviewCommonResp getOverviewIp() { + DashboardOverviewCommonResp resp = logMapper.selectDashboardOverviewIp(); + resp.setGrowth(this.calcGrowthFromYesterday(resp.getToday(), resp.getYesterday())); + List last12MonthList = this.getLast12Months(); + List dataList = logMapper.selectListDashboardAnalysisIp(last12MonthList); + if (dataList.size() < 12) { + // 填充缺失的数据 + this.fillMissingDateData(last12MonthList, dataList); + } + resp.setDataList(dataList); + return resp; + } + + @Override + public List getAnalysisGeo() throws IOException { + List originList = logMapper.selectListDashboardAnalysisGeo(); + List list = new ArrayList<>(34); + // 获取省份数据 + String chinaJson = IoUtil.readUtf8(new ClassPathResource("china.json").getInputStream()); + JSONArray jsonArr = JSONUtil.parseObj(chinaJson).getJSONArray("children"); + List provinceList = jsonArr.stream().map(item -> { + JSONObject itemJsonObj = JSONUtil.parseObj(item); + return "%s:%s".formatted(itemJsonObj.getStr("name"), itemJsonObj.getStr("fullname")); + }).toList(); + // 汇总各省份访问数据 + for (String province : provinceList) { + String[] split = province.split(StringConstants.COLON); + String name = split[0]; + String fullName = split[1]; + long sum = originList.stream() + .filter(item -> item.getName().contains(name)) + .mapToLong(DashboardChartCommonResp::getValue) + .sum(); + list.add(new DashboardChartCommonResp(fullName, sum)); + } + return list; + } + + @Override + public List listAccessTrend(Integer days) { + DateTime currentDate = DateUtil.date(); + Date startTime = DateUtil.beginOfDay(DateUtil.offsetDay(currentDate, -days)).toJdkDate(); + Date endTime = DateUtil.endOfDay(DateUtil.offsetDay(currentDate, -1)).toJdkDate(); + List list = logMapper.selectListDashboardAccessTrend(startTime, endTime); + if (list.size() < days) { + List all = DateUtil.rangeToList(startTime, endTime, DateField.DAY_OF_MONTH) + .stream() + .map(date -> date.toString(DatePattern.NORM_DATE_FORMAT)) + .toList(); + Collection missings = CollUtil.disjunction(all, list.stream() + .map(DashboardAccessTrendResp::getDate) + .toList()); + list.addAll(missings.stream().map(missing -> new DashboardAccessTrendResp(missing, 0L, 0L)).toList()); + list.sort(Comparator.comparing(DashboardAccessTrendResp::getDate)); + } + return list; + } + + @Override + public List getAnalysisTimeslot() { + List list = logMapper.selectListDashboardAnalysisTimeslot(); + if (list.size() < 12) { + // 获取所有时间段 + List allTimeSlotList = new ArrayList<>(12); + for (int hour = 0; hour < 24; hour += 2) { + allTimeSlotList.add(String.format("%02d:00", hour)); + } + // 填充缺失的数据 + this.fillMissingDateData(allTimeSlotList, list); + } + return list; + } + + @Override + public List getAnalysisModule() { + return logMapper.selectListDashboardAnalysisModule(10); + } + + @Override + public List getAnalysisOs() { + List list = logMapper.selectListDashboardAnalysisOs(4); + return this.buildOtherPieChartData(list); + } + + @Override + public List getAnalysisBrowser() { + List list = logMapper.selectListDashboardAnalysisBrowser(4); + return this.buildOtherPieChartData(list); + } + + /** + * 计算增长百分比 + * + * @param today 今日数量 + * @param yesterday 昨日数量 + * @return 增长百分比 + */ + private BigDecimal calcGrowthFromYesterday(Long today, Long yesterday) { + return (0 == yesterday) + ? BigDecimal.valueOf(100) + : NumberUtil.round(NumberUtil.mul(NumberUtil.div(NumberUtil.sub(today, yesterday), yesterday), 100), 1); + } + + /** + * 构建其他饼图数据 + * + * @param list 饼图数据列表 + * @return 饼图数据列表 + */ + private List buildOtherPieChartData(List list) { + Long totalCount = logMapper.selectTotalCount(); + long sumCount = list.stream().mapToLong(DashboardChartCommonResp::getValue).sum(); + if (sumCount < totalCount) { + list.add(new DashboardChartCommonResp("其他", totalCount - sumCount)); + } + return list; + } + + /** + * 填充缺失时间段的数据 + * + * @param all 所有时间段 + * @param list 待填充数据 + */ + private void fillMissingDateData(List all, List list) { + Collection missings = CollUtil.disjunction(all, list.stream() + .map(DashboardChartCommonResp::getName) + .toList()); + list.addAll(missings.stream().map(missing -> new DashboardChartCommonResp(missing, 0L)).toList()); + list.sort(Comparator.comparing(DashboardChartCommonResp::getName)); + } + + /** + * 获取最近12个月的月份列表 + * + * @return 月份列表 + */ + private List getLast12Months() { + DateTime currentMonth = DateUtil.beginOfMonth(DateUtil.date()); + return DateUtil.rangeToList(DateUtil.offsetMonth(currentMonth, -12), DateUtil + .offsetMonth(currentMonth, -1), DateField.MONTH) + .stream() + .map(dateTime -> DateUtil.format(dateTime, DatePattern.NORM_MONTH_FORMAT)) + .toList(); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DeptServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DeptServiceImpl.java new file mode 100644 index 0000000..5f90bf9 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DeptServiceImpl.java @@ -0,0 +1,201 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import jakarta.annotation.Resource; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.mapper.DeptMapper; +import top.ysoft.admin.system.model.entity.DeptDO; +import top.ysoft.admin.system.model.query.DeptQuery; +import top.ysoft.admin.system.model.req.DeptReq; +import top.ysoft.admin.system.model.resp.DeptResp; +import top.ysoft.admin.system.service.DeptService; +import top.ysoft.admin.system.service.RoleDeptService; +import top.ysoft.admin.system.service.UserService; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.data.core.enums.DatabaseType; +import top.continew.starter.data.core.util.MetaUtils; +import top.continew.starter.extension.crud.service.BaseServiceImpl; + +import javax.sql.DataSource; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * 部门业务实现 + * + * @author Charles7c + * @since 2023/1/22 17:55 + */ +@Service +@RequiredArgsConstructor +public class DeptServiceImpl extends BaseServiceImpl implements DeptService { + + private final RoleDeptService roleDeptService; + @Resource + private UserService userService; + @Resource + private DataSource dataSource; + + @Override + public void beforeAdd(DeptReq req) { + String name = req.getName(); + boolean isExists = this.isNameExists(name, req.getParentId(), null); + CheckUtils.throwIf(isExists, "新增失败,[{}] 已存在", name); + req.setAncestors(this.getAncestors(req.getParentId())); + } + + @Override + public void beforeUpdate(DeptReq req, Long id) { + String name = req.getName(); + boolean isExists = this.isNameExists(name, req.getParentId(), id); + CheckUtils.throwIf(isExists, "修改失败,[{}] 已存在", name); + DeptDO oldDept = super.getById(id); + String oldName = oldDept.getName(); + DisEnableStatusEnum newStatus = req.getStatus(); + Long oldParentId = oldDept.getParentId(); + if (Boolean.TRUE.equals(oldDept.getIsSystem())) { + CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, newStatus, "[{}] 是系统内置部门,不允许禁用", oldName); + CheckUtils.throwIfNotEqual(req.getParentId(), oldParentId, "[{}] 是系统内置部门,不允许变更上级部门", oldName); + } + // 启用/禁用部门 + if (ObjectUtil.notEqual(newStatus, oldDept.getStatus())) { + List children = this.listChildren(id); + long enabledChildrenCount = children.stream() + .filter(d -> DisEnableStatusEnum.ENABLE.equals(d.getStatus())) + .count(); + CheckUtils.throwIf(DisEnableStatusEnum.DISABLE + .equals(newStatus) && enabledChildrenCount > 0, "禁用 [{}] 前,请先禁用其所有下级部门", oldName); + DeptDO oldParentDept = this.getByParentId(oldParentId); + CheckUtils.throwIf(DisEnableStatusEnum.ENABLE.equals(newStatus) && DisEnableStatusEnum.DISABLE + .equals(oldParentDept.getStatus()), "启用 [{}] 前,请先启用其所有上级部门", oldName); + } + // 变更上级部门 + if (ObjectUtil.notEqual(req.getParentId(), oldParentId)) { + // 更新祖级列表 + String newAncestors = this.getAncestors(req.getParentId()); + req.setAncestors(newAncestors); + // 更新子级的祖级列表 + this.updateChildrenAncestors(newAncestors, oldDept.getAncestors(), id); + } + } + + @Override + public void beforeDelete(List ids) { + List list = baseMapper.lambdaQuery() + .select(DeptDO::getName, DeptDO::getIsSystem) + .in(DeptDO::getId, ids) + .list(); + Optional isSystemData = list.stream().filter(DeptDO::getIsSystem).findFirst(); + CheckUtils.throwIf(isSystemData::isPresent, "所选部门 [{}] 是系统内置部门,不允许删除", isSystemData.orElseGet(DeptDO::new) + .getName()); + CheckUtils.throwIf(this.countChildren(ids) > 0, "所选部门存在下级部门,不允许删除"); + CheckUtils.throwIf(userService.countByDeptIds(ids) > 0, "所选部门存在用户关联,请解除关联后重试"); + // 删除角色和部门关联 + roleDeptService.deleteByDeptIds(ids); + } + + @Override + public List listChildren(Long id) { + DatabaseType databaseType = MetaUtils.getDatabaseTypeOrDefault(dataSource, DatabaseType.MYSQL); + return baseMapper.lambdaQuery().apply(databaseType.findInSet(id, "ancestors")).list(); + } + + @Override + public List listByNames(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + return this.list(Wrappers.lambdaQuery().in(DeptDO::getName, list)); + } + + @Override + public int countByNames(List deptNames) { + if (CollUtil.isEmpty(deptNames)) { + return 0; + } + return (int)this.count(Wrappers.lambdaQuery().in(DeptDO::getName, deptNames)); + } + + /** + * 名称是否存在 + * + * @param name 名称 + * @param parentId 上级 ID + * @param id ID + * @return 是否存在 + */ + private boolean isNameExists(String name, Long parentId, Long id) { + return baseMapper.lambdaQuery() + .eq(DeptDO::getName, name) + .eq(DeptDO::getParentId, parentId) + .ne(null != id, DeptDO::getId, id) + .exists(); + } + + /** + * 获取祖级列表 + * + * @param parentId 上级部门 + * @return 祖级列表 + */ + private String getAncestors(Long parentId) { + DeptDO parentDept = this.getByParentId(parentId); + return "%s,%s".formatted(parentDept.getAncestors(), parentId); + } + + /** + * 根据上级部门 ID 查询 + * + * @param parentId 上级部门 ID + * @return 上级部门信息 + */ + private DeptDO getByParentId(Long parentId) { + DeptDO parentDept = baseMapper.selectById(parentId); + CheckUtils.throwIfNull(parentDept, "上级部门不存在"); + return parentDept; + } + + /** + * 查询子部门数量 + * + * @param ids ID 列表 + * @return 子部门数量 + */ + private Long countChildren(List ids) { + if (CollUtil.isEmpty(ids)) { + return 0L; + } + DatabaseType databaseType = MetaUtils.getDatabaseTypeOrDefault(dataSource, DatabaseType.MYSQL); + return ids.stream() + .mapToLong(id -> baseMapper.lambdaQuery().apply(databaseType.findInSet(id, "ancestors")).count()) + .sum(); + } + + /** + * 更新子部门祖级列表 + * + * @param newAncestors 新祖级列表 + * @param oldAncestors 原祖级列表 + * @param id ID + */ + private void updateChildrenAncestors(String newAncestors, String oldAncestors, Long id) { + List children = this.listChildren(id); + if (CollUtil.isEmpty(children)) { + return; + } + List list = new ArrayList<>(children.size()); + for (DeptDO child : children) { + DeptDO dept = new DeptDO(); + dept.setId(child.getId()); + dept.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors)); + list.add(dept); + } + baseMapper.updateById(list); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DictItemServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DictItemServiceImpl.java new file mode 100644 index 0000000..400e184 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DictItemServiceImpl.java @@ -0,0 +1,118 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.StrUtil; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.system.mapper.DictItemMapper; +import top.ysoft.admin.system.model.entity.DictItemDO; +import top.ysoft.admin.system.model.query.DictItemQuery; +import top.ysoft.admin.system.model.req.DictItemReq; +import top.ysoft.admin.system.model.resp.DictItemResp; +import top.ysoft.admin.system.service.DictItemService; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.autoconfigure.project.ProjectProperties; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.enums.BaseEnum; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 字典项业务实现 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DictItemServiceImpl extends BaseServiceImpl implements DictItemService { + + private final ProjectProperties projectProperties; + private static final Map> ENUM_DICT_CACHE = new ConcurrentHashMap<>(); + + @Override + public void beforeAdd(DictItemReq req) { + String value = req.getValue(); + CheckUtils.throwIf(this.isValueExists(value, null, req.getDictId()), "新增失败,字典值 [{}] 已存在", value); + RedisUtils.deleteByPattern(CacheConstants.DICT_KEY_PREFIX + StringConstants.ASTERISK); + } + + @Override + public void beforeUpdate(DictItemReq req, Long id) { + String value = req.getValue(); + CheckUtils.throwIf(this.isValueExists(value, id, req.getDictId()), "修改失败,字典值 [{}] 已存在", value); + RedisUtils.deleteByPattern(CacheConstants.DICT_KEY_PREFIX + StringConstants.ASTERISK); + } + + @Override + public List listByDictCode(String dictCode) { + return Optional.ofNullable(ENUM_DICT_CACHE.get(dictCode.toLowerCase())) + .orElseGet(() -> baseMapper.listByDictCode(dictCode)); + } + + @Override + public void deleteByDictIds(List dictIds) { + if (CollUtil.isEmpty(dictIds)) { + return; + } + baseMapper.lambdaUpdate().in(DictItemDO::getDictId, dictIds).remove(); + RedisUtils.deleteByPattern(CacheConstants.DICT_KEY_PREFIX + StringConstants.ASTERISK); + } + + @Override + public List listEnumDictNames() { + return ENUM_DICT_CACHE.keySet().stream().toList(); + } + + /** + * 字典值是否存在 + * + * @param value 字典值 + * @param id ID + * @param dictId 字典 ID + * @return 是否存在 + */ + private boolean isValueExists(String value, Long id, Long dictId) { + return baseMapper.lambdaQuery() + .eq(DictItemDO::getValue, value) + .eq(DictItemDO::getDictId, dictId) + .ne(null != id, DictItemDO::getId, id) + .exists(); + } + + /** + * 将枚举转换为枚举字典 + * + * @param enumClass 枚举类型 + * @return 枚举字典 + */ + private List toEnumDict(Class enumClass) { + Object[] enumConstants = enumClass.getEnumConstants(); + return Arrays.stream(enumConstants).map(e -> { + BaseEnum baseEnum = (BaseEnum)e; + return new LabelValueResp(baseEnum.getDescription(), baseEnum.getValue(), baseEnum.getColor()); + }).toList(); + } + + /** + * 缓存枚举字典 + */ + @PostConstruct + public void init() { + Set> classSet = ClassUtil.scanPackageBySuper(projectProperties.getBasePackage(), BaseEnum.class); + ENUM_DICT_CACHE.putAll(classSet.stream() + .collect(Collectors.toMap(cls -> StrUtil.toUnderlineCase(cls.getSimpleName()) + .toLowerCase(), this::toEnumDict))); + log.debug("枚举字典已缓存到内存:{}", ENUM_DICT_CACHE.keySet()); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DictServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DictServiceImpl.java new file mode 100644 index 0000000..992afa4 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/DictServiceImpl.java @@ -0,0 +1,86 @@ +package top.ysoft.admin.system.service.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.system.mapper.DictMapper; +import top.ysoft.admin.system.model.entity.DictDO; +import top.ysoft.admin.system.model.query.DictQuery; +import top.ysoft.admin.system.model.req.DictReq; +import top.ysoft.admin.system.model.resp.DictResp; +import top.ysoft.admin.system.service.DictItemService; +import top.ysoft.admin.system.service.DictService; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; + +import java.util.List; +import java.util.Optional; + +/** + * 字典业务实现 + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Service +@RequiredArgsConstructor +public class DictServiceImpl extends BaseServiceImpl implements DictService { + + private final DictItemService dictItemService; + + @Override + public void beforeAdd(DictReq req) { + String name = req.getName(); + CheckUtils.throwIf(this.isNameExists(name, null), "新增失败,[{}] 已存在", name); + String code = req.getCode(); + CheckUtils.throwIf(this.isCodeExists(code, null), "新增失败,[{}] 已存在", code); + } + + @Override + public void beforeUpdate(DictReq req, Long id) { + String name = req.getName(); + CheckUtils.throwIf(this.isNameExists(name, id), "修改失败,[{}] 已存在", name); + DictDO oldDict = super.getById(id); + CheckUtils.throwIfNotEqual(req.getCode(), oldDict.getCode(), "不允许修改字典编码"); + } + + @Override + public void beforeDelete(List ids) { + List list = baseMapper.lambdaQuery() + .select(DictDO::getName, DictDO::getIsSystem) + .in(DictDO::getId, ids) + .list(); + Optional isSystemData = list.stream().filter(DictDO::getIsSystem).findFirst(); + CheckUtils.throwIf(isSystemData::isPresent, "所选字典 [{}] 是系统内置字典,不允许删除", isSystemData.orElseGet(DictDO::new) + .getName()); + dictItemService.deleteByDictIds(ids); + } + + @Override + public List listEnumDict() { + List enumDictNameList = dictItemService.listEnumDictNames(); + return enumDictNameList.stream().map(name -> new LabelValueResp(name, name)).toList(); + } + + /** + * 名称是否存在 + * + * @param name 名称 + * @param id ID + * @return 是否存在 + */ + private boolean isNameExists(String name, Long id) { + return baseMapper.lambdaQuery().eq(DictDO::getName, name).ne(null != id, DictDO::getId, id).exists(); + } + + /** + * 编码是否存在 + * + * @param code 编码 + * @param id ID + * @return 是否存在 + */ + private boolean isCodeExists(String code, Long id) { + return baseMapper.lambdaQuery().eq(DictDO::getCode, code).ne(null != id, DictDO::getId, id).exists(); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/FileServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/FileServiceImpl.java new file mode 100644 index 0000000..41c452c --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/FileServiceImpl.java @@ -0,0 +1,149 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import jakarta.annotation.Resource; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.upload.UploadPretreatment; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import top.ysoft.admin.system.enums.FileTypeEnum; +import top.ysoft.admin.system.mapper.FileMapper; +import top.ysoft.admin.system.model.entity.FileDO; +import top.ysoft.admin.system.model.entity.StorageDO; +import top.ysoft.admin.system.model.query.FileQuery; +import top.ysoft.admin.system.model.req.FileReq; +import top.ysoft.admin.system.model.resp.file.FileResp; +import top.ysoft.admin.system.model.resp.file.FileStatisticsResp; +import top.ysoft.admin.system.service.FileService; +import top.ysoft.admin.system.service.StorageService; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.util.StrUtils; +import top.continew.starter.core.util.URLUtils; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.extension.crud.service.BaseServiceImpl; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 文件业务实现 + * + * @author Charles7c + * @since 2023/12/23 10:38 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class FileServiceImpl extends BaseServiceImpl implements FileService { + + private final FileStorageService fileStorageService; + @Resource + private StorageService storageService; + + @Override + protected void beforeDelete(List ids) { + List fileList = baseMapper.lambdaQuery().in(FileDO::getId, ids).list(); + Map> fileListGroup = fileList.stream().collect(Collectors.groupingBy(FileDO::getStorageId)); + for (Map.Entry> entry : fileListGroup.entrySet()) { + StorageDO storage = storageService.getById(entry.getKey()); + for (FileDO file : entry.getValue()) { + FileInfo fileInfo = file.toFileInfo(storage); + fileStorageService.delete(fileInfo); + } + } + } + + @Override + public FileInfo upload(MultipartFile file, String path, String storageCode, Boolean createMin, Boolean store) { + StorageDO storage; + if (StrUtil.isBlank(storageCode)) { + storage = storageService.getDefaultStorage(); + CheckUtils.throwIfNull(storage, "请先指定默认存储"); + } else { + storage = storageService.getByCode(storageCode); + CheckUtils.throwIfNotExists(storage, "StorageDO", "Code", storageCode); + } + UploadPretreatment uploadPretreatment = fileStorageService.of(file) + .setPlatform(storage.getCode()) + .setHashCalculatorMd5(true) + .putAttr(ClassUtil.getClassName(StorageDO.class, false), storage) + .setPath(path) + .setObjectType(String.valueOf(store)); + // 图片文件生成缩略图 + if (createMin && FileTypeEnum.IMAGE.getExtensions() + .contains(FileNameUtil.extName(file.getOriginalFilename()))) { + uploadPretreatment.thumbnail(img -> img.size(100, 100)); + } + uploadPretreatment.setProgressMonitor(new ProgressListener() { + @Override + public void start() { + log.info("开始上传"); + } + + @Override + public void progress(long progressSize, Long allSize) { + log.info("已上传 [{}],总大小 [{}]", progressSize, allSize); + } + + @Override + public void finish() { + log.info("上传结束"); + } + }); + // 处理本地存储文件 URL + FileInfo fileInfo = uploadPretreatment.upload(); + String domain = StrUtil.appendIfMissing(storage.getDomain(), StringConstants.SLASH); + fileInfo.setUrl(URLUtil.normalize(domain + fileInfo.getPath() + fileInfo.getFilename())); + if (StrUtil.isNotBlank(fileInfo.getThFilename())) { + fileInfo.setThUrl(URLUtil.normalize(domain + fileInfo.getPath() + fileInfo.getThFilename())); + } else { + fileInfo.setThUrl(fileInfo.getUrl()); + } + return fileInfo; + } + + @Override + public Long countByStorageIds(List storageIds) { + if (CollUtil.isEmpty(storageIds)) { + return 0L; + } + return baseMapper.lambdaQuery().in(FileDO::getStorageId, storageIds).count(); + } + + @Override + public FileStatisticsResp statistics() { + FileStatisticsResp resp = new FileStatisticsResp(); + List statisticsList = baseMapper.statistics(); + if (CollUtil.isEmpty(statisticsList)) { + return resp; + } + resp.setData(statisticsList); + resp.setSize(statisticsList.stream().mapToLong(FileStatisticsResp::getSize).sum()); + resp.setNumber(statisticsList.stream().mapToLong(FileStatisticsResp::getNumber).sum()); + return resp; + } + + @Override + protected void fill(Object obj) { + super.fill(obj); + if (obj instanceof FileResp fileResp && !URLUtils.isHttpUrl(fileResp.getUrl())) { + StorageDO storage = storageService.getById(fileResp.getStorageId()); + String prefix = StrUtil.appendIfMissing(storage.getDomain(), StringConstants.SLASH); + String url = URLUtil.normalize(prefix + fileResp.getUrl()); + fileResp.setUrl(url); + String thumbnailUrl = StrUtils.blankToDefault(fileResp.getThumbnailUrl(), url, thUrl -> URLUtil + .normalize(prefix + thUrl)); + fileResp.setThumbnailUrl(thumbnailUrl); + fileResp.setStorageName("%s (%s)".formatted(storage.getName(), storage.getCode())); + } + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/ImportExcelServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/ImportExcelServiceImpl.java new file mode 100644 index 0000000..acd5a03 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/ImportExcelServiceImpl.java @@ -0,0 +1,39 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.http.ContentType; +import cn.hutool.json.JSONUtil; +import jakarta.servlet.http.HttpServletResponse; +import net.dreamlu.mica.core.result.R; +import top.continew.starter.web.util.FileUploadUtils; +import top.ysoft.admin.system.service.ImportExcelService; + +import java.io.IOException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 导入Excel服务实现类 + * + * @author zc + * @since 2025/03/21 18:10 + */ +@Service +@Slf4j +public class ImportExcelServiceImpl implements ImportExcelService { + + @Override + public void downloadImportTemplate(HttpServletResponse response, + String fileName, + String fileNameZH) throws IOException { + try { + FileUploadUtils.download(response, ResourceUtil.getStream("templates/import/" + fileName), fileNameZH); + } catch (Exception e) { + log.error("下载导入模板失败:{}", e.getMessage(), e); + response.setCharacterEncoding(CharsetUtil.UTF_8); + response.setContentType(ContentType.JSON.toString()); + response.getWriter().write(JSONUtil.toJsonStr(R.fail("下载导入模板失败"))); + } + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/LogServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/LogServiceImpl.java new file mode 100644 index 0000000..a6ff9da --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/LogServiceImpl.java @@ -0,0 +1,114 @@ +package top.ysoft.admin.system.service.impl; + +import cn.crane4j.annotation.AutoOperate; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.mapper.LogMapper; +import top.ysoft.admin.system.model.entity.LogDO; +import top.ysoft.admin.system.model.query.LogQuery; +import top.ysoft.admin.system.model.resp.log.LogDetailResp; +import top.ysoft.admin.system.model.resp.log.LogResp; +import top.ysoft.admin.system.model.resp.log.LoginLogExportResp; +import top.ysoft.admin.system.model.resp.log.OperationLogExportResp; +import top.ysoft.admin.system.service.LogService; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.data.mp.util.QueryWrapperHelper; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.query.SortQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.file.excel.util.ExcelUtils; + +import java.util.Date; +import java.util.List; + +/** + * 系统日志业务实现 + * + * @author Charles7c + * @since 2022/12/23 20:12 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class LogServiceImpl implements LogService { + + private final LogMapper baseMapper; + + @Override + public PageResp page(LogQuery query, PageQuery pageQuery) { + QueryWrapper queryWrapper = this.buildQueryWrapper(query); + QueryWrapperHelper.sort(queryWrapper, pageQuery.getSort()); + IPage page = baseMapper.selectLogPage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + return PageResp.build(page); + } + + @Override + @AutoOperate(type = LogDetailResp.class) + public LogDetailResp get(Long id) { + LogDO logDO = baseMapper.selectById(id); + CheckUtils.throwIfNotExists(logDO, "LogDO", "ID", id); + return BeanUtil.copyProperties(logDO, LogDetailResp.class); + } + + @Override + public void exportLoginLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) { + List list = BeanUtil.copyToList(this.list(query, sortQuery), LoginLogExportResp.class); + ExcelUtils.export(list, "导出登录日志数据", LoginLogExportResp.class, response); + } + + @Override + public void exportOperationLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) { + List list = BeanUtil.copyToList(this + .list(query, sortQuery), OperationLogExportResp.class); + ExcelUtils.export(list, "导出操作日志数据", OperationLogExportResp.class, response); + } + + /** + * 查询列表 + * + * @param query 查询条件 + * @param sortQuery 排序查询条件 + * @return 列表信息 + */ + private List list(LogQuery query, SortQuery sortQuery) { + QueryWrapper queryWrapper = this.buildQueryWrapper(query); + QueryWrapperHelper.sort(queryWrapper, sortQuery.getSort()); + return baseMapper.selectLogList(queryWrapper); + } + + /** + * 构建 QueryWrapper + * + * @param query 查询条件 + * @return QueryWrapper + */ + private QueryWrapper buildQueryWrapper(LogQuery query) { + String description = query.getDescription(); + String module = query.getModule(); + String ip = query.getIp(); + String createUserString = query.getCreateUserString(); + DisEnableStatusEnum status = query.getStatus(); + List createTimeList = query.getCreateTime(); + return new QueryWrapper().and(StrUtil.isNotBlank(description), q -> q.like("t1.description", description) + .or() + .like("t1.module", description)) + .eq(StrUtil.isNotBlank(module), "t1.module", module) + .and(StrUtil.isNotBlank(ip), q -> q.like("t1.ip", ip).or().like("t1.address", ip)) + .and(StrUtil.isNotBlank(createUserString), q -> q.like("t2.username", createUserString) + .or() + .like("t2.nickname", createUserString)) + .eq(null != status, "t1.status", status) + .between(CollUtil.isNotEmpty(createTimeList), "t1.create_time", CollUtil.getFirst(createTimeList), CollUtil + .getLast(createTimeList)); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/MenuServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/MenuServiceImpl.java new file mode 100644 index 0000000..a0c0122 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/MenuServiceImpl.java @@ -0,0 +1,124 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import com.alicp.jetcache.anno.Cached; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.system.enums.MenuTypeEnum; +import top.ysoft.admin.system.mapper.MenuMapper; +import top.ysoft.admin.system.model.entity.MenuDO; +import top.ysoft.admin.system.model.query.MenuQuery; +import top.ysoft.admin.system.model.req.MenuReq; +import top.ysoft.admin.system.model.resp.MenuResp; +import top.ysoft.admin.system.service.MenuService; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.extension.crud.service.BaseServiceImpl; + +import java.util.List; +import java.util.Set; + +/** + * 菜单业务实现 + * + * @author Charles7c + * @since 2023/2/15 20:30 + */ +@Service +@RequiredArgsConstructor +public class MenuServiceImpl extends BaseServiceImpl implements MenuService { + + @Override + public Long add(MenuReq req) { + String title = req.getTitle(); + CheckUtils.throwIf(this.isTitleExists(title, req.getParentId(), null), "新增失败,标题 [{}] 已存在", title); + // 目录和菜单的组件名称不能重复 + if (!MenuTypeEnum.BUTTON.equals(req.getType())) { + String name = req.getName(); + CheckUtils.throwIf(this.isNameExists(name, null), "新增失败,组件名称 [{}] 已存在", name); + } + // 目录类型菜单,默认为 Layout + if (MenuTypeEnum.DIR.equals(req.getType())) { + req.setComponent(StrUtil.blankToDefault(req.getComponent(), "Layout")); + } + RedisUtils.deleteByPattern(CacheConstants.ROLE_MENU_KEY_PREFIX + StringConstants.ASTERISK); + return super.add(req); + } + + @Override + public void update(MenuReq req, Long id) { + String title = req.getTitle(); + CheckUtils.throwIf(this.isTitleExists(title, req.getParentId(), id), "修改失败,标题 [{}] 已存在", title); + // 目录和菜单的组件名称不能重复 + if (!MenuTypeEnum.BUTTON.equals(req.getType())) { + String name = req.getName(); + CheckUtils.throwIf(this.isNameExists(name, id), "修改失败,组件名称 [{}] 已存在", name); + } + MenuDO oldMenu = super.getById(id); + CheckUtils.throwIfNotEqual(req.getType(), oldMenu.getType(), "不允许修改菜单类型"); + super.update(req, id); + RedisUtils.deleteByPattern(CacheConstants.ROLE_MENU_KEY_PREFIX + StringConstants.ASTERISK); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(List ids) { + baseMapper.lambdaUpdate().in(MenuDO::getParentId, ids).remove(); + super.delete(ids); + RedisUtils.deleteByPattern(CacheConstants.ROLE_MENU_KEY_PREFIX + StringConstants.ASTERISK); + } + + @Override + public Set listPermissionByUserId(Long userId) { + return baseMapper.selectPermissionByUserId(userId); + } + + @Override + @Cached(key = "#roleId", name = CacheConstants.ROLE_MENU_KEY_PREFIX) + public List listByRoleId(Long roleId) { + if (SysConstants.SUPER_ROLE_ID.equals(roleId)) { + return super.list(new MenuQuery(DisEnableStatusEnum.ENABLE), null); + } + List menuList = baseMapper.selectListByRoleId(roleId); + List list = BeanUtil.copyToList(menuList, MenuResp.class); + list.forEach(super::fill); + return list; + } + + /** + * 标题是否存在 + * + * @param title 标题 + * @param parentId 上级 ID + * @param id ID + * @return true:存在;false:不存在 + */ + private boolean isTitleExists(String title, Long parentId, Long id) { + return baseMapper.lambdaQuery() + .eq(MenuDO::getTitle, title) + .eq(MenuDO::getParentId, parentId) + .ne(null != id, MenuDO::getId, id) + .exists(); + } + + /** + * 名称是否存在 + * + * @param name 标题 + * @param id ID + * @return true:存在;false:不存在 + */ + private boolean isNameExists(String name, Long id) { + return baseMapper.lambdaQuery() + .eq(MenuDO::getName, name) + .ne(MenuDO::getType, MenuTypeEnum.BUTTON) + .ne(null != id, MenuDO::getId, id) + .exists(); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/MessageServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/MessageServiceImpl.java new file mode 100644 index 0000000..a201625 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/MessageServiceImpl.java @@ -0,0 +1,65 @@ +package top.ysoft.admin.system.service.impl; + +import cn.crane4j.annotation.AutoOperate; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import top.ysoft.admin.system.mapper.MessageMapper; +import top.ysoft.admin.system.model.entity.MessageDO; +import top.ysoft.admin.system.model.query.MessageQuery; +import top.ysoft.admin.system.model.req.MessageReq; +import top.ysoft.admin.system.model.resp.message.MessageResp; +import top.ysoft.admin.system.service.MessageService; +import top.ysoft.admin.system.service.MessageUserService; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.data.mp.util.QueryWrapperHelper; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.util.List; + +/** + * 消息业务实现 + * + * @author Bull-BCLS + * @since 2023/10/15 19:05 + */ +@Service +@RequiredArgsConstructor +public class MessageServiceImpl implements MessageService { + + private final MessageMapper baseMapper; + private final MessageUserService messageUserService; + + @Override + @AutoOperate(type = MessageResp.class, on = "list") + public PageResp page(MessageQuery query, PageQuery pageQuery) { + QueryWrapper queryWrapper = QueryWrapperHelper.build(query, pageQuery.getSort()); + queryWrapper.apply(null != query.getUserId(), "t2.user_id={0}", query.getUserId()) + .apply(null != query.getIsRead(), "t2.is_read={0}", query.getIsRead()); + IPage page = baseMapper.selectPageByUserId(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + return PageResp.build(page); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void add(MessageReq req, List userIdList) { + CheckUtils.throwIf(() -> CollUtil.isEmpty(userIdList), "消息接收人不能为空"); + MessageDO message = BeanUtil.copyProperties(req, MessageDO.class); + baseMapper.insert(message); + messageUserService.add(message.getId(), userIdList); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(List ids) { + baseMapper.deleteByIds(ids); + messageUserService.deleteByMessageIds(ids); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/MessageUserServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/MessageUserServiceImpl.java new file mode 100644 index 0000000..8ec301d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/MessageUserServiceImpl.java @@ -0,0 +1,85 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.system.enums.MessageTypeEnum; +import top.ysoft.admin.system.mapper.MessageUserMapper; +import top.ysoft.admin.system.model.entity.MessageUserDO; +import top.ysoft.admin.system.model.resp.message.MessageTypeUnreadResp; +import top.ysoft.admin.system.model.resp.message.MessageUnreadResp; +import top.ysoft.admin.system.service.MessageUserService; +import top.continew.starter.core.validation.CheckUtils; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 消息和用户关联业务实现 + * + * @author Bull-BCLS + * @since 2023/10/15 19:05 + */ +@Service +@RequiredArgsConstructor +public class MessageUserServiceImpl implements MessageUserService { + + private final MessageUserMapper baseMapper; + + @Override + public MessageUnreadResp countUnreadMessageByUserId(Long userId, Boolean isDetail) { + MessageUnreadResp result = new MessageUnreadResp(); + Long total = 0L; + if (Boolean.TRUE.equals(isDetail)) { + List detailList = new ArrayList<>(); + for (MessageTypeEnum messageType : MessageTypeEnum.values()) { + MessageTypeUnreadResp resp = new MessageTypeUnreadResp(); + resp.setType(messageType); + Long count = baseMapper.selectUnreadCountByUserIdAndType(userId, messageType.getValue()); + resp.setCount(count); + detailList.add(resp); + total += count; + } + result.setDetails(detailList); + } else { + total = baseMapper.selectUnreadCountByUserIdAndType(userId, null); + } + result.setTotal(total); + return result; + } + + @Override + public void add(Long messageId, List userIdList) { + CheckUtils.throwIfEmpty(userIdList, "消息接收人不能为空"); + List messageUserList = userIdList.stream().map(userId -> { + MessageUserDO messageUser = new MessageUserDO(); + messageUser.setUserId(userId); + messageUser.setMessageId(messageId); + messageUser.setIsRead(false); + return messageUser; + }).toList(); + baseMapper.insert(messageUserList); + } + + @Override + public void readMessage(List ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + baseMapper.lambdaUpdate() + .set(MessageUserDO::getIsRead, true) + .set(MessageUserDO::getReadTime, LocalDateTime.now()) + .eq(MessageUserDO::getIsRead, false) + .in(CollUtil.isNotEmpty(ids), MessageUserDO::getMessageId, ids) + .update(); + } + + @Override + public void deleteByMessageIds(List messageIds) { + if (CollUtil.isEmpty(messageIds)) { + return; + } + baseMapper.lambdaUpdate().in(MessageUserDO::getMessageId, messageIds).remove(); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/NoticeServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/NoticeServiceImpl.java new file mode 100644 index 0000000..da7b22a --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/NoticeServiceImpl.java @@ -0,0 +1,33 @@ +package top.ysoft.admin.system.service.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.system.mapper.NoticeMapper; +import top.ysoft.admin.system.model.entity.NoticeDO; +import top.ysoft.admin.system.model.query.NoticeQuery; +import top.ysoft.admin.system.model.req.NoticeReq; +import top.ysoft.admin.system.model.resp.NoticeDetailResp; +import top.ysoft.admin.system.model.resp.NoticeResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardNoticeResp; +import top.ysoft.admin.system.service.NoticeService; +import top.continew.starter.extension.crud.service.BaseServiceImpl; + +import java.util.List; + +/** + * 公告业务实现 + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +@Service +@RequiredArgsConstructor +public class NoticeServiceImpl extends BaseServiceImpl implements NoticeService { + + @Override + public List listDashboard() { + Long userId = UserContextHolder.isAdmin() ? null : UserContextHolder.getUserId(); + return baseMapper.selectDashboardList(userId); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/OptionServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/OptionServiceImpl.java new file mode 100644 index 0000000..1bdb47b --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/OptionServiceImpl.java @@ -0,0 +1,125 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alicp.jetcache.anno.Cached; +import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.system.enums.OptionCategoryEnum; +import top.ysoft.admin.system.enums.PasswordPolicyEnum; +import top.ysoft.admin.system.mapper.OptionMapper; +import top.ysoft.admin.system.model.entity.OptionDO; +import top.ysoft.admin.system.model.query.OptionQuery; +import top.ysoft.admin.system.model.req.OptionReq; +import top.ysoft.admin.system.model.req.OptionResetValueReq; +import top.ysoft.admin.system.model.resp.OptionResp; +import top.ysoft.admin.system.service.OptionService; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.data.mp.util.QueryWrapperHelper; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 参数业务实现 + * + * @author Bull-BCLS + * @since 2023/8/26 19:38 + */ +@Service +@RequiredArgsConstructor +public class OptionServiceImpl implements OptionService { + + private final OptionMapper baseMapper; + + @Override + public List list(OptionQuery query) { + return BeanUtil.copyToList(baseMapper.selectList(QueryWrapperHelper.build(query)), OptionResp.class); + } + + @Override + @Cached(key = "#category", name = CacheConstants.OPTION_KEY_PREFIX + "MAP:") + public Map getByCategory(OptionCategoryEnum category) { + return baseMapper.selectByCategory(category.name()) + .stream() + .collect(Collectors.toMap(OptionDO::getCode, o -> StrUtil.emptyIfNull(ObjectUtil.defaultIfNull(o + .getValue(), o.getDefaultValue())), (oldVal, newVal) -> oldVal)); + } + + @Override + public void update(List options) { + // 非空校验 + List idList = options.stream().map(OptionReq::getId).toList(); + List optionList = baseMapper.selectByIds(idList); + Map optionMap = optionList.stream() + .collect(Collectors.toMap(OptionDO::getCode, Function.identity(), (existing, replacement) -> existing)); + for (OptionReq req : options) { + OptionDO option = optionMap.get(req.getCode()); + ValidationUtils.throwIfNull(option, "参数 [{}] 不存在", req.getCode()); + if (StrUtil.isNotBlank(option.getDefaultValue())) { + ValidationUtils.throwIfBlank(req.getValue(), "参数 [{}] 的值不能为空", option.getName()); + } + } + // 校验密码策略参数取值范围 + Map passwordPolicyOptionMap = options.stream() + .filter(option -> StrUtil.startWith(option.getCode(), PasswordPolicyEnum.CATEGORY + .name() + StringConstants.UNDERLINE)) + .collect(Collectors.toMap(OptionReq::getCode, OptionReq::getValue, (oldVal, newVal) -> oldVal)); + for (Map.Entry passwordPolicyOptionEntry : passwordPolicyOptionMap.entrySet()) { + String code = passwordPolicyOptionEntry.getKey(); + String value = passwordPolicyOptionEntry.getValue(); + ValidationUtils.throwIf(!NumberUtil.isNumber(value), "参数 [%s] 的值必须为数字", code); + PasswordPolicyEnum passwordPolicy = PasswordPolicyEnum.valueOf(code); + passwordPolicy.validateRange(Integer.parseInt(value), passwordPolicyOptionMap); + } + RedisUtils.deleteByPattern(CacheConstants.OPTION_KEY_PREFIX + StringConstants.ASTERISK); + baseMapper.updateById(BeanUtil.copyToList(options, OptionDO.class)); + } + + @Override + public void resetValue(OptionResetValueReq req) { + RedisUtils.deleteByPattern(CacheConstants.OPTION_KEY_PREFIX + StringConstants.ASTERISK); + String category = req.getCategory(); + List codeList = req.getCode(); + ValidationUtils.throwIf(StrUtil.isBlank(category) && CollUtil.isEmpty(codeList), "键列表不能为空"); + LambdaUpdateChainWrapper updateWrapper = baseMapper.lambdaUpdate().set(OptionDO::getValue, null); + if (StrUtil.isNotBlank(category)) { + updateWrapper.eq(OptionDO::getCategory, category); + } else { + updateWrapper.in(OptionDO::getCode, req.getCode()); + } + updateWrapper.update(); + } + + @Override + public int getValueByCode2Int(String code) { + return this.getValueByCode(code, Integer::parseInt); + } + + @Override + public T getValueByCode(String code, Function mapper) { + String value = RedisUtils.get(CacheConstants.OPTION_KEY_PREFIX + code); + if (StrUtil.isNotBlank(value)) { + return mapper.apply(value); + } + OptionDO option = baseMapper.lambdaQuery() + .eq(OptionDO::getCode, code) + .select(OptionDO::getValue, OptionDO::getDefaultValue) + .one(); + CheckUtils.throwIfNull(option, "参数 [{}] 不存在", code); + value = StrUtil.nullToDefault(option.getValue(), option.getDefaultValue()); + CheckUtils.throwIfBlank(value, "参数 [{}] 数据错误", code); + RedisUtils.set(CacheConstants.OPTION_KEY_PREFIX + code, value); + return mapper.apply(value); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/PeopleEquipmentServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/PeopleEquipmentServiceImpl.java new file mode 100644 index 0000000..abe7d59 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/PeopleEquipmentServiceImpl.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.system.service.impl; + +import lombok.RequiredArgsConstructor; + +import org.springframework.stereotype.Service; + +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.ysoft.admin.system.mapper.PeopleEquipmentMapper; +import top.ysoft.admin.system.model.entity.PeopleEquipmentDO; +import top.ysoft.admin.system.model.query.PeopleEquipmentQuery; +import top.ysoft.admin.system.model.req.PeopleEquipmentReq; +import top.ysoft.admin.system.model.resp.PeopleEquipmentResp; +import top.ysoft.admin.system.service.PeopleEquipmentService; + +/** + * 人员设备下发信息业务实现 + * + * @author zc + * @since 2025/03/27 14:59 + */ +@Service +@RequiredArgsConstructor +public class PeopleEquipmentServiceImpl extends BaseServiceImpl implements PeopleEquipmentService {} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/RoleDeptServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/RoleDeptServiceImpl.java new file mode 100644 index 0000000..d37b2af --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/RoleDeptServiceImpl.java @@ -0,0 +1,68 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import top.ysoft.admin.system.mapper.RoleDeptMapper; +import top.ysoft.admin.system.model.entity.RoleDeptDO; +import top.ysoft.admin.system.service.RoleDeptService; + +import java.util.List; + +/** + * 角色和部门业务实现 + * + * @author Charles7c + * @since 2023/2/19 10:47 + */ +@Service +@RequiredArgsConstructor +public class RoleDeptServiceImpl implements RoleDeptService { + + private final RoleDeptMapper baseMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean add(List deptIds, Long roleId) { + // 检查是否有变更 + List oldDeptIdList = baseMapper.lambdaQuery() + .select(RoleDeptDO::getDeptId) + .eq(RoleDeptDO::getRoleId, roleId) + .list() + .stream() + .map(RoleDeptDO::getDeptId) + .toList(); + if (CollUtil.isEmpty(CollUtil.disjunction(deptIds, oldDeptIdList))) { + return false; + } + // 删除原有关联 + baseMapper.lambdaUpdate().eq(RoleDeptDO::getRoleId, roleId).remove(); + // 保存最新关联 + List roleDeptList = deptIds.stream().map(deptId -> new RoleDeptDO(roleId, deptId)).toList(); + return baseMapper.insertBatch(roleDeptList); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteByRoleIds(List roleIds) { + if (CollUtil.isEmpty(roleIds)) { + return; + } + baseMapper.lambdaUpdate().in(RoleDeptDO::getRoleId, roleIds).remove(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteByDeptIds(List deptIds) { + if (CollUtil.isEmpty(deptIds)) { + return; + } + baseMapper.lambdaUpdate().in(RoleDeptDO::getDeptId, deptIds).remove(); + } + + @Override + public List listDeptIdByRoleId(Long roleId) { + return baseMapper.selectDeptIdByRoleId(roleId); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/RoleMenuServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/RoleMenuServiceImpl.java new file mode 100644 index 0000000..03878d3 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/RoleMenuServiceImpl.java @@ -0,0 +1,64 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import top.ysoft.admin.system.mapper.RoleMenuMapper; +import top.ysoft.admin.system.model.entity.RoleMenuDO; +import top.ysoft.admin.system.service.RoleMenuService; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 角色和菜单业务实现 + * + * @author Charles7c + * @since 2023/2/19 10:43 + */ +@Service +@RequiredArgsConstructor +public class RoleMenuServiceImpl implements RoleMenuService { + + private final RoleMenuMapper baseMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean add(List menuIds, Long roleId) { + // 检查是否有变更 + List oldMenuIdList = baseMapper.lambdaQuery() + .select(RoleMenuDO::getMenuId) + .eq(RoleMenuDO::getRoleId, roleId) + .list() + .stream() + .map(RoleMenuDO::getMenuId) + .collect(Collectors.toList()); + if (CollUtil.isEmpty(CollUtil.disjunction(menuIds, oldMenuIdList))) { + return false; + } + // 删除原有关联 + baseMapper.lambdaUpdate().eq(RoleMenuDO::getRoleId, roleId).remove(); + // 保存最新关联 + List roleMenuList = menuIds.stream().map(menuId -> new RoleMenuDO(roleId, menuId)).toList(); + return baseMapper.insertBatch(roleMenuList); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteByRoleIds(List roleIds) { + if (CollUtil.isEmpty(roleIds)) { + return; + } + baseMapper.lambdaUpdate().in(RoleMenuDO::getRoleId, roleIds).remove(); + } + + @Override + public List listMenuIdByRoleIds(List roleIds) { + if (CollUtil.isEmpty(roleIds)) { + return new ArrayList<>(0); + } + return baseMapper.selectMenuIdByRoleIds(roleIds); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/RoleServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..fee7767 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/RoleServiceImpl.java @@ -0,0 +1,247 @@ +package top.ysoft.admin.system.service.impl; + +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.alicp.jetcache.anno.CacheInvalidate; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.common.context.RoleContext; +import top.ysoft.admin.common.context.UserContext; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.common.enums.DataScopeEnum; +import top.ysoft.admin.system.mapper.RoleMapper; +import top.ysoft.admin.system.model.entity.RoleDO; +import top.ysoft.admin.system.model.query.RoleQuery; +import top.ysoft.admin.system.model.req.RoleReq; +import top.ysoft.admin.system.model.req.RoleUpdatePermissionReq; +import top.ysoft.admin.system.model.resp.MenuResp; +import top.ysoft.admin.system.model.resp.role.RoleDetailResp; +import top.ysoft.admin.system.model.resp.role.RoleResp; +import top.ysoft.admin.system.service.*; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.extension.crud.service.BaseServiceImpl; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 角色业务实现 + * + * @author Charles7c + * @since 2023/2/8 23:17 + */ +@Service +@RequiredArgsConstructor +public class RoleServiceImpl extends BaseServiceImpl implements RoleService { + + private final MenuService menuService; + private final RoleMenuService roleMenuService; + private final RoleDeptService roleDeptService; + private final UserRoleService userRoleService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long add(RoleReq req) { + String name = req.getName(); + CheckUtils.throwIf(this.isNameExists(name, null), "新增失败,[{}] 已存在", name); + String code = req.getCode(); + CheckUtils.throwIf(this.isCodeExists(code, null), "新增失败,[{}] 已存在", code); + // 新增信息 + Long roleId = super.add(req); + // 保存角色和部门关联 + roleDeptService.add(req.getDeptIds(), roleId); + return roleId; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(RoleReq req, Long id) { + String name = req.getName(); + CheckUtils.throwIf(this.isNameExists(name, id), "修改失败,[{}] 已存在", name); + RoleDO oldRole = super.getById(id); + CheckUtils.throwIfNotEqual(req.getCode(), oldRole.getCode(), "角色编码不允许修改", oldRole.getName()); + DataScopeEnum oldDataScope = oldRole.getDataScope(); + if (Boolean.TRUE.equals(oldRole.getIsSystem())) { + CheckUtils.throwIfNotEqual(req.getDataScope(), oldDataScope, "[{}] 是系统内置角色,不允许修改角色数据权限", oldRole.getName()); + } + // 更新信息 + super.update(req, id); + if (SysConstants.SUPER_ROLE_CODE.equals(req.getCode())) { + return; + } + // 保存角色和部门关联 + boolean isSaveDeptSuccess = roleDeptService.add(req.getDeptIds(), id); + // 如果数据权限有变更,则更新在线用户权限信息 + if (isSaveDeptSuccess || ObjectUtil.notEqual(req.getDataScope(), oldDataScope)) { + this.updateUserContext(id); + } + } + + @Override + public void beforeDelete(List ids) { + List list = baseMapper.lambdaQuery() + .select(RoleDO::getName, RoleDO::getIsSystem) + .in(RoleDO::getId, ids) + .list(); + Optional isSystemData = list.stream().filter(RoleDO::getIsSystem).findFirst(); + CheckUtils.throwIf(isSystemData::isPresent, "所选角色 [{}] 是系统内置角色,不允许删除", isSystemData.orElseGet(RoleDO::new) + .getName()); + CheckUtils.throwIf(userRoleService.isRoleIdExists(ids), "所选角色存在用户关联,请解除关联后重试"); + // 删除角色和菜单关联 + roleMenuService.deleteByRoleIds(ids); + // 删除角色和部门关联 + roleDeptService.deleteByRoleIds(ids); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @CacheInvalidate(key = "#id", name = CacheConstants.ROLE_MENU_KEY_PREFIX) + public void updatePermission(Long id, RoleUpdatePermissionReq req) { + super.getById(id); + // 保存角色和菜单关联 + boolean isSaveMenuSuccess = roleMenuService.add(req.getMenuIds(), id); + // 如果功能权限有变更,则更新在线用户权限信息 + if (isSaveMenuSuccess) { + this.updateUserContext(id); + } + baseMapper.lambdaUpdate() + .set(RoleDO::getMenuCheckStrictly, req.getMenuCheckStrictly()) + .eq(RoleDO::getId, id) + .update(); + } + + @Override + public void assignToUsers(Long id, List userIds) { + super.getById(id); + // 保存用户和角色关联 + userRoleService.assignRoleToUsers(id, userIds); + // 更新用户上下文 + this.updateUserContext(id); + } + + @Override + public void fill(Object obj) { + super.fill(obj); + if (obj instanceof RoleDetailResp detail) { + Long roleId = detail.getId(); + List list = menuService.listByRoleId(roleId); + List menuIds = list.stream().map(MenuResp::getId).toList(); + detail.setMenuIds(menuIds); + } + } + + @Override + public Set listPermissionByUserId(Long userId) { + Set roleCodeSet = this.listCodeByUserId(userId); + // 超级管理员赋予全部权限 + if (roleCodeSet.contains(SysConstants.SUPER_ROLE_CODE)) { + return CollUtil.newHashSet(SysConstants.ALL_PERMISSION); + } + return menuService.listPermissionByUserId(userId); + } + + @Override + @ContainerMethod(namespace = ContainerConstants.USER_ROLE_NAME_LIST, type = MappingType.ORDER_OF_KEYS) + public List listNameByIds(List ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + List roleList = baseMapper.lambdaQuery().select(RoleDO::getName).in(RoleDO::getId, ids).list(); + return roleList.stream().map(RoleDO::getName).toList(); + } + + @Override + public Set listCodeByUserId(Long userId) { + List roleIdList = userRoleService.listRoleIdByUserId(userId); + if (CollUtil.isEmpty(roleIdList)) { + return Collections.emptySet(); + } + List roleList = baseMapper.lambdaQuery().select(RoleDO::getCode).in(RoleDO::getId, roleIdList).list(); + return roleList.stream().map(RoleDO::getCode).collect(Collectors.toSet()); + } + + @Override + public Set listByUserId(Long userId) { + List roleIdList = userRoleService.listRoleIdByUserId(userId); + if (CollUtil.isEmpty(roleIdList)) { + return Collections.emptySet(); + } + List roleList = baseMapper.lambdaQuery() + .select(RoleDO::getId, RoleDO::getCode, RoleDO::getDataScope) + .in(RoleDO::getId, roleIdList) + .list(); + return roleList.stream() + .map(r -> new RoleContext(r.getId(), r.getCode(), r.getDataScope())) + .collect(Collectors.toSet()); + } + + @Override + public RoleDO getByCode(String code) { + return baseMapper.lambdaQuery().eq(RoleDO::getCode, code).one(); + } + + @Override + public List listByNames(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + return this.list(Wrappers.lambdaQuery().in(RoleDO::getName, list)); + } + + @Override + public int countByNames(List roleNames) { + if (CollUtil.isEmpty(roleNames)) { + return 0; + } + return (int)this.count(Wrappers.lambdaQuery().in(RoleDO::getName, roleNames)); + } + + /** + * 名称是否存在 + * + * @param name 名称 + * @param id ID + * @return 是否存在 + */ + private boolean isNameExists(String name, Long id) { + return baseMapper.lambdaQuery().eq(RoleDO::getName, name).ne(null != id, RoleDO::getId, id).exists(); + } + + /** + * 编码是否存在 + * + * @param code 编码 + * @param id ID + * @return 是否存在 + */ + private boolean isCodeExists(String code, Long id) { + return baseMapper.lambdaQuery().eq(RoleDO::getCode, code).ne(null != id, RoleDO::getId, id).exists(); + } + + /** + * 更新用户上下文 + * + * @param roleId 角色 ID + */ + private void updateUserContext(Long roleId) { + List userIdList = userRoleService.listUserIdByRoleId(roleId); + userIdList.parallelStream().forEach(userId -> { + UserContext userContext = UserContextHolder.getContext(userId); + if (null != userContext) { + userContext.setRoles(this.listByUserId(userId)); + userContext.setPermissions(this.listPermissionByUserId(userId)); + UserContextHolder.setContext(userContext); + } + }); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/StorageServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/StorageServiceImpl.java new file mode 100644 index 0000000..1998f91 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/StorageServiceImpl.java @@ -0,0 +1,231 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import jakarta.annotation.Resource; +import lombok.RequiredArgsConstructor; +import org.dromara.x.file.storage.core.FileStorageProperties; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.FileStorageServiceBuilder; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.model.req.CommonStatusUpdateReq; +import top.ysoft.admin.common.util.SecureUtils; +import top.ysoft.admin.system.enums.StorageTypeEnum; +import top.ysoft.admin.system.mapper.StorageMapper; +import top.ysoft.admin.system.model.entity.StorageDO; +import top.ysoft.admin.system.model.query.StorageQuery; +import top.ysoft.admin.system.model.req.StorageReq; +import top.ysoft.admin.system.model.resp.StorageResp; +import top.ysoft.admin.system.service.FileService; +import top.ysoft.admin.system.service.StorageService; +import top.ysoft.admin.system.validation.ValidationGroup; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.util.ExceptionUtils; +import top.continew.starter.core.util.URLUtils; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.continew.starter.web.util.SpringWebUtils; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * 存储业务实现 + * + * @author Charles7c + * @since 2023/12/26 22:09 + */ +@Service +@RequiredArgsConstructor +public class StorageServiceImpl extends BaseServiceImpl implements StorageService { + + private final FileStorageService fileStorageService; + @Resource + private FileService fileService; + + @Override + public void beforeAdd(StorageReq req) { + this.decodeSecretKey(req, null); + String code = req.getCode(); + CheckUtils.throwIf(this.isCodeExists(code, null), "新增失败,[{}] 已存在", code); + // 单独指定默认存储 + req.setIsDefault(false); + if (DisEnableStatusEnum.ENABLE.equals(req.getStatus())) { + this.load(req); + } + } + + @Override + public void beforeUpdate(StorageReq req, Long id) { + StorageDO oldStorage = super.getById(id); + CheckUtils.throwIfNotEqual(req.getCode(), oldStorage.getCode(), "不允许修改存储编码"); + CheckUtils.throwIfNotEqual(req.getType(), oldStorage.getType(), "不允许修改存储类型"); + this.decodeSecretKey(req, oldStorage); + DisEnableStatusEnum newStatus = req.getStatus(); + CheckUtils.throwIf(Boolean.TRUE.equals(oldStorage.getIsDefault()) && DisEnableStatusEnum.DISABLE + .equals(newStatus), "[{}] 是默认存储,不允许禁用", oldStorage.getName()); + // 重新加载存储引擎 + DisEnableStatusEnum oldStatus = oldStorage.getStatus(); + // 先卸载 + if (DisEnableStatusEnum.ENABLE.equals(oldStatus)) { + this.unload(BeanUtil.copyProperties(oldStorage, StorageReq.class)); + } + // 再加载 + if (DisEnableStatusEnum.ENABLE.equals(newStatus)) { + this.load(req); + } + } + + @Override + public void beforeDelete(List ids) { + CheckUtils.throwIf(fileService.countByStorageIds(ids) > 0, "所选存储存在文件关联,请删除文件后重试"); + List storageList = baseMapper.lambdaQuery().in(StorageDO::getId, ids).list(); + storageList.forEach(s -> { + CheckUtils.throwIfEqual(Boolean.TRUE, s.getIsDefault(), "[{}] 是默认存储,不允许删除", s.getName()); + // 卸载启用状态的存储 + if (DisEnableStatusEnum.ENABLE.equals(s.getStatus())) { + this.unload(BeanUtil.copyProperties(s, StorageReq.class)); + } + }); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateStatus(CommonStatusUpdateReq req, Long id) { + StorageDO storage = super.getById(id); + // 状态未改变 + DisEnableStatusEnum newStatus = req.getStatus(); + if (storage.getStatus().equals(newStatus)) { + return; + } + // 修改状态 + baseMapper.lambdaUpdate().eq(StorageDO::getId, id).set(StorageDO::getStatus, newStatus).update(); + // 加载、卸载存储引擎 + StorageReq storageReq = BeanUtil.copyProperties(storage, StorageReq.class); + switch (newStatus) { + case ENABLE: + this.load(storageReq); + break; + case DISABLE: + CheckUtils.throwIfEqual(Boolean.TRUE, storage.getIsDefault(), "[{}] 是默认存储,不允许禁用", storage.getName()); + this.unload(storageReq); + break; + default: + break; + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void setDefault(Long id) { + StorageDO storage = super.getById(id); + if (Boolean.TRUE.equals(storage.getIsDefault())) { + return; + } + baseMapper.lambdaUpdate().eq(StorageDO::getIsDefault, true).set(StorageDO::getIsDefault, false).update(); + baseMapper.lambdaUpdate().eq(StorageDO::getId, id).set(StorageDO::getIsDefault, true).update(); + } + + @Override + public StorageDO getDefaultStorage() { + return baseMapper.lambdaQuery().eq(StorageDO::getIsDefault, true).one(); + } + + @Override + public StorageDO getByCode(String code) { + return baseMapper.lambdaQuery().eq(StorageDO::getCode, code).one(); + } + + @Override + public void load(StorageReq req) { + CopyOnWriteArrayList fileStorageList = fileStorageService.getFileStorageList(); + String domain = req.getDomain(); + ValidationUtils.throwIf(!URLUtils.isHttpUrl(domain), "域名格式错误"); + String bucketName = req.getBucketName(); + StorageTypeEnum type = req.getType(); + if (StorageTypeEnum.LOCAL.equals(type)) { + ValidationUtils.validate(req, ValidationGroup.Storage.Local.class); + req.setBucketName(StrUtil.appendIfMissing(bucketName + .replace(StringConstants.BACKSLASH, StringConstants.SLASH), StringConstants.SLASH)); + FileStorageProperties.LocalPlusConfig config = new FileStorageProperties.LocalPlusConfig(); + config.setPlatform(req.getCode()); + config.setStoragePath(bucketName); + fileStorageList.addAll(FileStorageServiceBuilder.buildLocalPlusFileStorage(Collections + .singletonList(config))); + SpringWebUtils.registerResourceHandler(MapUtil.of(URLUtil.url(req.getDomain()).getPath(), bucketName)); + } else if (StorageTypeEnum.OSS.equals(type)) { + ValidationUtils.validate(req, ValidationGroup.Storage.OSS.class); + FileStorageProperties.AmazonS3Config config = new FileStorageProperties.AmazonS3Config(); + config.setPlatform(req.getCode()); + config.setAccessKey(req.getAccessKey()); + config.setSecretKey(req.getSecretKey()); + config.setEndPoint(req.getEndpoint()); + config.setBucketName(bucketName); + config.setDomain(domain); + fileStorageList.addAll(FileStorageServiceBuilder.buildAmazonS3FileStorage(Collections + .singletonList(config), null)); + } + } + + @Override + public void unload(StorageReq req) { + CopyOnWriteArrayList fileStorageList = fileStorageService.getFileStorageList(); + FileStorage fileStorage = fileStorageService.getFileStorage(req.getCode()); + fileStorageList.remove(fileStorage); + fileStorage.close(); + SpringWebUtils.deRegisterResourceHandler(MapUtil.of(URLUtil.url(req.getDomain()).getPath(), req + .getBucketName())); + } + + /** + * 解密 SecretKey + * + * @param req 请求参数 + * @param storage 存储信息 + */ + private void decodeSecretKey(StorageReq req, StorageDO storage) { + if (!StorageTypeEnum.OSS.equals(req.getType())) { + return; + } + // 修改时,如果 SecretKey 不修改,需要手动修正 + String newSecretKey = req.getSecretKey(); + boolean isSecretKeyNotUpdate = StrUtil.isBlank(newSecretKey) || newSecretKey.contains(StringConstants.ASTERISK); + if (null != storage && isSecretKeyNotUpdate) { + req.setSecretKey(storage.getSecretKey()); + return; + } + // 新增时或修改了 SecretKey + String secretKey = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(newSecretKey)); + ValidationUtils.throwIfNull(secretKey, "私有密钥解密失败"); + ValidationUtils.throwIf(secretKey.length() > 255, "私有密钥长度不能超过 255 个字符"); + req.setSecretKey(secretKey); + } + + /** + * 默认存储是否存在 + * + * @param id ID + * @return 是否存在 + */ + private boolean isDefaultExists(Long id) { + return baseMapper.lambdaQuery().eq(StorageDO::getIsDefault, true).ne(null != id, StorageDO::getId, id).exists(); + } + + /** + * 编码是否存在 + * + * @param code 编码 + * @param id ID + * @return 是否存在 + */ + private boolean isCodeExists(String code, Long id) { + return baseMapper.lambdaQuery().eq(StorageDO::getCode, code).ne(null != id, StorageDO::getId, id).exists(); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserPasswordHistoryServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserPasswordHistoryServiceImpl.java new file mode 100644 index 0000000..ae96e4c --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserPasswordHistoryServiceImpl.java @@ -0,0 +1,63 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import top.ysoft.admin.system.mapper.UserPasswordHistoryMapper; +import top.ysoft.admin.system.model.entity.UserPasswordHistoryDO; +import top.ysoft.admin.system.service.UserPasswordHistoryService; + +import java.util.List; + +/** + * 用户历史密码业务实现 + * + * @author Charles7c + * @since 2024/5/16 21:58 + */ +@Service +@RequiredArgsConstructor +public class UserPasswordHistoryServiceImpl implements UserPasswordHistoryService { + + private final UserPasswordHistoryMapper baseMapper; + private final PasswordEncoder passwordEncoder; + + @Override + @Transactional(rollbackFor = Exception.class) + public void add(Long userId, String password, int count) { + if (StrUtil.isBlank(password)) { + return; + } + baseMapper.insert(new UserPasswordHistoryDO(userId, password)); + // 删除过期历史密码 + baseMapper.deleteExpired(userId, count); + } + + @Override + public void deleteByUserIds(List userIds) { + if (CollUtil.isEmpty(userIds)) { + return; + } + baseMapper.lambdaUpdate().in(UserPasswordHistoryDO::getUserId, userIds).remove(); + } + + @Override + public boolean isPasswordReused(Long userId, String password, int count) { + // 查询近 N 个历史密码 + List list = baseMapper.lambdaQuery() + .select(UserPasswordHistoryDO::getPassword) + .eq(UserPasswordHistoryDO::getUserId, userId) + .orderByDesc(UserPasswordHistoryDO::getCreateTime) + .last("LIMIT %s".formatted(count)) + .list(); + if (CollUtil.isEmpty(list)) { + return false; + } + // 校验是否重复使用历史密码 + List passwordList = list.stream().map(UserPasswordHistoryDO::getPassword).toList(); + return passwordList.stream().anyMatch(p -> passwordEncoder.matches(password, p)); + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserRoleServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserRoleServiceImpl.java new file mode 100644 index 0000000..97a969c --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserRoleServiceImpl.java @@ -0,0 +1,133 @@ +package top.ysoft.admin.system.service.impl; + +import cn.crane4j.annotation.AutoOperate; +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.system.mapper.UserRoleMapper; +import top.ysoft.admin.system.model.entity.UserRoleDO; +import top.ysoft.admin.system.model.query.RoleUserQuery; +import top.ysoft.admin.system.model.resp.role.RoleUserResp; +import top.ysoft.admin.system.service.UserRoleService; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.data.mp.util.QueryWrapperHelper; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.util.List; + +/** + * 用户和角色业务实现 + * + * @author Charles7c + * @since 2023/2/20 21:30 + */ +@Service +@RequiredArgsConstructor +public class UserRoleServiceImpl implements UserRoleService { + + private final UserRoleMapper baseMapper; + + @Override + @AutoOperate(type = RoleUserResp.class, on = "list") + public PageResp pageUser(RoleUserQuery query, PageQuery pageQuery) { + String description = query.getDescription(); + QueryWrapper queryWrapper = new QueryWrapper().eq("t1.role_id", query.getRoleId()) + .and(StrUtil.isNotBlank(description), q -> q.like("t2.username", description) + .or() + .like("t2.nickname", description) + .or() + .like("t2.description", description)); + QueryWrapperHelper.sort(queryWrapper, pageQuery.getSort()); + IPage page = baseMapper.selectUserPage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + return PageResp.build(page); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean assignRolesToUser(List roleIds, Long userId) { + // 检查是否有变更 + List oldRoleIdList = baseMapper.lambdaQuery() + .select(UserRoleDO::getRoleId) + .eq(UserRoleDO::getUserId, userId) + .list() + .stream() + .map(UserRoleDO::getRoleId) + .toList(); + if (CollUtil.isEmpty(CollUtil.disjunction(roleIds, oldRoleIdList))) { + return false; + } + CheckUtils.throwIf(SysConstants.SUPER_USER_ID.equals(userId) && !roleIds + .contains(SysConstants.SUPER_ROLE_ID), "不允许变更超管用户角色"); + // 删除原有关联 + baseMapper.lambdaUpdate().eq(UserRoleDO::getUserId, userId).remove(); + // 保存最新关联 + List userRoleList = roleIds.stream().map(roleId -> new UserRoleDO(userId, roleId)).toList(); + return baseMapper.insertBatch(userRoleList); + } + + @Override + public boolean assignRoleToUsers(Long roleId, List userIds) { + List userRoleList = userIds.stream().map(userId -> new UserRoleDO(userId, roleId)).toList(); + return baseMapper.insertBatch(userRoleList); + } + + @Override + public void deleteByIds(List ids) { + baseMapper.deleteByIds(ids); + } + + @Override + public void deleteByUserIds(List userIds) { + if (CollUtil.isEmpty(userIds)) { + return; + } + baseMapper.lambdaUpdate().in(UserRoleDO::getUserId, userIds).remove(); + } + + @Override + public void saveBatch(List list) { + baseMapper.insert(list); + } + + @Override + @ContainerMethod(namespace = ContainerConstants.USER_ROLE_ID_LIST, type = MappingType.ORDER_OF_KEYS) + public List listRoleIdByUserId(Long userId) { + return baseMapper.lambdaQuery() + .select(UserRoleDO::getRoleId) + .eq(UserRoleDO::getUserId, userId) + .list() + .stream() + .map(UserRoleDO::getRoleId) + .toList(); + } + + @Override + public List listUserIdByRoleId(Long roleId) { + return baseMapper.lambdaQuery() + .select(UserRoleDO::getUserId) + .eq(UserRoleDO::getRoleId, roleId) + .list() + .stream() + .map(UserRoleDO::getUserId) + .toList(); + } + + @Override + public boolean isRoleIdExists(List roleIds) { + if (CollUtil.isEmpty(roleIds)) { + return false; + } + return baseMapper.lambdaQuery().in(UserRoleDO::getRoleId, roleIds).exists(); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..367d94e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserServiceImpl.java @@ -0,0 +1,739 @@ +package top.ysoft.admin.system.service.impl; + +import cn.crane4j.annotation.ContainerMethod; +import cn.crane4j.annotation.MappingType; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.UUID; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.validation.ValidationUtil; +import cn.hutool.http.ContentType; +import cn.hutool.json.JSONUtil; +import com.alibaba.excel.EasyExcel; +import com.alicp.jetcache.anno.CacheInvalidate; +import com.alicp.jetcache.anno.CacheType; +import com.alicp.jetcache.anno.CacheUpdate; +import com.alicp.jetcache.anno.Cached; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.ahoo.cosid.IdGenerator; +import me.ahoo.cosid.provider.DefaultIdGeneratorProvider; +import net.dreamlu.mica.core.result.R; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import top.ysoft.admin.auth.service.OnlineUserService; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.common.constant.ContainerConstants; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.common.context.UserContext; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.enums.GenderEnum; +import top.ysoft.admin.common.service.CommonUserService; +import top.ysoft.admin.common.util.SecureUtils; +import top.ysoft.admin.system.enums.OptionCategoryEnum; +import top.ysoft.admin.system.mapper.UserMapper; +import top.ysoft.admin.system.model.entity.DeptDO; +import top.ysoft.admin.system.model.entity.RoleDO; +import top.ysoft.admin.system.model.entity.UserDO; +import top.ysoft.admin.system.model.entity.UserRoleDO; +import top.ysoft.admin.system.model.query.UserQuery; +import top.ysoft.admin.system.model.req.user.*; +import top.ysoft.admin.system.model.resp.user.UserDetailResp; +import top.ysoft.admin.system.model.resp.user.UserImportParseResp; +import top.ysoft.admin.system.model.resp.user.UserImportResp; +import top.ysoft.admin.system.model.resp.user.UserResp; +import top.ysoft.admin.system.service.*; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.exception.BusinessException; +import top.continew.starter.core.util.SpringUtils; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.query.SortQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.continew.starter.web.util.FileUploadUtils; + +import java.io.IOException; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static top.ysoft.admin.system.enums.ImportPolicyEnum.*; +import static top.ysoft.admin.system.enums.PasswordPolicyEnum.*; + +/** + * 用户业务实现 + * + * @author Charles7c + * @since 2022/12/21 21:49 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class UserServiceImpl extends BaseServiceImpl implements UserService, CommonUserService { + + private final PasswordEncoder passwordEncoder; + private final UserPasswordHistoryService userPasswordHistoryService; + private final OnlineUserService onlineUserService; + private final OptionService optionService; + private final UserRoleService userRoleService; + private final RoleService roleService; + private final FileService fileService; + private final FileStorageService fileStorageService; + + @Resource + private DeptService deptService; + @Value("${avatar.support-suffix}") + private String[] avatarSupportSuffix; + @Value("${avatar.path}") + private String avatarPath; + + @Override + public PageResp page(UserQuery query, PageQuery pageQuery) { + QueryWrapper queryWrapper = this.buildQueryWrapper(query); + super.sort(queryWrapper, pageQuery); + IPage page = baseMapper.selectUserPage(new Page<>(pageQuery.getPage(), pageQuery + .getSize()), queryWrapper); + PageResp pageResp = PageResp.build(page, super.getListClass()); + pageResp.getList().forEach(this::fill); + return pageResp; + } + + @Override + public void beforeAdd(UserReq req) { + final String errorMsgTemplate = "新增失败,[{}] 已存在"; + String username = req.getUsername(); + CheckUtils.throwIf(this.isNameExists(username, null), errorMsgTemplate, username); + String email = req.getEmail(); + CheckUtils.throwIf(StrUtil.isNotBlank(email) && this.isEmailExists(email, null), errorMsgTemplate, email); + String phone = req.getPhone(); + CheckUtils.throwIf(StrUtil.isNotBlank(phone) && this.isPhoneExists(phone, null), errorMsgTemplate, phone); + } + + @Override + public void afterAdd(UserReq req, UserDO user) { + Long userId = user.getId(); + baseMapper.lambdaUpdate().set(UserDO::getPwdResetTime, LocalDateTime.now()).eq(UserDO::getId, userId).update(); + // 保存用户和角色关联 + userRoleService.assignRolesToUser(req.getRoleIds(), userId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @CacheUpdate(key = "#id", value = "#req.nickname", name = CacheConstants.USER_KEY_PREFIX) + public void update(UserReq req, Long id) { + final String errorMsgTemplate = "修改失败,[{}] 已存在"; + String username = req.getUsername(); + CheckUtils.throwIf(this.isNameExists(username, id), errorMsgTemplate, username); + String email = req.getEmail(); + CheckUtils.throwIf(StrUtil.isNotBlank(email) && this.isEmailExists(email, id), errorMsgTemplate, email); + String phone = req.getPhone(); + CheckUtils.throwIf(StrUtil.isNotBlank(phone) && this.isPhoneExists(phone, id), errorMsgTemplate, phone); + DisEnableStatusEnum newStatus = req.getStatus(); + CheckUtils.throwIf(DisEnableStatusEnum.DISABLE.equals(newStatus) && ObjectUtil.equal(id, UserContextHolder + .getUserId()), "不允许禁用当前用户"); + UserDO oldUser = super.getById(id); + if (Boolean.TRUE.equals(oldUser.getIsSystem())) { + CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, newStatus, "[{}] 是系统内置用户,不允许禁用", oldUser + .getNickname()); + Collection disjunctionRoleIds = CollUtil.disjunction(req.getRoleIds(), userRoleService + .listRoleIdByUserId(id)); + CheckUtils.throwIfNotEmpty(disjunctionRoleIds, "[{}] 是系统内置用户,不允许变更角色", oldUser.getNickname()); + } + // 更新信息 + UserDO newUser = BeanUtil.toBean(req, UserDO.class); + newUser.setId(id); + baseMapper.updateById(newUser); + // 保存用户和角色关联 + boolean isSaveUserRoleSuccess = userRoleService.assignRolesToUser(req.getRoleIds(), id); + // 如果禁用用户,则踢出在线用户 + if (DisEnableStatusEnum.DISABLE.equals(newStatus)) { + onlineUserService.kickOut(id); + return; + } + // 如果角色有变更,则更新在线用户权限信息 + if (isSaveUserRoleSuccess) { + this.updateContext(id); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + @CacheInvalidate(key = "#ids", name = CacheConstants.USER_KEY_PREFIX, multi = true) + public void delete(List ids) { + CheckUtils.throwIf(CollUtil.contains(ids, UserContextHolder.getUserId()), "不允许删除当前用户"); + List list = baseMapper.lambdaQuery() + .select(UserDO::getNickname, UserDO::getIsSystem) + .in(UserDO::getId, ids) + .list(); + Optional isSystemData = list.stream().filter(UserDO::getIsSystem).findFirst(); + CheckUtils.throwIf(isSystemData::isPresent, "所选用户 [{}] 是系统内置用户,不允许删除", isSystemData.orElseGet(UserDO::new) + .getNickname()); + // 删除用户和角色关联 + userRoleService.deleteByUserIds(ids); + // 删除历史密码 + userPasswordHistoryService.deleteByUserIds(ids); + // 删除用户 + super.delete(ids); + // 踢出在线用户 + ids.forEach(onlineUserService::kickOut); + } + + @Override + @Cached(key = "#id", name = CacheConstants.USER_KEY_PREFIX, cacheType = CacheType.BOTH, syncLocal = true) + public String getNicknameById(Long id) { + return baseMapper.selectNicknameById(id); + } + + @Override + public void downloadImportTemplate(HttpServletResponse response) throws IOException { + try { + FileUploadUtils.download(response, ResourceUtil.getStream("templates/import/user.xlsx"), "用户导入模板.xlsx"); + } catch (Exception e) { + log.error("下载用户导入模板失败:{}", e.getMessage(), e); + response.setCharacterEncoding(CharsetUtil.UTF_8); + response.setContentType(ContentType.JSON.toString()); + response.getWriter().write(JSONUtil.toJsonStr(R.fail("下载用户导入模板失败"))); + } + } + + @Override + public UserImportParseResp parseImport(MultipartFile file) { + UserImportParseResp userImportResp = new UserImportParseResp(); + List importRowList; + // 读取表格数据 + try { + importRowList = EasyExcel.read(file.getInputStream()) + .head(UserImportRowReq.class) + .sheet() + .headRowNumber(1) + .doReadSync(); + } catch (Exception e) { + log.error("用户导入数据文件解析异常:{}", e.getMessage(), e); + throw new BusinessException("数据文件解析异常"); + } + // 总计行数 + userImportResp.setTotalRows(importRowList.size()); + CheckUtils.throwIfEmpty(importRowList, "数据文件格式错误"); + // 有效行数:过滤无效数据 + List validRowList = this.filterImportData(importRowList); + userImportResp.setValidRows(validRowList.size()); + CheckUtils.throwIfEmpty(validRowList, "数据文件格式错误"); + + // 检测表格内数据是否合法 + Set seenEmails = new HashSet<>(); + boolean hasDuplicateEmail = validRowList.stream() + .map(UserImportRowReq::getEmail) + .anyMatch(email -> email != null && !seenEmails.add(email)); + CheckUtils.throwIf(hasDuplicateEmail, "存在重复邮箱,请检测数据"); + Set seenPhones = new HashSet<>(); + boolean hasDuplicatePhone = validRowList.stream() + .map(UserImportRowReq::getPhone) + .anyMatch(phone -> phone != null && !seenPhones.add(phone)); + CheckUtils.throwIf(hasDuplicatePhone, "存在重复手机,请检测数据"); + + // 校验是否存在错误角色 + List roleNames = validRowList.stream().map(UserImportRowReq::getRoleName).distinct().toList(); + int existRoleCount = roleService.countByNames(roleNames); + CheckUtils.throwIf(existRoleCount < roleNames.size(), "存在错误角色,请检查数据"); + // 校验是否存在错误部门 + List deptNames = validRowList.stream().map(UserImportRowReq::getDeptName).distinct().toList(); + int existDeptCount = deptService.countByNames(deptNames); + CheckUtils.throwIf(existDeptCount < deptNames.size(), "存在错误部门,请检查数据"); + + // 查询重复用户 + userImportResp + .setDuplicateUserRows(countExistByField(validRowList, UserImportRowReq::getUsername, UserDO::getUsername, false)); + // 查询重复邮箱 + userImportResp + .setDuplicateEmailRows(countExistByField(validRowList, UserImportRowReq::getEmail, UserDO::getEmail, true)); + // 查询重复手机 + userImportResp + .setDuplicatePhoneRows(countExistByField(validRowList, UserImportRowReq::getPhone, UserDO::getPhone, true)); + + // 设置导入会话并缓存数据,有效期10分钟 + String importKey = UUID.fastUUID().toString(true); + RedisUtils.set(CacheConstants.DATA_IMPORT_KEY + importKey, JSONUtil.toJsonStr(validRowList), Duration + .ofMinutes(10)); + userImportResp.setImportKey(importKey); + return userImportResp; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public UserImportResp importUser(UserImportReq req) { + // 校验导入会话是否过期 + List importUserList; + try { + String data = RedisUtils.get(CacheConstants.DATA_IMPORT_KEY + req.getImportKey()); + importUserList = JSONUtil.toList(data, UserImportRowReq.class); + CheckUtils.throwIf(CollUtil.isEmpty(importUserList), "导入已过期,请重新上传"); + } catch (Exception e) { + log.error("导入异常:", e); + throw new BusinessException("导入已过期,请重新上传"); + } + // 已存在数据查询 + List existEmails = listExistByField(importUserList, UserImportRowReq::getEmail, UserDO::getEmail); + List existPhones = listExistByField(importUserList, UserImportRowReq::getPhone, UserDO::getPhone); + List existUserList = listByUsernames(importUserList.stream() + .map(UserImportRowReq::getUsername) + .filter(Objects::nonNull) + .toList()); + List existUsernames = existUserList.stream().map(UserDO::getUsername).toList(); + CheckUtils + .throwIf(isExitImportUser(req, importUserList, existUsernames, existEmails, existPhones), "数据不符合导入策略,已退出导入"); + + // 基础数据准备 + Map userMap = existUserList.stream() + .collect(Collectors.toMap(UserDO::getUsername, UserDO::getId)); + List roleList = roleService.listByNames(importUserList.stream() + .map(UserImportRowReq::getRoleName) + .distinct() + .toList()); + Map roleMap = roleList.stream().collect(Collectors.toMap(RoleDO::getName, RoleDO::getId)); + List deptList = deptService.listByNames(importUserList.stream() + .map(UserImportRowReq::getDeptName) + .distinct() + .toList()); + Map deptMap = deptList.stream().collect(Collectors.toMap(DeptDO::getName, DeptDO::getId)); + + // 批量操作数据库集合 + List insertList = new ArrayList<>(); + List updateList = new ArrayList<>(); + List userRoleDOList = new ArrayList<>(); + // ID生成器 + IdGenerator idGenerator = DefaultIdGeneratorProvider.INSTANCE.getShare(); + for (UserImportRowReq row : importUserList) { + if (isSkipUserImport(req, row, existUsernames, existPhones, existEmails)) { + // 按规则跳过该行 + continue; + } + UserDO userDO = BeanUtil.toBeanIgnoreError(row, UserDO.class); + userDO.setStatus(req.getDefaultStatus()); + userDO.setPwdResetTime(LocalDateTime.now()); + userDO.setGender(EnumUtil.getBy(GenderEnum::getDescription, row.getGender(), GenderEnum.UNKNOWN)); + userDO.setDeptId(deptMap.get(row.getDeptName())); + // 修改 or 新增 + if (UPDATE.validate(req.getDuplicateUser(), row.getUsername(), existUsernames)) { + userDO.setId(userMap.get(row.getUsername())); + updateList.add(userDO); + } else { + userDO.setId(idGenerator.generate()); + userDO.setIsSystem(false); + insertList.add(userDO); + } + userRoleDOList.add(new UserRoleDO(userDO.getId(), roleMap.get(row.getRoleName()))); + } + doImportUser(insertList, updateList, userRoleDOList); + RedisUtils.delete(CacheConstants.DATA_IMPORT_KEY + req.getImportKey()); + return new UserImportResp(insertList.size() + updateList.size(), insertList.size(), updateList.size()); + } + + @Override + public void resetPassword(UserPasswordResetReq req, Long id) { + super.getById(id); + baseMapper.lambdaUpdate() + .set(UserDO::getPassword, req.getNewPassword()) + .set(UserDO::getPwdResetTime, LocalDateTime.now()) + .eq(UserDO::getId, id) + .update(); + } + + @Override + public void updateRole(UserRoleUpdateReq updateReq, Long id) { + super.getById(id); + List roleIds = updateReq.getRoleIds(); + // 保存用户和角色关联 + userRoleService.assignRolesToUser(roleIds, id); + // 更新用户上下文 + this.updateContext(id); + } + + @Override + public String updateAvatar(MultipartFile avatarFile, Long id) throws IOException { + String avatarImageType = FileNameUtil.extName(avatarFile.getOriginalFilename()); + CheckUtils.throwIf(!StrUtil.equalsAnyIgnoreCase(avatarImageType, avatarSupportSuffix), "头像仅支持 {} 格式的图片", String + .join(StringConstants.CHINESE_COMMA, avatarSupportSuffix)); + // 上传新头像 + UserDO user = super.getById(id); + FileInfo fileInfo = fileService.upload(avatarFile, avatarPath, null, false); + // 更新用户头像 + String newAvatar = fileInfo.getUrl(); + baseMapper.lambdaUpdate().set(UserDO::getAvatar, newAvatar).eq(UserDO::getId, id).update(); + // 删除原头像 + String oldAvatar = user.getAvatar(); + if (StrUtil.isNotBlank(oldAvatar)) { + fileStorageService.delete(oldAvatar); + } + return newAvatar; + } + + @Override + @CacheUpdate(key = "#id", value = "#req.nickname", name = CacheConstants.USER_KEY_PREFIX) + public void updateBasicInfo(UserBasicInfoUpdateReq req, Long id) { + super.getById(id); + baseMapper.lambdaUpdate() + .set(UserDO::getNickname, req.getNickname()) + .set(UserDO::getGender, req.getGender()) + .eq(UserDO::getId, id) + .update(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePassword(String oldPassword, String newPassword, Long id) { + CheckUtils.throwIfEqual(newPassword, oldPassword, "新密码不能与当前密码相同"); + UserDO user = super.getById(id); + String password = user.getPassword(); + if (StrUtil.isNotBlank(password)) { + CheckUtils.throwIf(!passwordEncoder.matches(oldPassword, password), "当前密码错误"); + } + // 校验密码合法性 + int passwordRepetitionTimes = this.checkPassword(newPassword, user); + // 更新密码和密码重置时间 + baseMapper.lambdaUpdate() + .set(UserDO::getPassword, newPassword) + .set(UserDO::getPwdResetTime, LocalDateTime.now()) + .eq(UserDO::getId, id) + .update(); + // 保存历史密码 + userPasswordHistoryService.add(id, password, passwordRepetitionTimes); + // 修改后登出 + StpUtil.logout(); + } + + @Override + public void updatePhone(String newPhone, String oldPassword, Long id) { + UserDO user = super.getById(id); + CheckUtils.throwIf(!passwordEncoder.matches(oldPassword, user.getPassword()), "当前密码错误"); + CheckUtils.throwIf(this.isPhoneExists(newPhone, id), "手机号已绑定其他账号,请更换其他手机号"); + CheckUtils.throwIfEqual(newPhone, user.getPhone(), "新手机号不能与当前手机号相同"); + // 更新手机号 + baseMapper.lambdaUpdate().set(UserDO::getPhone, newPhone).eq(UserDO::getId, id).update(); + } + + @Override + public void updateEmail(String newEmail, String oldPassword, Long id) { + UserDO user = super.getById(id); + CheckUtils.throwIf(!passwordEncoder.matches(oldPassword, user.getPassword()), "当前密码错误"); + CheckUtils.throwIf(this.isEmailExists(newEmail, id), "邮箱已绑定其他账号,请更换其他邮箱"); + CheckUtils.throwIfEqual(newEmail, user.getEmail(), "新邮箱不能与当前邮箱相同"); + // 更新邮箱 + baseMapper.lambdaUpdate().set(UserDO::getEmail, newEmail).eq(UserDO::getId, id).update(); + } + + @Override + public UserDO getByUsername(String username) { + return baseMapper.selectByUsername(username); + } + + @Override + public UserDO getByPhone(String phone) { + return baseMapper.selectByPhone(phone); + } + + @Override + public UserDO getByEmail(String email) { + return baseMapper.selectByEmail(email); + } + + @Override + public Long countByDeptIds(List deptIds) { + if (CollUtil.isEmpty(deptIds)) { + return 0L; + } + return baseMapper.lambdaQuery().in(UserDO::getDeptId, deptIds).count(); + } + + @Override + protected List list(UserQuery query, SortQuery sortQuery, Class targetClass) { + QueryWrapper queryWrapper = this.buildQueryWrapper(query); + // 设置排序 + super.sort(queryWrapper, sortQuery); + List entityList = baseMapper.selectUserList(queryWrapper); + if (this.getEntityClass() == targetClass) { + return (List)entityList; + } + return BeanUtil.copyToList(entityList, targetClass); + } + + @Override + protected QueryWrapper buildQueryWrapper(UserQuery query) { + String description = query.getDescription(); + DisEnableStatusEnum status = query.getStatus(); + List createTimeList = query.getCreateTime(); + Long deptId = query.getDeptId(); + List userIdList = query.getUserIds(); + // 获取排除用户 ID 列表 + List excludeUserIdList = null; + if (null != query.getRoleId()) { + excludeUserIdList = userRoleService.listUserIdByRoleId(query.getRoleId()); + } + return new QueryWrapper().and(StrUtil.isNotBlank(description), q -> q.like("t1.username", description) + .or() + .like("t1.nickname", description) + .or() + .like("t1.description", description)) + .eq(null != status, "t1.status", status) + .between(CollUtil.isNotEmpty(createTimeList), "t1.create_time", CollUtil.getFirst(createTimeList), CollUtil + .getLast(createTimeList)) + .and(null != deptId && !SysConstants.SUPER_DEPT_ID.equals(deptId), q -> { + List deptIdList = deptService.listChildren(deptId) + .stream() + .map(DeptDO::getId) + .collect(Collectors.toList()); + deptIdList.add(deptId); + q.in("t1.dept_id", deptIdList); + }) + .in(CollUtil.isNotEmpty(userIdList), "t1.id", userIdList) + .notIn(CollUtil.isNotEmpty(excludeUserIdList), "t1.id", excludeUserIdList); + } + + /** + * 导入用户 + * + * @param insertList 新增用户 + * @param updateList 修改用户 + * @param userRoleDOList 用户角色关联 + */ + private void doImportUser(List insertList, List updateList, List userRoleDOList) { + if (CollUtil.isNotEmpty(insertList)) { + baseMapper.insert(insertList); + } + if (CollUtil.isNotEmpty(updateList)) { + SpringUtils.getProxy(this).updateBatchById(updateList); + userRoleService.deleteByUserIds(updateList.stream().map(UserDO::getId).toList()); + } + if (CollUtil.isNotEmpty(userRoleDOList)) { + userRoleService.saveBatch(userRoleDOList); + } + } + + /** + * 判断是否跳过导入 + * + * @param req 导入参数 + * @param row 导入数据 + * @param existUsernames 导入数据中已存在的用户名 + * @param existEmails 导入数据中已存在的邮箱 + * @param existPhones 导入数据中已存在的手机号 + * @return 是否跳过 + */ + private boolean isSkipUserImport(UserImportReq req, + UserImportRowReq row, + List existUsernames, + List existPhones, + List existEmails) { + return SKIP.validate(req.getDuplicateUser(), row.getUsername(), existUsernames) || SKIP.validate(req + .getDuplicateEmail(), row.getEmail(), existEmails) || SKIP.validate(req.getDuplicatePhone(), row + .getPhone(), existPhones); + } + + /** + * 判断是否退出导入 + * + * @param req 导入参数 + * @param list 导入数据 + * @param existUsernames 导入数据中已存在的用户名 + * @param existEmails 导入数据中已存在的邮箱 + * @param existPhones 导入数据中已存在的手机号 + * @return 是否退出 + */ + private boolean isExitImportUser(UserImportReq req, + List list, + List existUsernames, + List existEmails, + List existPhones) { + return list.stream() + .anyMatch(row -> EXIT.validate(req.getDuplicateUser(), row.getUsername(), existUsernames) || EXIT + .validate(req.getDuplicateEmail(), row.getEmail(), existEmails) || EXIT.validate(req + .getDuplicatePhone(), row.getPhone(), existPhones)); + } + + /** + * 按指定数据集获取数据库已存在的数量 + * + * @param userRowList 导入的数据源 + * @param rowField 导入数据的字段 + * @param dbField 对比数据库的字段 + * @return 存在的数量 + */ + private int countExistByField(List userRowList, + Function rowField, + SFunction dbField, + boolean fieldEncrypt) { + List fieldValues = userRowList.stream().map(rowField).filter(Objects::nonNull).toList(); + if (fieldValues.isEmpty()) { + return 0; + } + return (int)this.count(Wrappers.lambdaQuery() + .in(dbField, fieldEncrypt ? SecureUtils.encryptFieldByAes(fieldValues) : fieldValues)); + } + + /** + * 按指定数据集获取数据库已存在内容 + * + * @param userRowList 导入的数据源 + * @param rowField 导入数据的字段 + * @param dbField 对比数据库的字段 + * @return 存在的内容 + */ + private List listExistByField(List userRowList, + Function rowField, + SFunction dbField) { + List fieldValues = userRowList.stream().map(rowField).filter(Objects::nonNull).toList(); + if (fieldValues.isEmpty()) { + return Collections.emptyList(); + } + List userDOList = baseMapper.selectList(Wrappers.lambdaQuery() + .in(dbField, SecureUtils.encryptFieldByAes(fieldValues)) + .select(dbField)); + return userDOList.stream().map(dbField).filter(Objects::nonNull).toList(); + } + + /** + * 过滤无效的导入用户数据(批量导入不严格校验数据) + * + * @param importRowList 导入数据 + */ + private List filterImportData(List importRowList) { + // 校验过滤 + List list = importRowList.stream() + .filter(row -> ValidationUtil.validate(row).isEmpty()) + .toList(); + // 用户名去重 + return list.stream() + .collect(Collectors.toMap(UserImportRowReq::getUsername, user -> user, (existing, replacement) -> existing)) + .values() + .stream() + .toList(); + } + + /** + * 检测密码合法性 + * + * @param password 密码 + * @param user 用户信息 + * @return 密码允许重复使用次数 + */ + private int checkPassword(String password, UserDO user) { + Map passwordPolicy = optionService.getByCategory(OptionCategoryEnum.PASSWORD); + // 密码最小长度 + PASSWORD_MIN_LENGTH.validate(password, MapUtil.getInt(passwordPolicy, PASSWORD_MIN_LENGTH.name()), user); + // 密码是否必须包含特殊字符 + PASSWORD_REQUIRE_SYMBOLS.validate(password, MapUtil.getInt(passwordPolicy, PASSWORD_REQUIRE_SYMBOLS + .name()), user); + // 密码是否允许包含正反序账号名 + PASSWORD_ALLOW_CONTAIN_USERNAME.validate(password, MapUtil + .getInt(passwordPolicy, PASSWORD_ALLOW_CONTAIN_USERNAME.name()), user); + // 密码重复使用次数 + int passwordRepetitionTimes = MapUtil.getInt(passwordPolicy, PASSWORD_REPETITION_TIMES.name()); + PASSWORD_REPETITION_TIMES.validate(password, passwordRepetitionTimes, user); + return passwordRepetitionTimes; + } + + /** + * 名称是否存在 + * + * @param name 名称 + * @param id ID + * @return 是否存在 + */ + private boolean isNameExists(String name, Long id) { + return baseMapper.lambdaQuery().eq(UserDO::getUsername, name).ne(null != id, UserDO::getId, id).exists(); + } + + /** + * 邮箱是否存在 + * + * @param email 邮箱 + * @param id ID + * @return 是否存在 + */ + private boolean isEmailExists(String email, Long id) { + Long count = baseMapper.selectCountByEmail(email, id); + return null != count && count > 0; + } + + /** + * 手机号码是否存在 + * + * @param phone 手机号码 + * @param id ID + * @return 是否存在 + */ + private boolean isPhoneExists(String phone, Long id) { + Long count = baseMapper.selectCountByPhone(phone, id); + return null != count && count > 0; + } + + /** + * 根据用户名获取用户列表 + * + * @param usernames 用户名列表 + * @return 用户列表 + */ + private List listByUsernames(List usernames) { + return this.list(Wrappers.lambdaQuery() + .in(UserDO::getUsername, usernames) + .select(UserDO::getId, UserDO::getUsername)); + } + + /** + * 更新用户上下文信息 + * + * @param id ID + */ + private void updateContext(Long id) { + UserContext userContext = UserContextHolder.getContext(id); + if (null != userContext) { + userContext.setRoles(roleService.listByUserId(id)); + userContext.setPermissions(roleService.listPermissionByUserId(id)); + UserContextHolder.setContext(userContext); + } + } + + @Override + @ContainerMethod(namespace = ContainerConstants.USER_NAME_LIST, type = MappingType.ORDER_OF_KEYS) + public List listNameByIds(List ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + List roleList = baseMapper.lambdaQuery().select(UserDO::getUsername).in(UserDO::getId, ids).list(); + return roleList.stream().map(UserDO::getUsername).toList(); + } + + @Override + @ContainerMethod(namespace = ContainerConstants.USER_NAME, type = MappingType.ORDER_OF_KEYS) + public String userNameByIds(Long id) { + if (null == id) { + return ""; + } + + UserDO userDO = baseMapper.lambdaQuery().eq(UserDO::getId, id).one(); + return null == userDO ? "" : userDO.getUsername(); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserSocialServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserSocialServiceImpl.java new file mode 100644 index 0000000..3691119 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserSocialServiceImpl.java @@ -0,0 +1,77 @@ +package top.ysoft.admin.system.service.impl; + +import cn.hutool.json.JSONUtil; +import lombok.RequiredArgsConstructor; +import me.zhyd.oauth.model.AuthUser; +import org.springframework.stereotype.Service; +import top.ysoft.admin.system.enums.SocialSourceEnum; +import top.ysoft.admin.system.mapper.UserSocialMapper; +import top.ysoft.admin.system.model.entity.UserSocialDO; +import top.ysoft.admin.system.service.UserSocialService; +import top.continew.starter.core.validation.CheckUtils; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 用户社会化关联业务实现 + * + * @author Charles7c + * @since 2023/10/11 22:10 + */ +@Service +@RequiredArgsConstructor +public class UserSocialServiceImpl implements UserSocialService { + + private final UserSocialMapper baseMapper; + + @Override + public UserSocialDO getBySourceAndOpenId(String source, String openId) { + return baseMapper.selectBySourceAndOpenId(source, openId); + } + + @Override + public void saveOrUpdate(UserSocialDO userSocial) { + if (null == userSocial.getCreateTime()) { + baseMapper.insert(userSocial); + } else { + baseMapper.lambdaUpdate() + .set(UserSocialDO::getMetaJson, userSocial.getMetaJson()) + .set(UserSocialDO::getLastLoginTime, userSocial.getLastLoginTime()) + .eq(UserSocialDO::getSource, userSocial.getSource()) + .eq(UserSocialDO::getOpenId, userSocial.getOpenId()) + .update(); + } + } + + @Override + public List listByUserId(Long userId) { + return baseMapper.lambdaQuery().eq(UserSocialDO::getUserId, userId).list(); + } + + @Override + public void bind(AuthUser authUser, Long userId) { + String source = authUser.getSource(); + String openId = authUser.getUuid(); + List userSocialList = this.listByUserId(userId); + Set boundSocialSet = userSocialList.stream().map(UserSocialDO::getSource).collect(Collectors.toSet()); + String description = SocialSourceEnum.valueOf(source).getDescription(); + CheckUtils.throwIf(boundSocialSet.contains(source), "您已经绑定过了 [{}] 平台,请先解绑", description); + UserSocialDO userSocial = this.getBySourceAndOpenId(source, openId); + CheckUtils.throwIfNotNull(userSocial, "[{}] 平台账号 [{}] 已被其他用户绑定", description, authUser.getUsername()); + userSocial = new UserSocialDO(); + userSocial.setUserId(userId); + userSocial.setSource(source); + userSocial.setOpenId(openId); + userSocial.setMetaJson(JSONUtil.toJsonStr(authUser)); + userSocial.setLastLoginTime(LocalDateTime.now()); + baseMapper.insert(userSocial); + } + + @Override + public void deleteBySourceAndUserId(String source, Long userId) { + baseMapper.lambdaUpdate().eq(UserSocialDO::getSource, source).eq(UserSocialDO::getUserId, userId).remove(); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/system/validation/ValidationGroup.java b/wms-module-system/src/main/java/top/wms/admin/system/validation/ValidationGroup.java new file mode 100644 index 0000000..4b4ca96 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/system/validation/ValidationGroup.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.system.validation; + +import jakarta.validation.groups.Default; + +/** + * 分组校验 + * + * @author Charles7c + * @since 2024/7/3 22:01 + */ +public interface ValidationGroup extends Default { + + /** + * 分组校验-增删改查 + */ + interface Storage extends ValidationGroup { + /** + * 本地存储 + */ + interface Local extends Storage { + } + + /** + * 对象存储 + */ + interface OSS extends Storage { + } + } +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/BackInfo.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/BackInfo.java new file mode 100644 index 0000000..9136250 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/BackInfo.java @@ -0,0 +1,81 @@ +package top.ysoft.admin.yfApi.domain; + +import lombok.Data; + +/** + * Uface回调参数 持久化对象 + * + * @author nichun + */ +@Data +public class BackInfo { + + /** 设备序列号 **/ + protected String deviceKey; + + /** 识别记录毫秒级时间戳 */ + protected String time; + + /** 设备当前 IP 地址 " */ + protected String ip; + + /** 现场照保存路径 " */ + protected String path; + + /** 人员 ID .陌生人为 STRANGERBAB " */ + protected String personId; + + /** personId 对应的卡号 " */ + protected String idcardNum; + + /** 识别模式 " */ + protected String model; + + /** 活体判断结果 " */ + protected String aliveType; + + /** 人员比对结果 " */ + protected String identifyType; + + /** 有效时间段判断 " */ + protected String passTimeType; + + /** 有效日期判断 " */ + protected String permissionTimeType; + + /** 识别模式判断 " */ + protected String recModeType; + + /** 身份证信息 " */ + protected String data; + + /** 识别方式_人员类型 " */ + protected String type; + + /** 现场照 base64 码 " */ + protected String base64; + + /** 识别方式" */ + protected String recType; + + /** 人员测量温度值" */ + protected String temperature; + + /** 设置的体温异常标准" */ + protected String standard; + + /** 体温状态" */ + protected String temperatureState; + + /** 温度单位" */ + protected String tempUnit; + + private Integer index; + + private String attendanceStatus; + + @Override + public String toString() { + return "BackInfo{" + "deviceKey='" + deviceKey + '\'' + ", time='" + time + '\'' + ", ip='" + ip + '\'' + ", path='" + path + '\'' + ", personId='" + personId + '\'' + ", idcardNum='" + idcardNum + '\'' + ", model='" + model + '\'' + ", aliveType='" + aliveType + '\'' + ", identifyType='" + identifyType + '\'' + ", passTimeType='" + passTimeType + '\'' + ", permissionTimeType='" + permissionTimeType + '\'' + ", recModeType='" + recModeType + '\'' + ", data='" + data + '\'' + ", type='" + type + '\'' + ", base64='" + base64 + '\'' + ", recType='" + recType + '\'' + ", temperature='" + temperature + '\'' + ", standard='" + standard + '\'' + ", temperatureState='" + temperatureState + '\'' + ", tempUnit='" + tempUnit + '\'' + '}'; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/BackResult.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/BackResult.java new file mode 100644 index 0000000..d654d2d --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/BackResult.java @@ -0,0 +1,35 @@ +package top.ysoft.admin.yfApi.domain; + +import lombok.Data; + +import java.util.HashMap; + +/** + * Uface回调参数 持久化对象 + * + * @author nichun + */ +@Data +public class BackResult extends HashMap { + + /** 返回内容 */ + public static final String RESULT_TAG = "result"; + + /** 数据对象 */ + public static final String SUCCESS_TAG = "success"; + + public BackResult() { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public BackResult(int code, Boolean msg) { + super.put(RESULT_TAG, code); + super.put(SUCCESS_TAG, msg); + } + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/DingtalkCode.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/DingtalkCode.java new file mode 100644 index 0000000..eeb4ce0 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/DingtalkCode.java @@ -0,0 +1,22 @@ +package top.ysoft.admin.yfApi.domain; + +import lombok.Data; + +@Data +public class DingtalkCode { + + /** + * sys_people的工号 + */ + private String userId; + + /** + * 二维码过期 + */ + private String message; + + /** + * 错误类型 + */ + private String code; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/DingtalkMsgVo.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/DingtalkMsgVo.java new file mode 100644 index 0000000..ccd9145 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/DingtalkMsgVo.java @@ -0,0 +1,27 @@ +package top.ysoft.admin.yfApi.domain; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +/** + * 钉钉消息 + */ +@Data +public class DingtalkMsgVo { + /** + * 消息类型 + */ + private String msgtype; + + private Text text; + + @Getter + @Setter + public static class Text { + /** + * 类容 + */ + private String content; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/EventResult.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/EventResult.java new file mode 100644 index 0000000..8885cea --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/EventResult.java @@ -0,0 +1,22 @@ +package top.ysoft.admin.yfApi.domain; + +import lombok.Data; + +/** + * Uface回调参数 持久化对象 + * + * @author nichun + */ +@Data +public class EventResult { + + /** 事件Guid,唯一不重复标志 */ + protected String eventGuid; + + /** 事件编码,具体见文档描述 */ + protected String eventCode; + + /** 具体时间内容(JSON格式),具体见文档描述 */ + protected String eventMsg; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/EwmInfo.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/EwmInfo.java new file mode 100644 index 0000000..54a0a2f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/EwmInfo.java @@ -0,0 +1,37 @@ +package top.ysoft.admin.yfApi.domain; + +import lombok.Data; + +/** + * Uface回调参数 持久化对象 + * + * @author nichun + */ +@Data +public class EwmInfo { + + /** 设备序列号 **/ + protected String deviceKey; + + /** 识别记录毫秒级时间戳 */ + protected String time; + + /** 设备当前 IP 地址 " */ + protected String ip; + + /** 现场照保存路径 " */ + protected String QRdata; + + /** + * 码值 + */ + private String payCode; + + /** + * 请求id,随机生成 + */ + private String requestId; + + private String qrCode; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/HeartInfo.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/HeartInfo.java new file mode 100644 index 0000000..7a49886 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/HeartInfo.java @@ -0,0 +1,52 @@ +package top.ysoft.admin.yfApi.domain; + +import lombok.Data; + +/** + * Uface回调参数 持久化对象 + * + * @author nichun + */ +@Data +public class HeartInfo { + + /** 设备序列号 **/ + protected String deviceKey; + + /** 识别记录毫秒级时间戳 */ + protected String time; + + /** 设备当前 IP 地址 " */ + protected String ip; + + /** 设备上的人员数量 " */ + protected String personCount; + + /** 设备上的照片数量 " */ + protected String faceCount; + + /** 设备上的指纹数量 " */ + protected String fingerCount; + + /** 设备版本号 " */ + protected String version; + + /** 磁盘剩余空间,单位:M " */ + protected String freeDiskSpace; + + /** CPU 使用率,单位:% " */ + protected String cpuUsageRate; + + /** CPU 温度,单位:℃ " */ + protected String cpuTemperature; + + /** 设备名称 " */ + protected String deviceName; + + /** 设备算法版本 " */ + protected String SDKVersion; + + /** 公司名称 " */ + protected String companyName; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICCardWhite.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICCardWhite.java new file mode 100644 index 0000000..8b9b083 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICCardWhite.java @@ -0,0 +1,40 @@ +package top.ysoft.admin.yfApi.domain; + +import lombok.Data; + +@Data +public class ICCardWhite { + + /** + * 卡号 + */ + private String CodeVal; + + /** + * 类型 + */ + private String CodeType = "C"; + + /** + * 有效起始时间 + */ + private String ValidityStart; + + /** + * 有效结束时间 + */ + private String ValidityEnd; + + /** + * 是否删除 + * 1 为删除 + * 0 为新增或修改 + */ + private String DeleteFlag = "0"; + + /** + * 授权表主键id + */ + private String manageId; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICCheckCode.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICCheckCode.java new file mode 100644 index 0000000..5f8ad07 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICCheckCode.java @@ -0,0 +1,62 @@ +package top.ysoft.admin.yfApi.domain; + +import lombok.Data; + +@Data +public class ICCheckCode { + + /** + * 二维码值,IC 卡卡号 + */ + private String CodeVal; + + /** + * 数据类型 + */ + private String CodeType; + + /** + * 日期时间 + */ + private String BrushTime; + + /** + * 区域 ID + */ + private String ViewId; + + /** + * 设备 ID + */ + private String UID; + + /** + * 授权 KEY + */ + private String UKey; + + /** + * 设备序列号 + */ + private String SN; + + /** + * 是否在线数据 1 为在线验证实时数据,0 为离线脱机数据 + */ + private String IsOnline; + + /** + * 脱机数据合法卡性 + */ + private String Property; + + /** + * 时间戳 + */ + private String Timestamp; + + /** + * 签名 + */ + private String Sign; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICQueryCmd.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICQueryCmd.java new file mode 100644 index 0000000..14baacd --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICQueryCmd.java @@ -0,0 +1,48 @@ +package top.ysoft.admin.yfApi.domain; + +import lombok.Data; + +@Data +public class ICQueryCmd { + + /** + * 区域 ID + */ + private String ViewId; + + /** + * 设备 ID + */ + private String UID; + + /** + * 授权 KEY + */ + private String UKey; + + /** + * 设备序列号 + */ + private String SN; + + /** + * 设备防拆状态 + */ + private String TamperAlarm; + + /** + * 门状态 0 门关闭状态 + * 1 门打开状态 + */ + private String DoorMagnetic; + + /** + * 时间戳 + */ + private String Timestamp; + + /** + * 签名 + */ + private String Sign; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICQueryCmdPostData.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICQueryCmdPostData.java new file mode 100644 index 0000000..a69c6b6 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/ICQueryCmdPostData.java @@ -0,0 +1,60 @@ +package top.ysoft.admin.yfApi.domain; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +@Data +public class ICQueryCmdPostData { + + /** + * 区域 ID + */ + private String ViewId; + + /** + * 设备 ID + */ + private String UID; + + /** + * 授权 Key + */ + private String UKey; + + /** + * 设备序列号 + */ + private String SN; + + /** + * 命令 ID + */ + private String CmdID; + + /** + * 命令代码ICCmdCodeEnum + */ + private Integer CmdCode; + + /** + * 命令状态 + * 1 执行成功 + * 2 执行失败 + */ + private Integer CmdStatus; + + /** + * 命令上传参数 + */ + private JSONObject CmdParams; + + /** + * 时间戳 + */ + private String Timestamp; + + /** + * 签名 + */ + private String Sign; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/Person.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/Person.java new file mode 100644 index 0000000..d3e661e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/Person.java @@ -0,0 +1,96 @@ +package top.ysoft.admin.yfApi.domain; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 人员管理对象 person + * + * @author nichun + * @date 2023-03-08 + */ +@Data +public class Person implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 人员Id + */ + private String id; + + /** + * 人员名称 + */ + private String name; + + /** + * 人员卡号 + */ + private String idcardNum; + + /** + * 身份证号 + */ + private String iDNumber; + + /** + * 人像模式权限 1:关 2:开 (默认) + */ + private int facePermission = 2; + + /** + * 刷卡模式权限 1:关 2:开 (默认) + */ + private int idCardPermission = 2; + + /** + * 人卡合一模式权限 1:关(默认) 2:开 + */ + private int faceAndCardPermission = 1; + + /** + * 单一验证方式 指纹验证 1:关(默认) 2:开 + */ + private int fingerPermission = 1; + + /** + * 人证比对模式权限 1:关(默认) 2:开 + */ + private int iDPermission = 1; + + /** + * 人员备注 + */ + private String tag; + + /** + * 手机号 + */ + private String phone; + + /** + * 用户密码 + */ + private String password; + + /** + * 用户密码权限 1:关 (默认) 2:开 + */ + private int passwordPermssion = 1; + + /** + * 人员类型 0:长期人员(默认),1:临时员工 2:黑名单用户, + */ + private int role = 0; + + /** + * 绑定的规则 + */ + private List rule; + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/QrCodeVo.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/QrCodeVo.java new file mode 100644 index 0000000..017cb68 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/QrCodeVo.java @@ -0,0 +1,37 @@ +package top.ysoft.admin.yfApi.domain; + +import lombok.Data; + +@Data +public class QrCodeVo { + + /** + * 设备序列号 + **/ + protected String deviceKey; + + /** + * 识别记录毫秒级时间戳 + */ + protected String time; + + /** + * 设备当前 IP 地址 " + */ + protected String ip; + + /** + * 现场照保存路径 " + */ + protected String qrData; + + /** + * 码值 + */ + private String payCode; + + /** + * 请求id,随机生成 + */ + private String requestId; +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/RuleBody.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/RuleBody.java new file mode 100644 index 0000000..94d92f3 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/domain/RuleBody.java @@ -0,0 +1,70 @@ +package top.ysoft.admin.yfApi.domain; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class RuleBody { + private String pass; + private String ruleId; + private String ruleName; + private int type = 1; + private Map content; + + public String getPass() { + return pass; + } + + public void setPass(String pass) { + this.pass = pass; + } + + public String getRuleId() { + return ruleId; + } + + public void setRuleId(String ruleId) { + this.ruleId = ruleId; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getRuleName() { + return ruleName; + } + + public void setRuleName(String ruleName) { + this.ruleName = ruleName; + } + + public Map getContent() { + return content; + } + + public void setContent(Map content) { + this.content = content; + } + + // 辅助方法:快速构建 content 结构 + public static Map createContent(Long endAt, Long startAt, List> dayList) { + LinkedHashMap content = new LinkedHashMap<>(); + content.put("endAt", endAt); + content.put("startAt", startAt); + content.put("day", dayList); + return content; + } + + public static Map createDayItem(Long endAt, Long startAt, List segments) { + LinkedHashMap dayItem = new LinkedHashMap<>(); + dayItem.put("endAt", endAt); + dayItem.put("segment", segments); + dayItem.put("startAt", startAt); + return dayItem; + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/service/ISysYFService.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/ISysYFService.java new file mode 100644 index 0000000..fcb474e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/ISysYFService.java @@ -0,0 +1,34 @@ +package top.ysoft.admin.yfApi.service; + +import java.util.List; + +/** + * 平台与宇泛交互层 + */ +public interface ISysYFService { + + /** + * 根据规则下发人员信息 + * + * @param peopleIds 人员id列表 + * @param operType 操作类型 + */ + List authDevice(List peopleIds, Integer operType); + + /** + * 下发单个人员信息 + * + * @param equipmentId 设备id + * @param peopleId 人员id + * @param operType 操作类型 + */ + Boolean authDevice(Long peopleId, Long equipmentId, Long ruleId, Integer operType); + + /** + * 批量删除人员信息 + * + * @param peopleIds 人员id列表 + * @param operType 操作类型 + */ + List delDevice(List peopleIds, Integer operType); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/service/IYFListenService.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/IYFListenService.java new file mode 100644 index 0000000..5e05a0f --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/IYFListenService.java @@ -0,0 +1,24 @@ +package top.ysoft.admin.yfApi.service; + +import top.ysoft.admin.yfApi.domain.BackInfo; +import top.ysoft.admin.yfApi.domain.HeartInfo; + +/** + * 接收宇泛推送接口 + */ +public interface IYFListenService { + + /** + * 设备心跳回调 + * + * @param info + */ + void heart(HeartInfo info); + + /** + * 人脸识别回调 + * + * @param info + */ + void processPeopleRecord(BackInfo info); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/service/IYFPushService.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/IYFPushService.java new file mode 100644 index 0000000..0621050 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/IYFPushService.java @@ -0,0 +1,107 @@ +package top.ysoft.admin.yfApi.service; + +import com.alibaba.fastjson.JSONObject; +import top.ysoft.admin.yfApi.domain.Person; +import top.ysoft.admin.yfApi.domain.RuleBody; + +/** + * 宇泛下发设备接口 + * + * @author nichun + */ +public interface IYFPushService { + + //人员接口-人员注册 + String personCreate(Person person, String ip, String pass); + + //人员接口-人员删除(若删除多个人员,id 用英文逗号拼接,传入-1 则删除所有人员) + String personDelete(String id, String ip, String pass); + + //人员接口-人员更新 + String personUpdate(Person person, String ip, String pass); + + //人员接口-人员查询(若删除多个人员,id 用英文逗号拼接,传入-1 则删除所有人员) + String personFind(String id, String ip, String pass); + + //人员接口-人员卡号注册 + String icCardRegist(String id, String ip, String pass); + + //人员接口-时间段权限设置 + String createPasstime(JSONObject object, String ip, String pass); + + //人员接口-时间段权限设置(安卓设备不支持) + String passtime(JSONObject object, String ip, String pass); + + //人员接口-时间段权限批量删除(传入-1 可清除所有人员的passtime) + String deletePasstime(String id, String ip, String pass); + + //人员接口-人员有效期设置(2017-07-15 12:05:00) 仅支持单个人员 + String permissionsCreate(String id, String time, String ip, String pass); + + //人员有效期批量设置(安卓设备不支持) + //2017-07-15 12:05:00 + //传入-1 则选择为所有人员进行设置权限时间 + String permissionTime(String id, String startTime, String endTime, String ip, String pass); + + //人员接口-人员有效期批量删除(传入-1,可清除所有人员的权限时间) + String permissionsDelete(String id, String ip, String pass); + + //照片管理-照片注册册(base64)(不推荐使用)图片像素大于112*112、分辨率小于1080p、文件大小小于2M + String imageCreate64(String id, String imgBase64, String ip, String pass); + + //照片管理-照片注册册(url)(不推荐使用)图片像素大于112*112、分辨率小于1080p、文件大小小于2M + String imageCreateUrl(String id, String faceId, String imgUrl, String ip, String pass); + + String imageCreateUrl(String id, String imgUrl, String ip, String pass); + + //照片管理-照片删除 + String imageDeletel(String faceId, String ip, String pass); + + //照片管理-照片更新(base64) + String imageUpdate64(String id, String faceId, String imgBase64, String ip, String pass); + + //照片管理-拍照注册 + String takeImg(String id, String ip, String pass); + + //照片管理-清空人员注册照片 + String deletePerson(String id, String ip, String pass); + + //设备管理-识别回调 + String setIdentifyCallBack(String callbackUrl, String ip, String pass); + + //远程控制输出--默认远程开门 + String openDoorControl(String ip, String pass, Integer type, String content); + + //远程控制输出--设备重启 + String restartDevice(String ip, String pass); + + //远程控制输出--设备重置 + String resetDevice(String ip, String pass); + + //设备管理-二维码回调 + String setQRCodeCallback(String callbackUrl, String ip, String pass); + + //设备管理-心跳回调 + String setDeviceHeartBeat(String callbackUrl, String ip, String pass); + + //设备管理-指纹采集 + String fingerRegist(String id, String ip, String pass); + + //设备管理-指纹特征值查询 + String featureInfo(String id, String ip, String pass); + + //设备管理-照片查询 + String faceFind(String id, String ip, String pass); + + //设备管理-指纹特征值注册 + String featureReg(String id, String fingerprint, String ip, String pass); + + //设备管理-规则创建 + String ruleCreate(RuleBody rule, String ip); + + //设备管理-规则删除 + String ruleDelete(String ruleId, String ip, String pass, int isDeleteAll); + + //设备管理-规则修改 + String ruleUpdate(RuleBody rule, String ip); +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/service/impl/SysYFServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/impl/SysYFServiceImpl.java new file mode 100644 index 0000000..2c43656 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/impl/SysYFServiceImpl.java @@ -0,0 +1,250 @@ +package top.ysoft.admin.yfApi.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import top.continew.starter.core.validation.CheckUtils; +import top.ysoft.admin.common.enums.OperTypeEnum; +import top.ysoft.admin.equipment.mapper.EquipmentMapper; +import top.ysoft.admin.equipment.model.entity.EquipmentDO; +import top.ysoft.admin.peopleBranch.mapper.PeopleMapper; +import top.ysoft.admin.peopleBranch.model.entity.PeopleDO; +import top.ysoft.admin.peopleDownRecord.model.req.PeopleDownReq; +import top.ysoft.admin.peopleDownRecord.service.PeopleDownService; +import top.ysoft.admin.rule.model.resp.YFPeopleResp; +import top.ysoft.admin.rule.service.RuleService; +import top.ysoft.admin.system.mapper.RuleRelationMapper; +import top.ysoft.admin.system.model.entity.RuleRelationDO; +import top.ysoft.admin.yfApi.domain.Person; +import top.ysoft.admin.yfApi.service.ISysYFService; +import top.ysoft.admin.yfApi.service.IYFPushService; + +import java.util.*; + +@Service +@RequiredArgsConstructor +@Slf4j +public class SysYFServiceImpl implements ISysYFService { + + private final IYFPushService iyfPushService; + + private final PeopleDownService peopleDownService; + + private final PeopleMapper peopleMapper; + + private final EquipmentMapper equipmentMapper; + + private final RuleRelationMapper ruleRelationMapper; + + private final RuleService ruleService; + + @Override + public List authDevice(List peopleIds, Integer operType) { + List downReqs = new ArrayList<>(); + List failIds = new ArrayList<>(); + + //查询部门绑定的规则ids + log.info("authDevice入参-peopleIds:{}", JSON.toJSONString(peopleIds)); + List yfPeopleResps = ruleService.getEquipmentRulesByPeoples(peopleIds); + if (CollUtil.isEmpty(yfPeopleResps)) { + log.info("人员所在部门未绑定规则,不下发设备"); + return failIds; + } + + for (YFPeopleResp resp : yfPeopleResps) { + //下发记录 + PeopleDownReq peopleDownReq = new PeopleDownReq(); + peopleDownReq.setEquipmentId(resp.getEquipmentId()); + peopleDownReq.setPeopleId(resp.getPeopleId()); + peopleDownReq.setDownTime(new Date()); + peopleDownReq.setOperType(operType); + peopleDownReq.setRuleId(resp.getRuleId()); + + //设备在线处理逻辑 + if (StrUtil.equals("0", resp.getFlag())) { + //新增人员信息 + Person person = new Person(); + person.setName(resp.getPeopleName()); + person.setIDNumber(resp.getIdcard()); + person.setPhone(resp.getPhone()); + person.setId(resp.getGuid()); + JSONObject ruleId = new JSONObject(); + ruleId.put("ruleId", resp.getEquipmentRuleId()); + JSONObject timezoneRule = new JSONObject(); + timezoneRule.put("timezoneRule", ruleId); + ArrayList list = new ArrayList<>(); + list.add(timezoneRule); + person.setRule(list); + + //如果不是新增,先删除人员信息 + if (!Objects.equals(operType, OperTypeEnum.ADD.getValue())) { + String data = iyfPushService.personDelete(resp.getGuid(), resp.getIp(), resp.getPassword()); + if (!verifyResult(data, peopleDownReq)) { + failIds.add(resp.getPeopleId()); + downReqs.add(peopleDownReq); + continue; + } + } + + //下发人员信息 + String data = iyfPushService.personCreate(person, resp.getIp(), resp.getPassword()); + if (!verifyResult(data, peopleDownReq)) { + failIds.add(resp.getPeopleId()); + downReqs.add(peopleDownReq); + continue; + } + + //下发人员照片 + if (StrUtil.isNotBlank(resp.getFaceGuid())) { + String datas = iyfPushService.imageCreateUrl(resp.getGuid(), resp.getAvatar(), resp.getIp(), resp + .getPassword()); + if (!verifyResult(datas, peopleDownReq)) { + failIds.add(resp.getPeopleId()); + } + } + + //下发成功后,修改人员下发状态 + peopleMapper.lambdaUpdate().eq(PeopleDO::getId, resp.getPeopleId()).set(PeopleDO::getDown, 1).update(); + + } else { + failIds.add(resp.getPeopleId()); + peopleDownReq.setDownResult(1); + peopleDownReq.setMsg("该设备下发时处于离线状态"); + } + downReqs.add(peopleDownReq); + } + peopleDownService.batchUpsertRecord(downReqs); + log.info("authDevice-失败人员:{}", JSON.toJSONString(failIds)); + return failIds; + } + + @Override + public Boolean authDevice(Long ruleId, Long equipmentId, Long peopleId, Integer operType) { + //下发记录 + PeopleDownReq peopleDownReq = new PeopleDownReq(); + peopleDownReq.setEquipmentId(equipmentId); + peopleDownReq.setPeopleId(peopleId); + peopleDownReq.setDownTime(new Date()); + peopleDownReq.setOperType(operType); + peopleDownReq.setRuleId(ruleId); + + RuleRelationDO ruleRelationDO = ruleRelationMapper.selectOne(new LambdaQueryWrapper() + .eq(RuleRelationDO::getEquipmentId, equipmentId) + .eq(RuleRelationDO::getRuleId, ruleId)); + PeopleDO peopleDO = peopleMapper.selectById(peopleId); + EquipmentDO equipmentDO = equipmentMapper.selectById(equipmentId); + + CheckUtils.throwIfEmpty(ruleRelationDO, "{设备id:" + equipmentId + "}:设备未绑定规则"); + CheckUtils.throwIfEmpty(peopleDO, "{人员id:" + peopleId + "}:人员不存在"); + CheckUtils.throwIfNotEqual(equipmentDO.getFlag(), "0", "{设备id:" + equipmentId + "}:当前设备离线中"); + + String data = iyfPushService.personDelete(peopleDO.getGuid(), equipmentDO.getIp(), equipmentDO.getPassword()); + if (!verifyResult(data, peopleDownReq)) { + peopleDownService.upsertRecord(peopleDownReq); + return false; + } + + //新增人员信息 + Person person = new Person(); + person.setName(peopleDO.getName()); + person.setIDNumber(peopleDO.getIdcard()); + person.setPhone(peopleDO.getPhone()); + person.setId(peopleDO.getGuid()); + JSONObject ruleJs = new JSONObject(); + ruleJs.put("ruleId", ruleRelationDO.getEquipmentRuleId()); + JSONObject timezoneRule = new JSONObject(); + timezoneRule.put("timezoneRule", ruleJs); + ArrayList list = new ArrayList<>(); + list.add(timezoneRule); + person.setRule(list); + + //下发人员信息 + String data1 = iyfPushService.personCreate(person, equipmentDO.getIp(), equipmentDO.getPassword()); + if (!verifyResult(data1, peopleDownReq)) { + peopleDownService.upsertRecord(peopleDownReq); + return false; + } + + //下发人员照片 + if (StrUtil.isNotBlank(peopleDO.getFaceGuid())) { + String datas = iyfPushService.imageCreateUrl(peopleDO.getGuid(), peopleDO.getAvatar(), equipmentDO + .getIp(), equipmentDO.getPassword()); + if (!verifyResult(datas, peopleDownReq)) { + peopleDownService.upsertRecord(peopleDownReq); + return false; + } + } + peopleDownService.upsertRecord(peopleDownReq); + return true; + + } + + @Override + public List delDevice(List peopleIds, Integer operType) { + List downReqs = new ArrayList<>(); + + List failIds = new ArrayList<>(); + //查询部门绑定的规则ids + log.info("delDevice入参-peopleIds:{}", JSON.toJSONString(peopleIds)); + List yfPeopleResps = ruleService.getEquipmentRulesByPeoples(peopleIds); + if (CollUtil.isEmpty(yfPeopleResps)) { + log.info("人员删除-人员所在部门未绑定规则,不下发设备"); + return failIds; + } + + for (YFPeopleResp resp : yfPeopleResps) { + //下发记录 + PeopleDownReq peopleDownReq = new PeopleDownReq(); + peopleDownReq.setEquipmentId(resp.getEquipmentId()); + peopleDownReq.setPeopleId(resp.getPeopleId()); + peopleDownReq.setDownTime(new Date()); + peopleDownReq.setOperType(operType); + + //设备在线处理逻辑 + if (StrUtil.equals("0", resp.getFlag())) { + String data = iyfPushService.personDelete(resp.getGuid(), resp.getIp(), resp.getPassword()); + peopleDownReq.setMsg(data); + JSONObject jsons = JSONObject.parseObject(data); + if (jsons != null && jsons.containsKey("code") && StrUtil.equals("LAN_SUS-0", jsons + .getString("code"))) { + peopleDownReq.setDownResult(0); + } else { + failIds.add(resp.getPeopleId()); + peopleDownReq.setDownResult(1); + } + } else { + peopleDownReq.setDownResult(1); + peopleDownReq.setMsg("该设备下发时处于离线状态"); + } + downReqs.add(peopleDownReq); + } + + if (CollUtil.isNotEmpty(downReqs)) { + peopleDownService.batchUpsertRecord(downReqs); + } + + List collect = peopleIds.stream().filter(id -> !failIds.contains(id)).toList(); + if (CollUtil.isNotEmpty(collect)) { + peopleDownService.batchUpsertRecord(downReqs); + } + return failIds; + } + + private Boolean verifyResult(String data, PeopleDownReq peopleDownReq) { + peopleDownReq.setMsg(data); + JSONObject jsons = JSONObject.parseObject(data); + if (jsons != null && jsons.containsKey("code") && StrUtil.equals("LAN_SUS-0", jsons.getString("code"))) { + peopleDownReq.setDownResult(0); + return true; + } else { + peopleDownReq.setDownResult(1); + return false; + } + } + +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/service/impl/YFListenServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/impl/YFListenServiceImpl.java new file mode 100644 index 0000000..32f2974 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/impl/YFListenServiceImpl.java @@ -0,0 +1,90 @@ +package top.ysoft.admin.yfApi.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.ysoft.admin.equipment.mapper.EquipmentMapper; +import top.ysoft.admin.equipment.model.entity.EquipmentDO; +import top.ysoft.admin.peopleBranch.mapper.PeopleMapper; +import top.ysoft.admin.peopleBranch.model.entity.PeopleDO; +import top.ysoft.admin.rule.mapper.PeopleRecordMapper; +import top.ysoft.admin.rule.model.entity.PeopleRecordDO; +import top.ysoft.admin.yfApi.domain.BackInfo; +import top.ysoft.admin.yfApi.domain.HeartInfo; +import top.ysoft.admin.yfApi.service.IYFListenService; + +import java.time.Duration; +import java.util.Date; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Slf4j +public class YFListenServiceImpl implements IYFListenService { + + private final EquipmentMapper equipmentMapper; + + private final PeopleMapper peopleMapper; + + private final PeopleRecordMapper peopleRecordMapper; + + @Override + public void heart(HeartInfo info) { + //获取设备缓存的设备时间 + RedisUtils.set(info.getDeviceKey(), info.getTime(), Duration.ofSeconds(2 * 60)); + List list = equipmentMapper.selectList(new QueryWrapper().eq("product_id", 3L)); + for (EquipmentDO e : list) { + EquipmentDO equipmentDO = new EquipmentDO(); + equipmentDO.setId(e.getId()); + String time = RedisUtils.get(e.getSequence()); + if (StrUtil.isNotEmpty(time)) { + equipmentDO.setFlag("0"); + } else { + equipmentDO.setFlag("1"); + } + if (!StrUtil.equals(equipmentDO.getFlag(), e.getFlag())) { + equipmentMapper.updateById(equipmentDO); + } + } + } + + @Override + public void processPeopleRecord(BackInfo info) { + EquipmentDO equipmentDO = equipmentMapper.selectOne(new QueryWrapper().eq("sequence", info + .getDeviceKey())); + if (equipmentDO == null) { + log.error("未知设备识别记录:{}", info.getDeviceKey()); + return; + } + + PeopleDO peopleDO = peopleMapper.selectOne(new QueryWrapper().eq("admit_guid", info.getPersonId())); + if (ObjectUtil.isEmpty(peopleDO)) { + log.error("未知人员识别记录:{}", info.getPersonId()); + return; + } + + //新增通行记录 + PeopleRecordDO peopleRecordDO = new PeopleRecordDO(); + peopleRecordDO.setEquipmentId(equipmentDO.getId()); + peopleRecordDO.setAdmitGuid(info.getPersonId()); + peopleRecordDO.setAliveType(info.getAliveType()); + peopleRecordDO.setDeviceNo(info.getDeviceKey()); + peopleRecordDO.setType(info.getIdentifyType()); + peopleRecordDO.setCardNo(info.getIdcardNum()); + peopleRecordDO.setRecType(info.getRecType()); + peopleRecordDO.setPermissionTimeType(info.getPermissionTimeType()); + peopleRecordDO.setPassTimeType(info.getPassTimeType()); + peopleRecordDO.setRecModeType(info.getRecModeType()); + peopleRecordDO.setTimestamp(Long.parseLong(info.getTime())); + peopleRecordDO.setShowTime(Long.parseLong(info.getTime())); + peopleRecordDO.setShowDate(new Date(Long.parseLong(info.getTime()))); + peopleRecordDO.setRecMode(info.getModel()); + peopleRecordDO.setDeviceIp(info.getIp()); + peopleRecordDO.setPeopleId(peopleDO.getId()); + peopleRecordMapper.insert(peopleRecordDO); + } +} diff --git a/wms-module-system/src/main/java/top/wms/admin/yfApi/service/impl/YFPushServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/impl/YFPushServiceImpl.java new file mode 100644 index 0000000..bc8301c --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/yfApi/service/impl/YFPushServiceImpl.java @@ -0,0 +1,465 @@ +package top.ysoft.admin.yfApi.service.impl; + +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import top.ysoft.admin.yfApi.domain.Person; +import top.ysoft.admin.yfApi.domain.RuleBody; +import top.ysoft.admin.yfApi.service.IYFPushService; + +import java.util.*; + +/** + * 宇泛接口 + * + * @author zc + */ +@Service +@Slf4j +public class YFPushServiceImpl implements IYFPushService { + + @Override + public String personCreate(Person person, String ip, String pass) { + String url = "http://" + ip + ":8090/person/create"; + Map paramMap = new HashMap(); + person.setPhone(""); + JSONObject personObject = JSONObject.parseObject(JSON.toJSONString(person)); + paramMap.put("pass", pass); + paramMap.put("person", personObject); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String personDelete(String id, String ip, String pass) { + String url = "http://" + ip + ":8090/person/delete"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("id", id); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String personUpdate(Person person, String ip, String pass) { + String url = "http://" + ip + ":8090/person/update"; + Map paramMap = new HashMap(); + JSONObject personObject = JSONObject.parseObject(JSON.toJSONString(person)); + paramMap.put("pass", pass); + paramMap.put("person", personObject); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String personFind(String id, String ip, String pass) { + String url = "http://" + ip + ":8090/person/find?pass=" + pass + "&id=" + id; + return HttpUtil.createGet(url).execute().body(); + } + + @Override + public String icCardRegist(String id, String ip, String pass) { + String url = "http://" + ip + ":8090/face/icCardRegist"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String createPasstime(JSONObject object, String ip, String pass) { + /* {"personId":"9eecc839cd7941c5a4d3165202dd3c32","passtime":"09:00:00,10: + 00:00,17:00:00,17:30:00,18:30:00,20:25:00"}*/ + String url = "http://" + ip + ":8090/person/createPasstime"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("passtime", object); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String passtime(JSONObject object, String ip, String pass) { + /* {"personId":"9eecc839cd7941c5a4d3165202dd3c32","passtime":"09:00:00,10: + 00:00,17:00:00,17:30:00,18:30:00,20:25:00"}*/ + String url = "http://" + ip + ":8090/person/passtime"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("passtime", object.get("passtime")); + paramMap.put("personId", object.get("personId")); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String deletePasstime(String id, String ip, String pass) { + String url = "http://" + ip + ":8090/person/deletePasstime"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String permissionsCreate(String id, String time, String ip, String pass) { + String url = "http://" + ip + ":8090/person/permissionsCreate"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("time", time); + paramMap.put("personId", id); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String permissionTime(String id, String startTime, String endTime, String ip, String pass) { + String url = "http://" + ip + ":8090/person/permissionTime"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + paramMap.put("startTime", startTime); + paramMap.put("endTime", endTime); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String permissionsDelete(String id, String ip, String pass) { + String url = "http://" + ip + ":8090/person/permissionsCreate"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String imageCreate64(String id, String imgBase64, String ip, String pass) { + String url = "http://" + ip + ":8090/face/create"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + paramMap.put("faceId", id);//可不传,设为空 + paramMap.put("imgBase64", imgBase64); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String imageCreateUrl(String id, String imgUrl, String ip, String pass) { + String url = "http://" + ip + ":8090/face/createByUrl"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + paramMap.put("faceId", "");//可不传,设为空 + //照地址替换 + if (imgUrl.contains("http://tdmj.kmlygroup.com")) { + imgUrl = imgUrl.replace("http://tdmj.kmlygroup.com", "http://192.168.1.35"); + } + if (imgUrl.contains("https://mj.kmlygroup.com/")) { + imgUrl = imgUrl.replace("https://mj.kmlygroup.com/", "http://192.168.255.51:9000/"); + } + paramMap.put("imgUrl", imgUrl); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String imageCreateUrl(String id, String faceId, String imgUrl, String ip, String pass) { + String url = "http://" + ip + ":8090/face/createByUrl"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + paramMap.put("faceId", faceId);//可不传,设为空 + //照地址替换 + if (imgUrl.contains("http://tdmj.kmlygroup.com")) { + imgUrl = imgUrl.replace("http://tdmj.kmlygroup.com", "http://192.168.251.16"); + } + if (imgUrl.contains("https://mj.kmlygroup.com/")) { + imgUrl = imgUrl.replace("https://mj.kmlygroup.com/", "http://192.168.255.51:9000/"); + } + paramMap.put("imgUrl", imgUrl); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String imageDeletel(String faceId, String ip, String pass) { + String url = "http://" + ip + ":8090/face/delete"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("faceId", faceId);//可不传,设为空 + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String imageUpdate64(String id, String faceId, String imgBase64, String ip, String pass) { + String url = "http://" + ip + ":8090/face/update"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + paramMap.put("faceId", faceId); + paramMap.put("imgBase64", imgBase64); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String takeImg(String id, String ip, String pass) { + String url = "http://" + ip + ":8090/face/takeImg"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String deletePerson(String id, String ip, String pass) { + String url = "http://" + ip + ":8090/face/deletePerson"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String setIdentifyCallBack(String callbackUrl, String ip, String pass) { + String url = "http://" + ip + ":8090/setIdentifyCallBack"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("callbackUrl", callbackUrl); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String openDoorControl(String ip, String pass, Integer type, String content) { + String url = "http://" + ip + ":8090/device/openDoorControl"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("type", type); + paramMap.put("content", content); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String restartDevice(String ip, String pass) { + String url = "http://" + ip + ":8090/restartDevice"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String resetDevice(String ip, String pass) { + String url = "http://" + ip + ":8090/device/reset"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String setQRCodeCallback(String callbackUrl, String ip, String pass) { + String url = "http://" + ip + ":8090/setQRCodeCallback"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("url", callbackUrl + "Ewm"); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String setDeviceHeartBeat(String callbackUrl, String ip, String pass) { + String url = "http://" + ip + ":8090/setDeviceHeartBeat"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("url", callbackUrl + "Heart"); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String fingerRegist(String id, String ip, String pass) { + String url = "http://" + ip + ":8090/face/fingerRegist"; + Map paramMap = new HashMap<>(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String featureInfo(String id, String ip, String pass) { + String url = "http://" + ip + ":8090/person/featureInfo?pass=" + pass + "&personId=" + id + "&type=" + 2; + return HttpUtil.createGet(url).execute().body(); + } + + @Override + public String faceFind(String id, String ip, String pass) { + String url = "http://" + ip + ":8090/face/find"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String featureReg(String id, String fingerprint, String ip, String pass) { + String url = "http://" + ip + ":8090/face/featureReg"; + Map paramMap = new HashMap(); + paramMap.put("pass", pass); + paramMap.put("personId", id); + paramMap.put("faceId", ""); + paramMap.put("feature", fingerprint); + paramMap.put("type", 2); + return HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + } + + @Override + public String ruleCreate(RuleBody rule, String ip) { + String url = "http://" + ip + ":8090/rule/create"; + Map paramMap = new HashMap(); + JSONObject personObject = JSONObject.parseObject(JSON.toJSONString(rule.getContent())); + paramMap.put("type", rule.getType()); + paramMap.put("pass", rule.getPass()); + paramMap.put("ruleId", rule.getRuleId()); + paramMap.put("name", rule.getRuleName()); + paramMap.put("content", personObject); + String response = HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + log.info("规则创建结果:{}", response); + return response; + } + + @Override + public String ruleDelete(String ruleId, String ip, String pass, int isDeleteAll) { + String url = "http://" + ip + ":8090/rule/delete"; + Map paramMap = new HashMap(); + JSONArray jsonArray = new JSONArray(); + jsonArray.add(ruleId); + paramMap.put("pass", pass); + paramMap.put("isDeleteAll", isDeleteAll); + paramMap.put("ruleId", jsonArray); + String response = HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + log.info("规则删除结果{}", response); + return response; + } + + @Override + public String ruleUpdate(RuleBody rule, String ip) { + String url = "http://" + ip + ":8090/rule/update"; + Map paramMap = new HashMap(); + JSONObject personObject = JSONObject.parseObject(JSON.toJSONString(rule.getContent())); + paramMap.put("type", rule.getType()); + paramMap.put("pass", rule.getPass()); + paramMap.put("ruleId", rule.getRuleId()); + paramMap.put("name", rule.getRuleName()); + paramMap.put("content", personObject); + String response = HttpUtil.createPost(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(JSON.toJSONString(paramMap)) + .execute() + .body(); + log.info("规则修改结果" + response); + return response; + } + +} diff --git a/wms-module-system/src/main/resources/mapper/BranchMapper.xml b/wms-module-system/src/main/resources/mapper/BranchMapper.xml new file mode 100644 index 0000000..0b77cf6 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/BranchMapper.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/BranchRuleMapper.xml b/wms-module-system/src/main/resources/mapper/BranchRuleMapper.xml new file mode 100644 index 0000000..c98e452 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/BranchRuleMapper.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/DeptMapper.xml b/wms-module-system/src/main/resources/mapper/DeptMapper.xml new file mode 100644 index 0000000..4001e8b --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/DeptMapper.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/DictItemMapper.xml b/wms-module-system/src/main/resources/mapper/DictItemMapper.xml new file mode 100644 index 0000000..7493855 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/DictItemMapper.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/DownRecordMapper.xml b/wms-module-system/src/main/resources/mapper/DownRecordMapper.xml new file mode 100644 index 0000000..faa9e3c --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/DownRecordMapper.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/EmpowerRecordMapper.xml b/wms-module-system/src/main/resources/mapper/EmpowerRecordMapper.xml new file mode 100644 index 0000000..9e1d3ed --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/EmpowerRecordMapper.xml @@ -0,0 +1,37 @@ + + + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/GroupMapper.xml b/wms-module-system/src/main/resources/mapper/GroupMapper.xml new file mode 100644 index 0000000..085af3d --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/GroupMapper.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/LogMapper.xml b/wms-module-system/src/main/resources/mapper/LogMapper.xml new file mode 100644 index 0000000..abfa05a --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/LogMapper.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wms-module-system/src/main/resources/mapper/MenuMapper.xml b/wms-module-system/src/main/resources/mapper/MenuMapper.xml new file mode 100644 index 0000000..cdb5afa --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/MenuMapper.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/MessageMapper.xml b/wms-module-system/src/main/resources/mapper/MessageMapper.xml new file mode 100644 index 0000000..edac0e9 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/MessageMapper.xml @@ -0,0 +1,14 @@ + + + + diff --git a/wms-module-system/src/main/resources/mapper/MessageUserMapper.xml b/wms-module-system/src/main/resources/mapper/MessageUserMapper.xml new file mode 100644 index 0000000..a28d168 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/MessageUserMapper.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/NoticeMapper.xml b/wms-module-system/src/main/resources/mapper/NoticeMapper.xml new file mode 100644 index 0000000..6b1d28d --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/NoticeMapper.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/PeopleDownMapper.xml b/wms-module-system/src/main/resources/mapper/PeopleDownMapper.xml new file mode 100644 index 0000000..28d9d90 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/PeopleDownMapper.xml @@ -0,0 +1,67 @@ + + + + + + INSERT INTO equipment_down_record ( + people_id, + equipment_id, + rule_id, + down_time, + down_result, + create_user, + create_time, + msg, + oper_type + ) VALUES ( + #{peopleId}, + #{equipmentId}, + #{ruleId}, + #{downTime}, + #{downResult}, + #{createUser}, + #{createTime}, + #{msg}, + #{operType} + ) + ON DUPLICATE KEY UPDATE + down_time = values(down_time), + down_result = values(down_result), + create_user = values(create_user), + create_time = NOW(), + msg = values(msg), + oper_type = values(oper_type) + + + + + + + diff --git a/wms-module-system/src/main/resources/mapper/PeopleEquipmentMapper.xml b/wms-module-system/src/main/resources/mapper/PeopleEquipmentMapper.xml new file mode 100644 index 0000000..00dd594 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/PeopleEquipmentMapper.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/PeopleMapper.xml b/wms-module-system/src/main/resources/mapper/PeopleMapper.xml new file mode 100644 index 0000000..5886517 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/PeopleMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + diff --git a/wms-module-system/src/main/resources/mapper/PeopleRecordMapper.xml b/wms-module-system/src/main/resources/mapper/PeopleRecordMapper.xml new file mode 100644 index 0000000..ef1802f --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/PeopleRecordMapper.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/PointMapper.xml b/wms-module-system/src/main/resources/mapper/PointMapper.xml new file mode 100644 index 0000000..85cdc65 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/PointMapper.xml @@ -0,0 +1,26 @@ + + + + + + SELECT + t1.id, + t1.name, + t1.space_id, + t1.remark, + t1.create_user, + t1.create_time, + t1.update_user, + t1.update_time, + t1.sort, + t2.name as spaceName + FROM sys_point t1 + LEFT JOIN sys_space AS t2 ON t2.id = t1.space_id + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/ProductMapper.xml b/wms-module-system/src/main/resources/mapper/ProductMapper.xml new file mode 100644 index 0000000..2864de4 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/ProductMapper.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/RechargeRecordMapper.xml b/wms-module-system/src/main/resources/mapper/RechargeRecordMapper.xml new file mode 100644 index 0000000..474373e --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/RechargeRecordMapper.xml @@ -0,0 +1,48 @@ + + + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/RecordMapper.xml b/wms-module-system/src/main/resources/mapper/RecordMapper.xml new file mode 100644 index 0000000..e5ee10c --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/RecordMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/ReportMapper.xml b/wms-module-system/src/main/resources/mapper/ReportMapper.xml new file mode 100644 index 0000000..82c90b6 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/ReportMapper.xml @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/RoleMapper.xml b/wms-module-system/src/main/resources/mapper/RoleMapper.xml new file mode 100644 index 0000000..3755911 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/RoleMapper.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/RoleMenuMapper.xml b/wms-module-system/src/main/resources/mapper/RoleMenuMapper.xml new file mode 100644 index 0000000..a39262a --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/RoleMenuMapper.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/RuleMapper.xml b/wms-module-system/src/main/resources/mapper/RuleMapper.xml new file mode 100644 index 0000000..53d6a19 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/RuleMapper.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select sr.id, sr.name, sr.start_time, sr.end_time, sr.admittance_start, sr.admittance_end, sr.space_id, sr.point_id, + sr.remark, sr.create_user, sr.create_time, sr.update_user, sr.update_time, sr.type, sr.permission + from sys_rule sr + + + + + + + + diff --git a/wms-module-system/src/main/resources/mapper/RuleRelationMapper.xml b/wms-module-system/src/main/resources/mapper/RuleRelationMapper.xml new file mode 100644 index 0000000..cd39489 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/RuleRelationMapper.xml @@ -0,0 +1,21 @@ + + + + + + + DELETE FROM equipment_rule_relation + WHERE 1=1 + + + AND rule_id = #{ruleId} + + + AND equipment_id = #{equipmentId} + + + AND equipment_rule_id = #{equipmentRuleId} + + + + diff --git a/wms-module-system/src/main/resources/mapper/SpaceMapper.xml b/wms-module-system/src/main/resources/mapper/SpaceMapper.xml new file mode 100644 index 0000000..53f218c --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/SpaceMapper.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/TenantMapper.xml b/wms-module-system/src/main/resources/mapper/TenantMapper.xml new file mode 100644 index 0000000..1c0c98b --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/TenantMapper.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/TimeIntervalMapper.xml b/wms-module-system/src/main/resources/mapper/TimeIntervalMapper.xml new file mode 100644 index 0000000..dbc5379 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/TimeIntervalMapper.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/UserMapper.xml b/wms-module-system/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..102b80e --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,56 @@ + + + + + + SELECT + t1.id, + t1.create_user, + t1.create_time, + t1.update_user, + t1.update_time, + t1.username, + t1.nickname, + t1.password, + t1.gender, + t1.email, + t1.phone, + t1.avatar, + t1.description, + t1.status, + t1.is_system, + t1.pwd_reset_time, + t1.dept_id, + t2.name AS deptName + FROM sys_user AS t1 + LEFT JOIN sys_dept AS t2 ON t2.id = t1.dept_id + + + + + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/UserPasswordHistoryMapper.xml b/wms-module-system/src/main/resources/mapper/UserPasswordHistoryMapper.xml new file mode 100644 index 0000000..08fda30 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/UserPasswordHistoryMapper.xml @@ -0,0 +1,15 @@ + + + + + DELETE t1 FROM sys_user_password_history AS t1 + LEFT JOIN ( + SELECT id + FROM sys_user_password_history + WHERE user_id = #{userId} + ORDER BY create_time DESC + LIMIT #{count} + ) AS t2 ON t2.id = t1.id + WHERE t2.id IS NULL + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/UserRoleMapper.xml b/wms-module-system/src/main/resources/mapper/UserRoleMapper.xml new file mode 100644 index 0000000..c4cc9dd --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/UserRoleMapper.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/mapper/UserSocialMapper.xml b/wms-module-system/src/main/resources/mapper/UserSocialMapper.xml new file mode 100644 index 0000000..18c5ea4 --- /dev/null +++ b/wms-module-system/src/main/resources/mapper/UserSocialMapper.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/wms-module-system/src/main/resources/sql/DownRecordMenu.sql b/wms-module-system/src/main/resources/sql/DownRecordMenu.sql new file mode 100644 index 0000000..d49bf2b --- /dev/null +++ b/wms-module-system/src/main/resources/sql/DownRecordMenu.sql @@ -0,0 +1,18 @@ +SET @parentId = 2026492742201946112; +-- 消费下发记录管理菜单 +INSERT INTO `sys_menu` + (`id`, `title`, `parent_id`, `type`, `path`, `name`, `component`, `redirect`, `icon`, `is_external`, `is_cache`, `is_hidden`, `permission`, `sort`, `status`, `create_user`, `create_time`) +VALUES + (@parentId, '消费下发记录管理', 1000, 2, '/consume/downRecord', 'DownRecord', 'consume/downRecord/index', NULL, NULL, b'0', b'0', b'0', NULL, 1, 1, 1, NOW()); + +-- 消费下发记录管理按钮 +INSERT INTO `sys_menu` + (`id`, `title`, `parent_id`, `type`, `permission`, `sort`, `status`, `create_user`, `create_time`) +VALUES + (2026492742201946113, '列表', @parentId, 3, 'consume:downRecord:list', 1, 1, 1, NOW()), + (2026492742201946114, '详情', @parentId, 3, 'consume:downRecord:detail', 2, 1, 1, NOW()), + (2026492742201946115, '新增', @parentId, 3, 'consume:downRecord:add', 3, 1, 1, NOW()), + (2026492742201946116, '修改', @parentId, 3, 'consume:downRecord:update', 4, 1, 1, NOW()), + (2026492742210334720, '删除', @parentId, 3, 'consume:downRecord:delete', 5, 1, 1, NOW()), + (2026492742210334721, '导出', @parentId, 3, 'consume:downRecord:export', 6, 1, 1, NOW()); + diff --git a/wms-plugin/.flattened-pom.xml b/wms-plugin/.flattened-pom.xml new file mode 100644 index 0000000..ff92f9f --- /dev/null +++ b/wms-plugin/.flattened-pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + top.ysoft + ysoft-admin + 3.6.0-SNAPSHOT + + ysoft-plugin + 3.6.0-SNAPSHOT + pom + 插件模块(存放代码生成、任务调度等扩展模块) + + + GNU LESSER GENERAL PUBLIC LICENSE + http://www.gnu.org/licenses/lgpl.html + + + + ysoft-plugin-schedule + ysoft-plugin-open + ysoft-plugin-generator + + + + top.ysoft + ysoft-common + + + diff --git a/wms-plugin/pom.xml b/wms-plugin/pom.xml new file mode 100644 index 0000000..5cb2a55 --- /dev/null +++ b/wms-plugin/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + top.ysoft + ysoft-admin + ${revision} + + + ysoft-plugin + pom + 插件模块(存放代码生成、任务调度等扩展模块) + + + ysoft-plugin-schedule + ysoft-plugin-open + ysoft-plugin-generator + + + + + + top.ysoft + ysoft-common + + + \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/.flattened-pom.xml b/wms-plugin/wms-plugin-generator/.flattened-pom.xml new file mode 100644 index 0000000..79f02c8 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/.flattened-pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + top.ysoft + ysoft-plugin + 3.6.0-SNAPSHOT + + ysoft-plugin-generator + 3.6.0-SNAPSHOT + 代码生成器插件 + + + GNU LESSER GENERAL PUBLIC LICENSE + http://www.gnu.org/licenses/lgpl.html + + + diff --git a/wms-plugin/wms-plugin-generator/pom.xml b/wms-plugin/wms-plugin-generator/pom.xml new file mode 100644 index 0000000..9e4db8e --- /dev/null +++ b/wms-plugin/wms-plugin-generator/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + top.ysoft + ysoft-plugin + ${revision} + + + ysoft-plugin-generator + 代码生成器插件 + \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/config/properties/GeneratorProperties.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/config/properties/GeneratorProperties.java new file mode 100644 index 0000000..2ac6942 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/config/properties/GeneratorProperties.java @@ -0,0 +1,75 @@ +package top.ysoft.admin.generator.config.properties; + +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.map.MapUtil; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import top.continew.starter.data.core.enums.DatabaseType; + +import java.util.List; +import java.util.Map; + +/** + * 代码生成器配置属性 + * + * @author Charles7c + * @since 2023/8/5 11:08 + */ +@Data +@Component +@ConfigurationProperties(prefix = "generator") +public class GeneratorProperties { + + /** + * 排除数据表(哪些数据表不展示在代码生成中) + */ + private String[] excludeTables = new String[0]; + + /** + * 类型映射 + */ + private Map>> typeMappings = MapUtil.newHashMap(); + + /** + * 模板配置 + */ + private Map templateConfigs = MapUtil.newHashMap(true); + + /** + * 模板配置 + */ + @Data + public static class TemplateConfig { + + /** + * 模板路径 + */ + private String templatePath; + + /** + * 包名称 + */ + private String packageName; + + /** + * 排除字段 + */ + private String[] excludeFields; + + /** + * 扩展名 + */ + private String extension = FileNameUtil.EXT_JAVA; + + /** + * 后缀 + */ + private String suffix; + + /** + * 是否为后端模板 + */ + private boolean backend = true; + } +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/enums/FormTypeEnum.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/enums/FormTypeEnum.java new file mode 100644 index 0000000..7d0a5c5 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/enums/FormTypeEnum.java @@ -0,0 +1,79 @@ +package top.ysoft.admin.generator.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 表单类型枚举 + * + * @author Charles7c + * @since 2023/8/6 10:49 + */ +@Getter +@RequiredArgsConstructor +public enum FormTypeEnum implements BaseEnum { + + /** + * 输入框 + */ + INPUT(1, "输入框"), + + /** + * 数字输入框 + */ + INPUT_NUMBER(2, "数字输入框"), + + /** + * 密码输入框 + */ + INPUT_PASSWORD(3, "密码输入框"), + + /** + * 文本域 + */ + TEXT_AREA(4, "文本域"), + + /** + * 下拉框 + */ + SELECT(5, "下拉框"), + + /** + * 单选框 + */ + RADIO(6, "单选框"), + + /** + * 开关 + */ + SWITCH(7, "开关"), + + /** + * 复选框 + */ + CHECK_BOX(8, "复选框"), + + /** + * 树形选择 + */ + TREE_SELECT(9, "树选择"), + + /** + * 时间框 + */ + TIME(10, "时间框"), + + /** + * 日期框 + */ + DATE(11, "日期框"), + + /** + * 时间框 + */ + DATE_TIME(12, "日期时间框"),; + + private final Integer value; + private final String description; +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/enums/QueryTypeEnum.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/enums/QueryTypeEnum.java new file mode 100644 index 0000000..b22963a --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/enums/QueryTypeEnum.java @@ -0,0 +1,89 @@ +package top.ysoft.admin.generator.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 查询类型枚举 + * + * @author Charles7c + * @since 2023/8/6 10:49 + */ +@Getter +@RequiredArgsConstructor +public enum QueryTypeEnum implements BaseEnum { + + /** + * 等于 =,例如:WHERE age = 18 + */ + EQ(1, "="), + + /** + * 不等于 !=,例如:WHERE age != 18 + */ + NE(2, "!="), + + /** + * 大于 >,例如:WHERE age > 18 + */ + GT(3, ">"), + + /** + * 大于等于 >= ,例如:WHERE age >= 18 + */ + GE(4, ">="), + + /** + * 小于 <,例如:WHERE age < 18 + */ + LT(5, "<"), + + /** + * 小于等于 <=,例如:WHERE age <= 18 + */ + LE(6, "<="), + + /** + * 范围查询,例如:WHERE age BETWEEN 10 AND 18 + */ + BETWEEN(7, "BETWEEN"), + + /** + * LIKE '%值%',例如:WHERE nickname LIKE '%s%' + */ + LIKE(8, "LIKE '%s%'"), + + /** + * LIKE '%值',例如:WHERE nickname LIKE '%s' + */ + LIKE_LEFT(9, "LIKE '%s'"), + + /** + * LIKE '值%',例如:WHERE nickname LIKE 's%' + */ + LIKE_RIGHT(10, "LIKE 's%'"), + + /** + * 包含查询,例如:WHERE age IN (10, 20, 30) + */ + IN(11, "IN"), + + /** + * 不包含查询,例如:WHERE age NOT IN (20, 30) + */ + NOT_IN(12, "NOT IN"), + + /** + * 空查询,例如:WHERE email IS NULL + */ + IS_NULL(13, "IS NULL"), + + /** + * 非空查询,例如:WHERE email IS NOT NULL + */ + IS_NOT_NULL(14, "IS NOT NULL"),; + + private final Integer value; + private final String description; +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/mapper/FieldConfigMapper.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/mapper/FieldConfigMapper.java new file mode 100644 index 0000000..700eaee --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/mapper/FieldConfigMapper.java @@ -0,0 +1,26 @@ +package top.ysoft.admin.generator.mapper; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import top.ysoft.admin.generator.model.entity.FieldConfigDO; +import top.continew.starter.data.mp.base.BaseMapper; + +import java.util.List; + +/** + * 字段配置 Mapper + * + * @author Charles7c + * @since 2023/4/12 23:56 + */ +public interface FieldConfigMapper extends BaseMapper { + + /** + * 根据表名称查询 + * + * @param tableName 表名称 + * @return 字段配置信息 + */ + @Select("SELECT * FROM gen_field_config WHERE table_name = #{tableName} ORDER BY field_sort ASC") + List selectListByTableName(@Param("tableName") String tableName); +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/mapper/GenConfigMapper.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/mapper/GenConfigMapper.java new file mode 100644 index 0000000..4a0edf7 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/mapper/GenConfigMapper.java @@ -0,0 +1,13 @@ +package top.ysoft.admin.generator.mapper; + +import top.ysoft.admin.generator.model.entity.GenConfigDO; +import top.continew.starter.data.mp.base.BaseMapper; + +/** + * 生成配置 Mapper + * + * @author Charles7c + * @since 2023/4/12 23:56 + */ +public interface GenConfigMapper extends BaseMapper { +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/entity/FieldConfigDO.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/entity/FieldConfigDO.java new file mode 100644 index 0000000..ab9564b --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/entity/FieldConfigDO.java @@ -0,0 +1,172 @@ +package top.ysoft.admin.generator.model.entity; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.meta.Column; +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import top.ysoft.admin.generator.enums.FormTypeEnum; +import top.ysoft.admin.generator.enums.QueryTypeEnum; +import top.continew.starter.core.constant.StringConstants; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 字段配置实体 + * + * @author Charles7c + * @since 2023/4/12 20:21 + */ +@Data +@NoArgsConstructor +@TableName("gen_field_config") +@Schema(description = "字段配置信息") +public class FieldConfigDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId + private Long id; + + /** + * 表名称 + */ + @Schema(description = "表名称", example = "sys_user") + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** + * 列名称 + */ + @Schema(description = "列名称", example = "nickname") + @NotBlank(message = "列名称不能为空") + private String columnName; + + /** + * 列类型 + */ + @Schema(description = "列类型", example = "varchar") + @NotBlank(message = "列类型不能为空") + private String columnType; + + /** + * 列大小 + */ + @Schema(description = "列大小", example = "255") + private Long columnSize; + + /** + * 字段名称 + */ + @Schema(description = "字段名称", example = "nickname") + @NotBlank(message = "字段名称不能为空") + private String fieldName; + + /** + * 字段类型 + */ + @Schema(description = "字段类型", example = "String") + @NotBlank(message = "字段类型不能为空") + private String fieldType; + + /** + * 字段排序 + */ + @Schema(description = "字段排序", example = "字段排序") + @NotNull(message = "字段排序不能为空") + private Integer fieldSort; + + /** + * 注释 + */ + @Schema(description = "注释", example = "昵称") + private String comment; + + /** + * 是否必填 + */ + @Schema(description = "是否必填", example = "true") + private Boolean isRequired; + + /** + * 是否在列表中显示 + */ + @Schema(description = "是否在列表中显示", example = "true") + private Boolean showInList; + + /** + * 是否在表单中显示 + */ + @Schema(description = "是否在表单中显示", example = "true") + private Boolean showInForm; + + /** + * 是否在查询中显示 + */ + @Schema(description = "是否在查询中显示", example = "true") + private Boolean showInQuery; + + /** + * 表单类型 + */ + @Schema(description = "表单类型", example = "1") + private FormTypeEnum formType; + + /** + * 查询方式 + */ + @Schema(description = "查询方式", example = "1") + private QueryTypeEnum queryType; + + /** + * 字典编码 + */ + @Schema(description = "字典编码", example = "notice_type") + private String dictCode; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + public FieldConfigDO(@NonNull Column column) { + this.setTableName(column.getTableName()); + this.setColumnName(column.getName()); + this.setColumnType(column.getTypeName()); + this.setColumnSize(column.getSize()); + this.setComment(column.getComment()); + this.setIsRequired(!column.isPk() && !column.isNullable()); + this.setShowInList(true); + this.setShowInForm(this.getIsRequired()); + this.setShowInQuery(this.getIsRequired()); + } + + public void setColumnName(String columnName) { + this.columnName = columnName; + this.fieldName = StrUtil.toCamelCase(this.columnName); + } + + public void setColumnType(String columnType) { + String[] arr = StrUtil.splitToArray(columnType, StringConstants.SPACE); + this.columnType = arr.length > 1 ? arr[0].toLowerCase() : columnType.toLowerCase(); + } + + public void setComment(String comment) { + this.comment = StrUtil.nullToDefault(comment, StringConstants.EMPTY); + } +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/entity/GenConfigDO.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/entity/GenConfigDO.java new file mode 100644 index 0000000..716ac74 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/entity/GenConfigDO.java @@ -0,0 +1,132 @@ +package top.ysoft.admin.generator.model.entity; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.common.constant.RegexConstants; +import top.continew.starter.core.constant.CharConstants; +import top.continew.starter.core.util.StrUtils; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 生成配置实体 + * + * @author Charles7c + * @since 2023/4/12 20:21 + */ +@Data +@NoArgsConstructor +@TableName("gen_config") +@Schema(description = "生成配置信息") +public class GenConfigDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 表名称 + */ + @Schema(description = "表名称", example = "sys_user") + @TableId(type = IdType.INPUT) + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** + * 描述 + */ + @Schema(description = "描述", example = "用户表") + @TableField(exist = false) + private String comment; + + /** + * 模块名称 + */ + @Schema(description = "模块名称", example = "ysoft-admin-system") + @NotBlank(message = "模块名称不能为空") + @Length(max = 60, message = "模块名称不能超过 {max} 个字符") + private String moduleName; + + /** + * 包名称 + */ + @Schema(description = "包名称", example = "top.ysoft.admin.system") + @NotBlank(message = "包名称不能为空") + @Pattern(regexp = RegexConstants.PACKAGE_NAME, message = "包名称格式错误") + @Length(max = 60, message = "包名称不能超过 {max} 个字符") + private String packageName; + + /** + * 业务名称 + */ + @Schema(description = "业务名称", example = "用户") + @NotBlank(message = "业务名称不能为空") + @Length(max = 50, message = "业务名称不能超过 {max} 个字符") + private String businessName; + + /** + * 作者 + */ + @Schema(description = "作者", example = "Charles7c") + @NotBlank(message = "作者名称不能为空") + @Length(max = 100, message = "作者名称不能超过 {max} 个字符") + private String author; + + /** + * 表前缀 + */ + @Schema(description = "表前缀", example = "sys_") + private String tablePrefix; + + /** + * 是否覆盖 + */ + @Schema(description = "是否覆盖", example = "false") + @NotNull(message = "是否覆盖不能为空") + private Boolean isOverride; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 修改时间 + */ + @Schema(description = "修改时间", example = "2023-08-08 08:08:08", type = "string") + @TableField(fill = FieldFill.UPDATE) + private LocalDateTime updateTime; + + public GenConfigDO(String tableName) { + this.setTableName(tableName); + } + + public void setTableName(String tableName) { + this.tableName = tableName; + // 默认表前缀(sys_user -> sys_) + int underLineIndex = StrUtil.indexOf(tableName, CharConstants.UNDERLINE); + if (-1 != underLineIndex) { + this.tablePrefix = StrUtil.subPre(tableName, underLineIndex + 1); + } + } + + /** + * 类名前缀 + */ + @Schema(description = "类名前缀", example = "User") + public String getClassNamePrefix() { + String rawClassName = StrUtils.blankToDefault(this.getTablePrefix(), this.getTableName(), prefix -> StrUtil + .removePrefix(this.getTableName(), prefix)); + return StrUtil.upperFirst(StrUtil.toCamelCase(rawClassName)); + } +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/entity/InnerGenConfigDO.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/entity/InnerGenConfigDO.java new file mode 100644 index 0000000..569f769 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/entity/InnerGenConfigDO.java @@ -0,0 +1,108 @@ +package top.ysoft.admin.generator.model.entity; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import top.continew.starter.core.constant.StringConstants; + +import java.io.Serial; +import java.util.List; +import java.util.Set; + +/** + * 内部生成配置信息 + * + * @author Charles7c + * @since 2024/8/30 19:35 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class InnerGenConfigDO extends GenConfigDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 字段配置信息 + */ + private List fieldConfigs; + + /** + * 生成时间 + */ + private String datetime; + + /** + * API 模块名称 + */ + private String apiModuleName; + + /** + * API 名称 + */ + private String apiName; + + /** + * 类名 + */ + private String className; + + /** + * 类名前缀 + */ + private String classNamePrefix; + + /** + * 子包名称 + */ + private String subPackageName; + + /** + * 字典编码列表 + */ + private Set dictCodes; + + /** + * 是否包含必填字段 + */ + private boolean hasRequiredField; + + /** + * 是否包含字典字段 + */ + private boolean hasDictField; + + /** + * 是否包含 BigDecimal 字段 + */ + private boolean hasBigDecimalField; + + /** + * 是否包含 Time 包字段 + */ + private boolean hasTimeField; + + public InnerGenConfigDO() { + } + + public InnerGenConfigDO(GenConfigDO genConfig) { + BeanUtil.copyProperties(genConfig, this); + this.setDatetime(DateUtil.date().toString("yyyy/MM/dd HH:mm")); + this.setApiName(StrUtil.lowerFirst(this.getClassNamePrefix())); + } + + @Override + public void setPackageName(String packageName) { + super.setPackageName(packageName); + String realPackageName = this.getPackageName(); + this.setApiModuleName(StrUtil.subSuf(realPackageName, StrUtil + .lastIndexOfIgnoreCase(realPackageName, StringConstants.DOT) + 1)); + } + + @Override + public String getClassNamePrefix() { + return super.getClassNamePrefix(); + } +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/query/GenConfigQuery.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/query/GenConfigQuery.java new file mode 100644 index 0000000..bfb011a --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/query/GenConfigQuery.java @@ -0,0 +1,27 @@ +package top.ysoft.admin.generator.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 生成配置查询条件 + * + * @author Charles7c + * @since 2023/4/12 20:21 + */ +@Data +@Schema(description = "生成配置查询条件") +public class GenConfigQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 表名称 + */ + @Schema(description = "表名称", example = "sys_user") + private String tableName; +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/req/GenConfigReq.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/req/GenConfigReq.java new file mode 100644 index 0000000..049ea88 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/req/GenConfigReq.java @@ -0,0 +1,44 @@ +package top.ysoft.admin.generator.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import top.ysoft.admin.generator.model.entity.FieldConfigDO; +import top.ysoft.admin.generator.model.entity.GenConfigDO; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 代码生成配置信息 + * + * @author Charles7c + * @since 2023/8/8 20:40 + */ +@Data +@Schema(description = "代码生成配置信息") +public class GenConfigReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 字段配置信息 + */ + @Valid + @Schema(description = "字段配置信息") + @NotEmpty(message = "字段配置不能为空") + private List fieldConfigs = new ArrayList<>(); + + /** + * 生成配置信息 + */ + @Valid + @Schema(description = "生成配置信息") + @NotNull(message = "生成配置不能为空") + private GenConfigDO genConfig; +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/resp/GeneratePreviewResp.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/resp/GeneratePreviewResp.java new file mode 100644 index 0000000..a8e9e66 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/model/resp/GeneratePreviewResp.java @@ -0,0 +1,44 @@ +package top.ysoft.admin.generator.model.resp; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 生成预览信息 + * + * @author Charles7c + * @since 2023/12/19 21:34 + */ +@Data +@Schema(description = "生成预览信息") +public class GeneratePreviewResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "生成的文件路径", example = "ysoft-admin\\ysoft-admin\\ysoft-admin-generator\\src\\main\\java\\top\\ysoft\\admin\\generator\\service") + private String path; + + /** + * 文件名 + */ + @Schema(description = "文件名", example = "UserController.java") + private String fileName; + + /** + * 内容 + */ + @Schema(description = "内容", example = "public class UserController {...}") + private String content; + + /** + * 是否为后端代码 + */ + @Schema(hidden = true) + @JsonIgnore + private boolean backend; +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/service/GeneratorService.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/service/GeneratorService.java new file mode 100644 index 0000000..5939196 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/service/GeneratorService.java @@ -0,0 +1,80 @@ +package top.ysoft.admin.generator.service; + +import jakarta.servlet.http.HttpServletResponse; +import top.ysoft.admin.generator.model.entity.FieldConfigDO; +import top.ysoft.admin.generator.model.entity.GenConfigDO; +import top.ysoft.admin.generator.model.query.GenConfigQuery; +import top.ysoft.admin.generator.model.req.GenConfigReq; +import top.ysoft.admin.generator.model.resp.GeneratePreviewResp; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.sql.SQLException; +import java.util.List; + +/** + * 代码生成业务接口 + * + * @author Charles7c + * @since 2023/4/12 23:57 + */ +public interface GeneratorService { + + /** + * 分页查询生成配置列表 + * + * @param query 查询条件 + * @param pageQuery 分页查询条件 + * @return 分页列表信息 + */ + PageResp pageGenConfig(GenConfigQuery query, PageQuery pageQuery); + + /** + * 查询生成配置信息 + * + * @param tableName 表名称 + * @return 生成配置信息 + * @throws SQLException / + */ + GenConfigDO getGenConfig(String tableName) throws SQLException; + + /** + * 查询字段配置列表 + * + * @param tableName 表名称 + * @param requireSync 是否需要同步 + * @return 字段配置列表 + */ + List listFieldConfig(String tableName, Boolean requireSync); + + /** + * 保存代码生成配置信息 + * + * @param req 代码生成配置信息 + * @param tableName 表名称 + */ + void saveConfig(GenConfigReq req, String tableName); + + /** + * 生成预览 + * + * @param tableNames 表名称列表 + * @return 预览信息 + */ + List preview(List tableNames); + + /** + * 生成下载代码 + * + * @param tableNames 表名称列表 + * @param response 响应对象 + */ + void downloadCode(List tableNames, HttpServletResponse response); + + /** + * 生成下载代码 + * + * @param tableNames 表名称列表 + */ + void generateCode(List tableNames); +} diff --git a/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/service/impl/GeneratorServiceImpl.java b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/service/impl/GeneratorServiceImpl.java new file mode 100644 index 0000000..d0f8de3 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/java/top/wms/admin/generator/service/impl/GeneratorServiceImpl.java @@ -0,0 +1,433 @@ +package top.ysoft.admin.generator.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.ZipUtil; +import cn.hutool.db.meta.Column; +import cn.hutool.db.meta.Table; +import cn.hutool.extra.template.TemplateConfig; +import cn.hutool.extra.template.TemplateEngine; +import cn.hutool.extra.template.TemplateUtil; +import cn.hutool.extra.template.engine.freemarker.FreemarkerEngine; +import cn.hutool.system.SystemUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import freemarker.template.Configuration; +import freemarker.template.DefaultObjectWrapper; +import freemarker.template.DefaultObjectWrapperBuilder; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import top.ysoft.admin.generator.config.properties.GeneratorProperties; +import top.ysoft.admin.generator.enums.FormTypeEnum; +import top.ysoft.admin.generator.enums.QueryTypeEnum; +import top.ysoft.admin.generator.mapper.FieldConfigMapper; +import top.ysoft.admin.generator.mapper.GenConfigMapper; +import top.ysoft.admin.generator.model.entity.FieldConfigDO; +import top.ysoft.admin.generator.model.entity.GenConfigDO; +import top.ysoft.admin.generator.model.entity.InnerGenConfigDO; +import top.ysoft.admin.generator.model.query.GenConfigQuery; +import top.ysoft.admin.generator.model.req.GenConfigReq; +import top.ysoft.admin.generator.model.resp.GeneratePreviewResp; +import top.ysoft.admin.generator.service.GeneratorService; +import top.continew.starter.core.autoconfigure.project.ProjectProperties; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.exception.BusinessException; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.data.core.enums.DatabaseType; +import top.continew.starter.data.core.util.MetaUtils; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.web.util.FileUploadUtils; + +import javax.sql.DataSource; +import java.io.File; +import java.sql.SQLException; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 代码生成业务实现 + * + * @author Charles7c + * @since 2023/4/12 23:58 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class GeneratorServiceImpl implements GeneratorService { + + private final DataSource dataSource; + private final GeneratorProperties generatorProperties; + private final ProjectProperties projectProperties; + private final FieldConfigMapper fieldConfigMapper; + private final GenConfigMapper genConfigMapper; + private static final List TIME_PACKAGE_CLASS = Arrays.asList("LocalDate", "LocalTime", "LocalDateTime"); + + @Override + public PageResp pageGenConfig(GenConfigQuery query, PageQuery pageQuery) { + // 查询所有表 + List tableList = MetaUtils.getTables(dataSource); + tableList.removeIf(table -> StrUtil.equalsAnyIgnoreCase(table.getTableName(), generatorProperties + .getExcludeTables())); + String tableName = query.getTableName(); + if (StrUtil.isNotBlank(tableName)) { + tableList.removeIf(table -> !StrUtil.containsAnyIgnoreCase(table.getTableName(), tableName)); + } + // 查询生成配置 + List list = tableList.parallelStream().map(table -> { + GenConfigDO genConfig = genConfigMapper.selectById(table.getTableName()); + if (genConfig == null) { + genConfig = new GenConfigDO(table.getTableName()); + } + genConfig.setComment(table.getComment()); + return genConfig; + }) + .sorted(Comparator.comparing(GenConfigDO::getTableName) + .thenComparing(GenConfigDO::getUpdateTime, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(GenConfigDO::getCreateTime, Comparator.nullsLast(Comparator.naturalOrder()))) + .toList(); + // 分页 + return PageResp.build(pageQuery.getPage(), pageQuery.getSize(), list); + } + + @Override + public GenConfigDO getGenConfig(String tableName) throws SQLException { + GenConfigDO genConfig = genConfigMapper.selectById(tableName); + if (null == genConfig) { + genConfig = new GenConfigDO(tableName); + // 默认包名(当前包名) + String packageName = ClassUtil.getPackage(GeneratorService.class); + genConfig.setPackageName(StrUtil.subBefore("top.ysoft.admin.", StringConstants.DOT, true)); + genConfig.setModuleName("ysoft-module-system"); + // 默认业务名(表注释) + List
tableList = MetaUtils.getTables(dataSource, tableName); + if (CollUtil.isNotEmpty(tableList)) { + Table table = tableList.get(0); + genConfig.setBusinessName(StrUtil.replace(table.getComment(), "表", StringConstants.EMPTY)); + } + // 默认作者名称(上次保存使用的作者名称) + GenConfigDO lastGenConfig = genConfigMapper.selectOne(Wrappers.lambdaQuery(GenConfigDO.class) + .orderByDesc(GenConfigDO::getCreateTime) + .last("LIMIT 1")); + if (null != lastGenConfig) { + genConfig.setAuthor(lastGenConfig.getAuthor()); + } + } + return genConfig; + } + + @Override + public List listFieldConfig(String tableName, Boolean requireSync) { + List fieldConfigList = fieldConfigMapper.selectListByTableName(tableName); + if (CollUtil.isNotEmpty(fieldConfigList) && Boolean.FALSE.equals(requireSync)) { + return fieldConfigList; + } + List latestFieldConfigList = new ArrayList<>(); + // 获取最新数据表列信息 + Collection columnList = MetaUtils.getColumns(dataSource, tableName); + // 获取数据库对应的类型映射配置 + DatabaseType databaseType = MetaUtils.getDatabaseType(dataSource); + Map> typeMappingMap = generatorProperties.getTypeMappings().get(databaseType); + CheckUtils.throwIfEmpty(typeMappingMap, "请先配置对应数据库的类型映射"); + Set>> typeMappingEntrySet = typeMappingMap.entrySet(); + // 新增或更新字段配置 + Map fieldConfigMap = fieldConfigList.stream() + .collect(Collectors.toMap(FieldConfigDO::getColumnName, Function.identity(), (key1, key2) -> key2)); + int i = 1; + for (Column column : columnList) { + FieldConfigDO fieldConfig = Optional.ofNullable(fieldConfigMap.get(column.getName())) + .orElseGet(() -> new FieldConfigDO(column)); + // 更新已有字段配置 + if (null != fieldConfig.getCreateTime()) { + fieldConfig.setColumnType(column.getTypeName()); + fieldConfig.setColumnSize(column.getSize()); + } + String fieldType = typeMappingEntrySet.stream() + .filter(entry -> entry.getValue().contains(fieldConfig.getColumnType())) + .map(Map.Entry::getKey) + .findFirst() + .orElse(null); + fieldConfig.setFieldType(fieldType); + fieldConfig.setFieldSort(i++); + latestFieldConfigList.add(fieldConfig); + } + return latestFieldConfigList; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveConfig(GenConfigReq req, String tableName) { + // 保存字段配置(先删除再保存) + fieldConfigMapper.delete(Wrappers.lambdaQuery(FieldConfigDO.class).eq(FieldConfigDO::getTableName, tableName)); + List fieldConfigList = req.getFieldConfigs(); + for (int i = 0; i < fieldConfigList.size(); i++) { + FieldConfigDO fieldConfig = fieldConfigList.get(i); + // 重新设置排序 + fieldConfig.setFieldSort(i + 1); + if (Boolean.TRUE.equals(fieldConfig.getShowInForm())) { + fieldConfig.setFormType(ObjectUtil.defaultIfNull(fieldConfig.getFormType(), FormTypeEnum.INPUT)); + } else { + // 在表单中不显示,不需要设置必填 + fieldConfig.setIsRequired(false); + } + if (Boolean.TRUE.equals(fieldConfig.getShowInQuery())) { + fieldConfig.setFormType(ObjectUtil.defaultIfNull(fieldConfig.getFormType(), FormTypeEnum.INPUT)); + fieldConfig.setQueryType(ObjectUtil.defaultIfNull(fieldConfig.getQueryType(), QueryTypeEnum.EQ)); + } else { + // 在查询中不显示,不需要设置查询方式 + fieldConfig.setQueryType(null); + } + // 既不在表单也不在查询中显示,不需要设置表单类型 + if (Boolean.FALSE.equals(fieldConfig.getShowInForm()) && Boolean.FALSE.equals(fieldConfig + .getShowInQuery())) { + fieldConfig.setFormType(null); + } + fieldConfig.setTableName(tableName); + } + fieldConfigMapper.insert(fieldConfigList); + // 保存或更新生成配置信息 + GenConfigDO newGenConfig = req.getGenConfig(); + GenConfigDO oldGenConfig = genConfigMapper.selectById(tableName); + if (null != oldGenConfig) { + BeanUtil.copyProperties(newGenConfig, oldGenConfig); + genConfigMapper.updateById(oldGenConfig); + } else { + genConfigMapper.insert(newGenConfig); + } + } + + @Override + public List preview(List tableNames) { + List generatePreviewList = new ArrayList<>(); + for (String tableName : tableNames) { + generatePreviewList.addAll(this.preview(tableName)); + } + return generatePreviewList; + } + + @Override + public void downloadCode(List tableNames, HttpServletResponse response) { + try { + String tempDir = SystemUtil.getUserInfo().getTempDir(); + // 删除旧代码 + FileUtil.del(tempDir + projectProperties.getAppName()); + tableNames.forEach(tableName -> { + // 初始化配置及数据 + List generatePreviewList = this.preview(tableName); + // 生成代码 + this.generateCode(generatePreviewList, genConfigMapper.selectById(tableName)); + }); + // 打包下载 + File tempDirFile = new File(tempDir, projectProperties.getAppName()); + String zipFilePath = tempDirFile.getPath() + jodd.io.ZipUtil.ZIP_EXT; + ZipUtil.zip(tempDirFile.getPath(), zipFilePath); + FileUploadUtils.download(response, new File(zipFilePath)); + } catch (Exception e) { + log.error("Generate code of table '{}' occurred an error. {}", tableNames, e.getMessage(), e); + throw new BusinessException("代码生成失败,请手动清理生成文件"); + } + } + + @Override + public void generateCode(List tableNames) { + try { + String projectPath = System.getProperty("user.dir"); + tableNames.forEach(tableName -> { + // 初始化配置及数据 + List generatePreviewList = this.preview(tableName); + // 生成代码 + for (GeneratePreviewResp generatePreview : generatePreviewList) { + // 后端:ysoft-admin/ysoft-system/src/main/java/top/ysoft/admin/system/service/impl/XxxServiceImpl.java + // 前端:ysoft-admin/ysoft-admin-ui/src/views/system/user/index.vue + File file = new File(projectPath + generatePreview.getPath() + .replace("ysoft-admin\\ysoft-admin", ""), generatePreview.getFileName()); + // 如果已经存在,且不允许覆盖,则跳过 + if (!file.exists() || Boolean.TRUE.equals(genConfigMapper.selectById(tableName).getIsOverride())) { + FileUtil.writeUtf8String(generatePreview.getContent(), file); + } + } + }); + + } catch (Exception e) { + log.error("Generate code of table '{}' occurred an error. {}", tableNames, e.getMessage(), e); + throw new BusinessException("代码生成失败,请手动清理生成文件"); + } + } + + /** + * 生成预览 + * + * @param tableName 表名称 + * @return 预览信息 + */ + private List preview(String tableName) { + List generatePreviewList = new ArrayList<>(); + // 初始化配置 + GenConfigDO genConfig = genConfigMapper.selectById(tableName); + CheckUtils.throwIfNull(genConfig, "请先进行数据表 [{}] 生成配置", tableName); + List fieldConfigList = fieldConfigMapper.selectListByTableName(tableName); + CheckUtils.throwIfEmpty(fieldConfigList, "请先进行数据表 [{}] 字段配置", tableName); + InnerGenConfigDO innerGenConfig = new InnerGenConfigDO(genConfig); + // 渲染代码 + String classNamePrefix = innerGenConfig.getClassNamePrefix(); + Map templateConfigMap = generatorProperties.getTemplateConfigs(); + TemplateEngine engine = TemplateUtil + .createEngine(new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH)); + // 在模板中允许使用静态方法 + if (engine instanceof FreemarkerEngine freemarkerEngine) { + DefaultObjectWrapper wrapper = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_33).build(); + freemarkerEngine.getConfiguration().setSharedVariable("statics", wrapper.getStaticModels()); + } + for (Map.Entry templateConfigEntry : templateConfigMap.entrySet()) { + GeneratorProperties.TemplateConfig templateConfig = templateConfigEntry.getValue(); + // 移除需要忽略的字段 + innerGenConfig.setFieldConfigs(fieldConfigList.stream() + .filter(fieldConfig -> !StrUtil.equalsAny(fieldConfig.getFieldName(), templateConfig + .getExcludeFields())) + .toList()); + // 预处理配置 + this.pretreatment(innerGenConfig); + // 处理其他配置 + innerGenConfig.setSubPackageName(templateConfig.getPackageName()); + String classNameSuffix = templateConfigEntry.getKey(); + String className = classNamePrefix + StrUtil.blankToDefault(templateConfig.getSuffix(), classNameSuffix); + innerGenConfig.setClassName(className); + boolean isBackend = templateConfig.isBackend(); + String extension = templateConfig.getExtension(); + GeneratePreviewResp generatePreview = new GeneratePreviewResp(); + generatePreview.setBackend(isBackend); + generatePreviewList.add(generatePreview); + String fileName = className + extension; + if (!isBackend) { + fileName = ".vue".equals(extension) && "index".equals(classNameSuffix) + ? "index.vue" + : this.getFrontendFileName(classNamePrefix, className, extension); + } + generatePreview.setFileName(fileName); + generatePreview.setContent(engine.getTemplate(templateConfig.getTemplatePath()) + .render(BeanUtil.beanToMap(innerGenConfig))); + this.setPreviewPath(generatePreview, innerGenConfig, templateConfig); + } + return generatePreviewList; + } + + /** + * 设置预览路径 + * + * @param generatePreview 预览信息 + * @param genConfig 生成配置 + * @param templateConfig 模板配置 + */ + private void setPreviewPath(GeneratePreviewResp generatePreview, + InnerGenConfigDO genConfig, + GeneratorProperties.TemplateConfig templateConfig) { + // 获取前后端基础路径 + String backendBasicPackagePath = this.buildBackendBasicPackagePath(genConfig, templateConfig); + String frontendBasicPackagePath = String.join(File.separator, projectProperties.getAppName(), projectProperties + .getAppName() + "-ui"); + String packagePath; + if (generatePreview.isBackend()) { + // 例如:ysoft-admin/ysoft-system/src/main/java/top/ysoft/admin/system/service/impl + packagePath = String.join(File.separator, backendBasicPackagePath, templateConfig.getPackageName() + .replace(StringConstants.DOT, File.separator)); + } else { + // 例如:ysoft-admin/ysoft-admin-ui/src/views/system + packagePath = String.join(File.separator, frontendBasicPackagePath, templateConfig.getPackageName() + .replace(StringConstants.SLASH, File.separator), genConfig.getApiModuleName()); + // 例如:ysoft-admin/ysoft-admin-ui/src/views/system/user + packagePath = ".vue".equals(templateConfig.getExtension()) + ? packagePath + File.separator + StrUtil.lowerFirst(genConfig.getClassNamePrefix()) + : packagePath; + } + generatePreview.setPath(packagePath); + } + + /** + * 生成代码 + * + * @param generatePreviewList 生成预览列表 + * @param genConfig 生成配置 + */ + private void generateCode(List generatePreviewList, GenConfigDO genConfig) { + for (GeneratePreviewResp generatePreview : generatePreviewList) { + // 后端:ysoft-admin/ysoft-system/src/main/java/top/ysoft/admin/system/service/impl/XxxServiceImpl.java + // 前端:ysoft-admin/ysoft-admin-ui/src/views/system/user/index.vue + File file = new File(SystemUtil.getUserInfo().getTempDir() + generatePreview.getPath(), generatePreview + .getFileName()); + // 如果已经存在,且不允许覆盖,则跳过 + if (!file.exists() || Boolean.TRUE.equals(genConfig.getIsOverride())) { + FileUtil.writeUtf8String(generatePreview.getContent(), file); + } + } + } + + /** + * 构建后端包路径 + * + * @param genConfig 生成配置 + * @param templateConfig 模板配置 + * @return 后端包路径 + */ + private String buildBackendBasicPackagePath(GenConfigDO genConfig, + GeneratorProperties.TemplateConfig templateConfig) { + String extension = templateConfig.getExtension(); + // 例如:ysoft-admin/ysoft-system/src/main/java/top/ysoft/admin/system + return String.join(File.separator, projectProperties.getAppName(), projectProperties.getAppName(), genConfig + .getModuleName(), "src", "main", FileNameUtil.EXT_JAVA.equals(extension) + ? "java" + : "resources") + (FileNameUtil.EXT_JAVA.equals(extension) + ? File.separator + genConfig.getPackageName().replace(StringConstants.DOT, File.separator) + : StringConstants.EMPTY); + } + + /** + * 获取前端文件名 + * + * @param classNamePrefix 类名前缀 + * @param className 类名 + * @param extension 扩展名 + * @return 前端文件名 + */ + private String getFrontendFileName(String classNamePrefix, String className, String extension) { + return (".ts".equals(extension) ? StrUtil.lowerFirst(classNamePrefix) : className) + extension; + } + + /** + * 预处理生成配置 + * + * @param genConfig 生成配置 + */ + private void pretreatment(InnerGenConfigDO genConfig) { + List fieldConfigList = genConfig.getFieldConfigs(); + // 统计部分特殊字段特征 + Set dictCodeSet = new HashSet<>(); + for (FieldConfigDO fieldConfig : fieldConfigList) { + String fieldType = fieldConfig.getFieldType(); + // 必填项 + if (Boolean.TRUE.equals(fieldConfig.getIsRequired())) { + genConfig.setHasRequiredField(true); + } + // 数据类型 + if ("BigDecimal".equals(fieldType)) { + genConfig.setHasBigDecimalField(true); + } + if (TIME_PACKAGE_CLASS.contains(fieldType)) { + genConfig.setHasTimeField(true); + } + // 字典码 + if (StrUtil.isNotBlank(fieldConfig.getDictCode())) { + genConfig.setHasDictField(true); + dictCodeSet.add(fieldConfig.getDictCode()); + } + } + genConfig.setDictCodes(dictCodeSet); + } +} diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Controller.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Controller.ftl new file mode 100644 index 0000000..36753ca --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Controller.ftl @@ -0,0 +1,30 @@ +package top.ysoft.admin.controller.${subPackageName}; + +import top.continew.starter.extension.crud.enums.Api; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.ysoft.admin.common.controller.BaseController; +import ${packageName}.model.query.${classNamePrefix}Query; +import ${packageName}.model.req.${classNamePrefix}Req; +import ${packageName}.model.resp.${classNamePrefix}DetailResp; +import ${packageName}.model.resp.${classNamePrefix}Resp; +import ${packageName}.service.${classNamePrefix}Service; + +/** + * ${businessName}管理 API + * + * @author ${author} + * @since ${datetime} + */ +@Tag(name = "${businessName}管理 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/${apiModuleName}/${apiName}", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}) +public class ${className} extends BaseController<${classNamePrefix}Service, ${classNamePrefix}Resp, ${classNamePrefix}Resp, ${classNamePrefix}Query, ${classNamePrefix}Req> { + +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/DetailResp.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/DetailResp.ftl new file mode 100644 index 0000000..e257192 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/DetailResp.ftl @@ -0,0 +1,44 @@ +package ${packageName}.${subPackageName}; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +<#if hasTimeField> +import java.time.*; + +<#if hasBigDecimalField> +import java.math.BigDecimal; + + +/** + * ${businessName}详情信息 + * + * @author ${author} + * @since ${datetime} + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "${businessName}详情信息") +public class ${className} extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; +<#if fieldConfigs??> + <#list fieldConfigs as fieldConfig> + + /** + * ${fieldConfig.comment} + */ + @Schema(description = "${fieldConfig.comment}") + @ExcelProperty(value = "${fieldConfig.comment}") + private ${fieldConfig.fieldType} ${fieldConfig.fieldName}; + + +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Entity.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Entity.ftl new file mode 100644 index 0000000..fa24cda --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Entity.ftl @@ -0,0 +1,38 @@ +package ${packageName}.${subPackageName}; + +import lombok.Data; + +import com.baomidou.mybatisplus.annotation.TableName; + +import top.ysoft.admin.common.model.entity.BaseDO; + +import java.io.Serial; +<#if hasTimeField> +import java.time.*; + +<#if hasBigDecimalField> +import java.math.BigDecimal; + + +/** + * ${businessName}实体 + * + * @author ${author} + * @since ${datetime} + */ +@Data +@TableName("${tableName}") +public class ${className} extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; +<#if fieldConfigs??> + <#list fieldConfigs as fieldConfig> + + /** + * ${fieldConfig.comment} + */ + private ${fieldConfig.fieldType} ${fieldConfig.fieldName}; + + +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Mapper.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Mapper.ftl new file mode 100644 index 0000000..63767c6 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Mapper.ftl @@ -0,0 +1,16 @@ +package ${packageName}.${subPackageName}; + +import top.continew.starter.data.mp.base.BaseMapper; +import ${packageName}.model.entity.${classNamePrefix}DO; +import org.springframework.stereotype.Repository; + +/** +* ${businessName} Mapper +* +* @author ${author} +* @since ${datetime} +*/ +@Repository +public interface ${className} extends BaseMapper<${classNamePrefix}DO> { + +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/MapperXml.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/MapperXml.ftl new file mode 100644 index 0000000..a4d6605 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/MapperXml.ftl @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Menu.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Menu.ftl new file mode 100644 index 0000000..6560b76 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Menu.ftl @@ -0,0 +1,41 @@ +SET @parentId = ${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}; +-- ${businessName}管理菜单 +INSERT INTO `sys_menu` + (`id`, `title`, `parent_id`, `type`, `path`, `name`, `component`, `redirect`, `icon`, `is_external`, `is_cache`, `is_hidden`, `permission`, `sort`, `status`, `create_user`, `create_time`) +VALUES + (@parentId, '${businessName}管理', 1000, 2, '/${apiModuleName}/${apiName}', '${classNamePrefix}', '${apiModuleName}/${apiName}/index', NULL, NULL, b'0', b'0', b'0', NULL, 1, 1, 1, NOW()); + +-- ${businessName}管理按钮 +INSERT INTO `sys_menu` + (`id`, `title`, `parent_id`, `type`, `permission`, `sort`, `status`, `create_user`, `create_time`) +VALUES + (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '列表', @parentId, 3, '${apiModuleName}:${apiName}:list', 1, 1, 1, NOW()), + (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '详情', @parentId, 3, '${apiModuleName}:${apiName}:detail', 2, 1, 1, NOW()), + (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '新增', @parentId, 3, '${apiModuleName}:${apiName}:add', 3, 1, 1, NOW()), + (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '修改', @parentId, 3, '${apiModuleName}:${apiName}:update', 4, 1, 1, NOW()), + (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '删除', @parentId, 3, '${apiModuleName}:${apiName}:delete', 5, 1, 1, NOW()), + (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '导出', @parentId, 3, '${apiModuleName}:${apiName}:export', 6, 1, 1, NOW()); + +<#---- PostgreSQL(切换 PostgreSQL 数据库时请注释掉其他数据库脚本,并解开此段注释)--> +<#--DO $$--> +<#-- DECLARE sys_menu_id_seq INT8 := ${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c};--> +<#--BEGIN--> + +<#-- -- ${businessName}管理菜单--> +<#-- INSERT INTO "sys_menu"--> +<#-- ("id", "title", "parent_id", "type", "path", "name", "component", "redirect", "icon", "is_external", "is_cache", "is_hidden", "permission", "sort", "status", "create_user", "create_time")--> +<#-- VALUES--> +<#-- (sys_menu_id_seq, '${businessName}管理', 1000, 2, '/${apiModuleName}/${apiName}', '${classNamePrefix}', '${apiModuleName}/${apiName}/index', NULL, NULL, false, false, false, NULL, 1, 1, 1, NOW());--> + +<#-- -- ${businessName}管理按钮--> +<#-- INSERT INTO "sys_menu"--> +<#-- ("id", "title", "parent_id", "type", "permission", "sort", "status", "create_user", "create_time")--> +<#-- VALUES--> +<#-- (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '列表', sys_menu_id_seq, 3, '${apiModuleName}:${apiName}:list', 1, 1, 1, NOW()),--> +<#-- (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '详情', sys_menu_id_seq, 3, '${apiModuleName}:${apiName}:detail', 2, 1, 1, NOW()),--> +<#-- (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '新增', sys_menu_id_seq, 3, '${apiModuleName}:${apiName}:add', 3, 1, 1, NOW()),--> +<#-- (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '修改', sys_menu_id_seq, 3, '${apiModuleName}:${apiName}:update', 4, 1, 1, NOW()),--> +<#-- (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '删除', sys_menu_id_seq, 3, '${apiModuleName}:${apiName}:delete', 5, 1, 1, NOW()),--> +<#-- (${statics["cn.hutool.core.util.IdUtil"].getSnowflakeNextId()?c}, '导出', sys_menu_id_seq, 3, '${apiModuleName}:${apiName}:export', 6, 1, 1, NOW());--> + +<#--END $$;--> \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Query.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Query.ftl new file mode 100644 index 0000000..e871d2d --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Query.ftl @@ -0,0 +1,48 @@ +package ${packageName}.${subPackageName}; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +<#if hasTimeField> +import java.time.*; + +<#if hasBigDecimalField> +import java.math.BigDecimal; + + +/** + * ${businessName}查询条件 + * + * @author ${author} + * @since ${datetime} + */ +@Data +@Schema(description = "${businessName}查询条件") +public class ${className} implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; +<#if fieldConfigs??> + <#list fieldConfigs as fieldConfig> + <#if fieldConfig.showInQuery> + + /** + * ${fieldConfig.comment} + */ + @Schema(description = "${fieldConfig.comment}") + @Query(type = QueryType.${fieldConfig.queryType}) + <#if fieldConfig.queryType = 'IN' || fieldConfig.queryType = 'NOT_IN' || fieldConfig.queryType = 'BETWEEN'> + private ${fieldConfig.fieldType}[] ${fieldConfig.fieldName}; + <#else> + private ${fieldConfig.fieldType} ${fieldConfig.fieldName}; + + + + +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Req.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Req.ftl new file mode 100644 index 0000000..975057f --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Req.ftl @@ -0,0 +1,56 @@ +package ${packageName}.${subPackageName}; + +<#if hasRequiredField> +import jakarta.validation.constraints.*; + + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.hibernate.validator.constraints.Length; + +import java.io.Serial; +import java.io.Serializable; +<#if hasTimeField> +import java.time.*; + +<#if hasBigDecimalField> +import java.math.BigDecimal; + + +/** + * 创建或修改${businessName}参数 + * + * @author ${author} + * @since ${datetime} + */ +@Data +@Schema(description = "创建或修改${businessName}参数") +public class ${className} implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; +<#if fieldConfigs??> + <#list fieldConfigs as fieldConfig> + <#if fieldConfig.showInForm> + + /** + * ${fieldConfig.comment} + */ + @Schema(description = "${fieldConfig.comment}") + <#if fieldConfig.isRequired> + <#if fieldConfig.fieldType = 'String'> + @NotBlank(message = "${fieldConfig.comment}不能为空") + <#else> + @NotNull(message = "${fieldConfig.comment}不能为空") + + + <#if fieldConfig.fieldType = 'String' && fieldConfig.columnSize??> + @Length(max = ${fieldConfig.columnSize?c}, message = "${fieldConfig.comment}长度不能超过 {max} 个字符") + + private ${fieldConfig.fieldType} ${fieldConfig.fieldName}; + + + +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Resp.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Resp.ftl new file mode 100644 index 0000000..6ca27b8 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Resp.ftl @@ -0,0 +1,41 @@ +package ${packageName}.${subPackageName}; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import top.ysoft.admin.common.model.resp.BaseDetailResp; + +import java.io.Serial; +<#if hasTimeField> +import java.time.*; + +<#if hasBigDecimalField> +import java.math.BigDecimal; + + +/** + * ${businessName}信息 + * + * @author ${author} + * @since ${datetime} + */ +@Data +@Schema(description = "${businessName}信息") +public class ${className} extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; +<#if fieldConfigs??> + <#list fieldConfigs as fieldConfig> + <#if fieldConfig.showInList> + + /** + * ${fieldConfig.comment} + */ + @Schema(description = "${fieldConfig.comment}") + private ${fieldConfig.fieldType} ${fieldConfig.fieldName}; + + + +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Service.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Service.ftl new file mode 100644 index 0000000..f9ef77d --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/Service.ftl @@ -0,0 +1,17 @@ +package ${packageName}.${subPackageName}; + +import top.continew.starter.extension.crud.service.BaseService; +import ${packageName}.model.query.${classNamePrefix}Query; +import ${packageName}.model.req.${classNamePrefix}Req; +import ${packageName}.model.resp.${classNamePrefix}DetailResp; +import ${packageName}.model.resp.${classNamePrefix}Resp; + +/** + * ${businessName}业务接口 + * + * @author ${author} + * @since ${datetime} + */ +public interface ${className} extends BaseService<${classNamePrefix}Resp, ${classNamePrefix}Resp, ${classNamePrefix}Query, ${classNamePrefix}Req> { + +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/ServiceImpl.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/ServiceImpl.ftl new file mode 100644 index 0000000..77d8178 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/backend/ServiceImpl.ftl @@ -0,0 +1,26 @@ +package ${packageName}.${subPackageName}; + +import lombok.RequiredArgsConstructor; + +import org.springframework.stereotype.Service; + +import top.continew.starter.extension.crud.service.BaseServiceImpl; +import ${packageName}.mapper.${classNamePrefix}Mapper; +import ${packageName}.model.entity.${classNamePrefix}DO; +import ${packageName}.model.query.${classNamePrefix}Query; +import ${packageName}.model.req.${classNamePrefix}Req; +import ${packageName}.model.resp.${classNamePrefix}DetailResp; +import ${packageName}.model.resp.${classNamePrefix}Resp; +import ${packageName}.service.${classNamePrefix}Service; + +/** + * ${businessName}业务实现 + * + * @author ${author} + * @since ${datetime} + */ +@Service +@RequiredArgsConstructor +public class ${className} extends BaseServiceImpl<${classNamePrefix}Mapper, ${classNamePrefix}DO, ${classNamePrefix}Resp, ${classNamePrefix}Resp, ${classNamePrefix}Query, ${classNamePrefix}Req> implements ${classNamePrefix}Service { + +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/AddModal.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/AddModal.ftl new file mode 100644 index 0000000..662c463 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/AddModal.ftl @@ -0,0 +1,141 @@ + + + + + diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/DetailDrawer.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/DetailDrawer.ftl new file mode 100644 index 0000000..97db30c --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/DetailDrawer.ftl @@ -0,0 +1,42 @@ + + + + + diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/api.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/api.ftl new file mode 100644 index 0000000..e737b87 --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/api.ftl @@ -0,0 +1,57 @@ +import http from '@/utils/http' + +const BASE_URL = '/${apiModuleName}/${apiName}' + +export interface ${classNamePrefix}Resp { +<#if fieldConfigs??> +<#list fieldConfigs as fieldConfig> + <#if fieldConfig.showInList> + ${fieldConfig.fieldName}: string + + + createUserString: string + updateUserString: string + disabled: boolean + +} +export interface ${classNamePrefix}Query { +<#if fieldConfigs??> +<#list fieldConfigs as fieldConfig> + <#if fieldConfig.showInQuery> + ${fieldConfig.fieldName}: string | undefined + + + + sort: Array +} +export interface ${classNamePrefix}PageQuery extends ${classNamePrefix}Query, PageQuery {} + +/** @desc 查询${businessName}列表 */ +export function list${classNamePrefix}(query: ${classNamePrefix}PageQuery) { + return http.get>(`${'$'}{BASE_URL}`, query) +} + +/** @desc 查询${businessName}详情 */ +export function get${classNamePrefix}(id: string) { + return http.get<${classNamePrefix}DetailResp>(`${'$'}{BASE_URL}/${'$'}{id}`) +} + +/** @desc 新增${businessName} */ +export function add${classNamePrefix}(data: any) { + return http.post(`${'$'}{BASE_URL}`, data) +} + +/** @desc 修改${businessName} */ +export function update${classNamePrefix}(data: any, id: string) { + return http.put(`${'$'}{BASE_URL}/${'$'}{id}`, data) +} + +/** @desc 删除${businessName} */ +export function delete${classNamePrefix}(id: string) { + return http.del(`${'$'}{BASE_URL}/${'$'}{id}`) +} + +/** @desc 导出${businessName} */ +export function export${classNamePrefix}(query: ${classNamePrefix}Query) { + return http.download(`${'$'}{BASE_URL}/export`, query) +} diff --git a/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/index.ftl b/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/index.ftl new file mode 100644 index 0000000..a05031a --- /dev/null +++ b/wms-plugin/wms-plugin-generator/src/main/resources/templates/frontend/index.ftl @@ -0,0 +1,202 @@ + + + + + diff --git a/wms-plugin/wms-plugin-open/.flattened-pom.xml b/wms-plugin/wms-plugin-open/.flattened-pom.xml new file mode 100644 index 0000000..3ee25a7 --- /dev/null +++ b/wms-plugin/wms-plugin-open/.flattened-pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + top.ysoft + ysoft-plugin + 3.6.0-SNAPSHOT + + ysoft-plugin-open + 3.6.0-SNAPSHOT + 能力开放插件(包括应用管理、API开放授权、API开发等) + + + GNU LESSER GENERAL PUBLIC LICENSE + http://www.gnu.org/licenses/lgpl.html + + + diff --git a/wms-plugin/wms-plugin-open/pom.xml b/wms-plugin/wms-plugin-open/pom.xml new file mode 100644 index 0000000..33a3c92 --- /dev/null +++ b/wms-plugin/wms-plugin-open/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + top.ysoft + ysoft-plugin + ${revision} + + + ysoft-plugin-open + 能力开放插件(包括应用管理、API开放授权、API开发等) + \ No newline at end of file diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/handler/SaCheckPermissionHandler.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/handler/SaCheckPermissionHandler.java new file mode 100644 index 0000000..b9ef7af --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/handler/SaCheckPermissionHandler.java @@ -0,0 +1,32 @@ +package top.ysoft.admin.open.handler; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; +import org.springframework.stereotype.Component; +import top.ysoft.admin.open.util.OpenApiUtils; + +import java.lang.reflect.Method; + +import static cn.dev33.satoken.annotation.handler.SaCheckPermissionHandler._checkMethod; + +/** + * 重定义注解 SaCheckPermission 的处理器 + * + * @author chengzi + * @since 2024/10/25 12:03 + */ +@Component +public class SaCheckPermissionHandler implements SaAnnotationHandlerInterface { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckPermission.class; + } + + @Override + public void checkMethod(SaCheckPermission at, Method method) { + if (!OpenApiUtils.isSignParamExists()) { + _checkMethod(at.type(), at.value(), at.mode(), at.orRole()); + } + } +} diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/mapper/AppMapper.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/mapper/AppMapper.java new file mode 100644 index 0000000..cf2dd59 --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/mapper/AppMapper.java @@ -0,0 +1,25 @@ +package top.ysoft.admin.open.mapper; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import top.ysoft.admin.open.model.entity.AppDO; +import top.continew.starter.data.mp.base.BaseMapper; +import top.continew.starter.security.crypto.annotation.FieldEncrypt; + +/** + * 应用 Mapper + * + * @author chengzi + * @since 2024/10/17 16:03 + */ +public interface AppMapper extends BaseMapper { + + /** + * 根据 Access Key 查询 + * + * @param accessKey Access Key + * @return 应用信息 + */ + @Select("select * from sys_app where access_key = #{accessKey}") + AppDO selectByAccessKey(@FieldEncrypt @Param("accessKey") String accessKey); +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/entity/AppDO.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/entity/AppDO.java new file mode 100644 index 0000000..f163e52 --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/entity/AppDO.java @@ -0,0 +1,69 @@ +package top.ysoft.admin.open.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.common.model.entity.BaseDO; +import top.continew.starter.security.crypto.annotation.FieldEncrypt; + +import java.io.Serial; +import java.time.LocalDateTime; + +/** + * 应用实体 + * + * @author chengzi + * @author Charles7c + * @since 2024/10/17 16:03 + */ +@Data +@TableName("sys_app") +public class AppDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + private String name; + + /** + * Access Key(访问密钥) + */ + @FieldEncrypt + private String accessKey; + + /** + * Secret Key(私有密钥) + */ + @FieldEncrypt + private String secretKey; + + /** + * 失效时间 + */ + private LocalDateTime expireTime; + + /** + * 描述 + */ + private String description; + + /** + * 状态 + */ + private DisEnableStatusEnum status; + + /** + * 是否已过期 + * + * @return true:已过期;false:未过期 + */ + public boolean isExpired() { + if (expireTime == null) { + return false; + } + return LocalDateTime.now().isAfter(expireTime); + } +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/query/AppQuery.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/query/AppQuery.java new file mode 100644 index 0000000..204049f --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/query/AppQuery.java @@ -0,0 +1,31 @@ +package top.ysoft.admin.open.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 应用查询条件 + * + * @author chengzi + * @author Charles7c + * @since 2024/10/17 16:03 + */ +@Data +@Schema(description = "应用查询条件") +public class AppQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 关键词 + */ + @Schema(description = "关键词", example = "应用1") + @Query(columns = {"name", "description"}, type = QueryType.LIKE) + private String description; +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/req/AppReq.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/req/AppReq.java new file mode 100644 index 0000000..b264ca0 --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/req/AppReq.java @@ -0,0 +1,67 @@ +package top.ysoft.admin.open.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 创建或修改应用参数 + * + * @author chengzi + * @author Charles7c + * @since 2024/10/17 16:03 + */ +@Data +@Schema(description = "创建或修改应用参数") +public class AppReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "应用1") + @NotBlank(message = "名称不能为空") + @Length(max = 100, message = "名称长度不能超过 {max} 个字符") + private String name; + + /** + * 失效时间 + */ + @Schema(description = "失效时间", example = "2023-08-08 23:59:59", type = "string") + @Future(message = "失效时间必须是未来时间") + private LocalDateTime expireTime; + + /** + * 描述 + */ + @Schema(description = "描述", example = "应用1描述信息") + @Length(max = 200, message = "描述长度不能超过 {max} 个字符") + private String description; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * Access Key(访问密钥) + */ + @Schema(hidden = true) + private String accessKey; + + /** + * Secret Key(密钥) + */ + @Schema(hidden = true) + private String secretKey; +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/resp/AppDetailResp.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/resp/AppDetailResp.java new file mode 100644 index 0000000..16e2612 --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/resp/AppDetailResp.java @@ -0,0 +1,63 @@ +package top.ysoft.admin.open.model.resp; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; + +import java.io.Serial; +import java.time.LocalDateTime; + +/** + * 应用详情信息 + * + * @author chengzi + * @author Charles7c + * @since 2024/10/17 16:03 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "应用详情信息") +public class AppDetailResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "应用1") + @ExcelProperty(value = "名称", order = 2) + private String name; + + /** + * Access Key(访问密钥) + */ + @Schema(description = "Access Key(访问密钥)", example = "YjUyMGJjYjIxNTE0NDAxMWE1NmRiY2") + @ExcelProperty(value = "Access Key", order = 3) + private String accessKey; + + /** + * 失效时间 + */ + @Schema(description = "失效时间", example = "2023-08-08 08:08:08", type = "string") + @ExcelProperty(value = "失效时间", order = 4) + private LocalDateTime expireTime; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + @ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class, order = 5) + private DisEnableStatusEnum status; + + /** + * 描述 + */ + @Schema(description = "描述", example = "应用1描述信息") + @ExcelProperty(value = "描述", order = 6) + private String description; +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/resp/AppResp.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/resp/AppResp.java new file mode 100644 index 0000000..78fb51a --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/resp/AppResp.java @@ -0,0 +1,54 @@ +package top.ysoft.admin.open.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.common.model.resp.BaseDetailResp; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; + +import java.io.Serial; +import java.time.LocalDateTime; + +/** + * 应用信息 + * + * @author chengzi + * @author Charles7c + * @since 2024/10/17 16:03 + */ +@Data +@Schema(description = "应用信息") +public class AppResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 名称 + */ + @Schema(description = "名称", example = "应用1") + private String name; + + /** + * Access Key(访问密钥) + */ + @Schema(description = "Access Key(访问密钥)", example = "YjUyMGJjYjIxNTE0NDAxMWE1NmRiY2") + private String accessKey; + + /** + * 失效时间 + */ + @Schema(description = "失效时间", example = "2023-08-08 08:08:08", type = "string") + private LocalDateTime expireTime; + + /** + * 状态 + */ + @Schema(description = "状态", example = "1") + private DisEnableStatusEnum status; + + /** + * 描述 + */ + @Schema(description = "描述", example = "应用1描述信息") + private String description; +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/resp/AppSecretResp.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/resp/AppSecretResp.java new file mode 100644 index 0000000..b6cfde5 --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/model/resp/AppSecretResp.java @@ -0,0 +1,34 @@ +package top.ysoft.admin.open.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 应用密钥信息 + * + * @author chengzi + * @author Charles7c + * @since 2024/10/17 16:03 + */ +@Data +@Schema(description = "应用密钥信息") +public class AppSecretResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * Access Key(访问密钥) + */ + @Schema(description = "Access Key(访问密钥)", example = "YjUyMGJjYjIxNTE0NDAxMWE1NmRiY2") + private String accessKey; + + /** + * Secret Key(私有密钥) + */ + @Schema(description = "Secret Key(私有密钥)", example = "MDI2YzQ3YTU1NGEyNDM1ZWIwNTU5NmNjNmZjM2M2Nzg=") + private String secretKey; +} diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/service/AppService.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/service/AppService.java new file mode 100644 index 0000000..7f4f228 --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/service/AppService.java @@ -0,0 +1,42 @@ +package top.ysoft.admin.open.service; + +import top.ysoft.admin.open.model.entity.AppDO; +import top.ysoft.admin.open.model.query.AppQuery; +import top.ysoft.admin.open.model.req.AppReq; +import top.ysoft.admin.open.model.resp.AppDetailResp; +import top.ysoft.admin.open.model.resp.AppResp; +import top.ysoft.admin.open.model.resp.AppSecretResp; +import top.continew.starter.extension.crud.service.BaseService; + +/** + * 应用业务接口 + * + * @author chengzi + * @author Charles7c + * @since 2024/10/17 16:03 + */ +public interface AppService extends BaseService { + + /** + * 获取密钥 + * + * @param id ID + * @return 密钥信息 + */ + AppSecretResp getSecret(Long id); + + /** + * 重置密钥 + * + * @param id ID + */ + void resetSecret(Long id); + + /** + * 根据 Access Key 查询 + * + * @param accessKey Access Key + * @return 应用信息 + */ + AppDO getByAccessKey(String accessKey); +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/service/impl/AppServiceImpl.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/service/impl/AppServiceImpl.java new file mode 100644 index 0000000..6a41513 --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/service/impl/AppServiceImpl.java @@ -0,0 +1,71 @@ +package top.ysoft.admin.open.service.impl; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.IdUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.open.mapper.AppMapper; +import top.ysoft.admin.open.model.entity.AppDO; +import top.ysoft.admin.open.model.query.AppQuery; +import top.ysoft.admin.open.model.req.AppReq; +import top.ysoft.admin.open.model.resp.AppDetailResp; +import top.ysoft.admin.open.model.resp.AppResp; +import top.ysoft.admin.open.model.resp.AppSecretResp; +import top.ysoft.admin.open.service.AppService; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.extension.crud.service.BaseServiceImpl; + +/** + * 应用业务实现 + * + * @author chengzi + * @author Charles7c + * @since 2024/10/17 16:03 + */ +@Service +@RequiredArgsConstructor +public class AppServiceImpl extends BaseServiceImpl implements AppService { + + @Override + public void beforeAdd(AppReq req) { + req.setAccessKey(Base64.encode(IdUtil.fastSimpleUUID()) + .replace(StringConstants.SLASH, StringConstants.EMPTY) + .replace(StringConstants.PLUS, StringConstants.EMPTY) + .substring(0, 30)); + req.setSecretKey(this.generateSecret()); + } + + @Override + public AppSecretResp getSecret(Long id) { + AppDO app = super.getById(id); + AppSecretResp appSecretResp = new AppSecretResp(); + appSecretResp.setAccessKey(app.getAccessKey()); + appSecretResp.setSecretKey(app.getSecretKey()); + return appSecretResp; + } + + @Override + public void resetSecret(Long id) { + super.getById(id); + AppDO app = new AppDO(); + app.setSecretKey(this.generateSecret()); + baseMapper.update(app, Wrappers.lambdaQuery(AppDO.class).eq(AppDO::getId, id)); + } + + @Override + public AppDO getByAccessKey(String accessKey) { + return baseMapper.selectByAccessKey(accessKey); + } + + /** + * 生成密钥 + * + * @return 密钥 + */ + private String generateSecret() { + return Base64.encode(IdUtil.fastSimpleUUID()) + .replace(StringConstants.SLASH, StringConstants.EMPTY) + .replace(StringConstants.PLUS, StringConstants.EMPTY); + } +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/sign/OpenApiSignTemplate.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/sign/OpenApiSignTemplate.java new file mode 100644 index 0000000..dcf69ab --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/sign/OpenApiSignTemplate.java @@ -0,0 +1,60 @@ +package top.ysoft.admin.open.sign; + +import cn.dev33.satoken.sign.SaSignTemplate; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import top.ysoft.admin.common.enums.DisEnableStatusEnum; +import top.ysoft.admin.open.model.entity.AppDO; +import top.ysoft.admin.open.service.AppService; +import top.continew.starter.core.validation.ValidationUtils; + +import java.util.Map; + +/** + * API 参数签名算法 + * + * @author chengzi + * @author Charles7c + * @since 2024/10/17 16:03 + */ +@Component +@RequiredArgsConstructor +public class OpenApiSignTemplate extends SaSignTemplate { + + private final AppService appService; + public static final String ACCESS_KEY = "accessKey"; + + @Override + public void checkParamMap(Map paramMap) { + // 获取必须的参数 + String timestampValue = paramMap.get(timestamp); + String nonceValue = paramMap.get(nonce); + String signValue = paramMap.get(sign); + String accessKeyValue = paramMap.get(ACCESS_KEY); + + // 校验 + ValidationUtils.throwIfBlank(timestampValue, "timestamp不能为空"); + ValidationUtils.throwIfBlank(nonceValue, "nonce不能为空"); + ValidationUtils.throwIfBlank(signValue, "sign不能为空"); + ValidationUtils.throwIfBlank(accessKeyValue, "accessKey不能为空"); + AppDO app = appService.getByAccessKey(accessKeyValue); + ValidationUtils.throwIfNull(app, "accessKey非法"); + ValidationUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, app.getStatus(), "应用已被禁用, 请联系管理员"); + ValidationUtils.throwIf(app.isExpired(), "应用已过期, 请联系管理员"); + + // 依次校验三个参数 + super.checkTimestamp(Long.parseLong(timestampValue)); + super.checkNonce(nonceValue); + paramMap.put(key, app.getSecretKey()); + super.checkSign(paramMap, signValue); + } + + @Override + public String createSign(Map paramMap) { + ValidationUtils.throwIfEmpty(paramMap.get(key), "秘钥缺失, 请检查应用配置"); + // 移除 sign 参数 + paramMap.remove(sign); + // 计算签名 + return super.abstractStr(super.joinParamsDictSort(paramMap)); + } +} diff --git a/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/util/OpenApiUtils.java b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/util/OpenApiUtils.java new file mode 100644 index 0000000..1b03101 --- /dev/null +++ b/wms-plugin/wms-plugin-open/src/main/java/top/wms/admin/open/util/OpenApiUtils.java @@ -0,0 +1,31 @@ +package top.ysoft.admin.open.util; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.sign.SaSignTemplate; + +import java.util.List; + +/** + * Open Api 工具类 + * + * @author chengzi + * @author Charles7c + * @since 2024/10/25 15:31 + */ +public class OpenApiUtils { + + private OpenApiUtils() { + } + + /** + * 判断请求是否包含sign参数 + * + * @return 是否包含sign参数(true:包含;false:不包含) + */ + public static boolean isSignParamExists() { + SaRequest saRequest = SaHolder.getRequest(); + List paramNames = saRequest.getParamNames(); + return paramNames.stream().anyMatch(SaSignTemplate.sign::equals); + } +} diff --git a/wms-plugin/wms-plugin-schedule/.flattened-pom.xml b/wms-plugin/wms-plugin-schedule/.flattened-pom.xml new file mode 100644 index 0000000..0aefb00 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/.flattened-pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + top.ysoft + ysoft-plugin + 3.6.0-SNAPSHOT + + ysoft-plugin-schedule + 3.6.0-SNAPSHOT + 任务调度插件 + + + GNU LESSER GENERAL PUBLIC LICENSE + http://www.gnu.org/licenses/lgpl.html + + + + + com.aizuda + snail-job-client-starter + + + com.aizuda + snail-job-client-retry-core + + + com.aizuda + snail-job-client-job-core + + + org.springframework + spring-webflux + + + io.projectreactor.netty + reactor-netty-http + + + diff --git a/wms-plugin/wms-plugin-schedule/pom.xml b/wms-plugin/wms-plugin-schedule/pom.xml new file mode 100644 index 0000000..210f292 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + + top.ysoft + ysoft-plugin + ${revision} + + + + ysoft-plugin-schedule + 任务调度插件 + + + + + com.aizuda + snail-job-client-starter + + + com.aizuda + snail-job-client-retry-core + + + com.aizuda + snail-job-client-job-core + + + + + org.springframework + spring-webflux + + + + io.projectreactor.netty + reactor-netty-http + + + diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/api/JobApi.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/api/JobApi.java new file mode 100644 index 0000000..24f6fa7 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/api/JobApi.java @@ -0,0 +1,97 @@ +package top.ysoft.admin.schedule.api; + +import com.aizuda.snailjob.common.core.model.Result; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.service.annotation.*; +import top.ysoft.admin.schedule.model.JobPageResult; +import top.ysoft.admin.schedule.model.req.JobReq; +import top.ysoft.admin.schedule.model.req.JobStatusReq; +import top.ysoft.admin.schedule.model.resp.JobResp; + +import java.util.List; +import java.util.Set; + +/** + * 任务 REST API + * + * @author KAI + * @author Charles7c + * @since 2024/6/25 18:20 + */ +@HttpExchange(accept = MediaType.APPLICATION_JSON_VALUE) +public interface JobApi { + + /** + * 分页查询列表 + * + * @param groupName 任务组 + * @param jobName 任务名称 + * @param jobStatus 任务状态 + * @param page 页码 + * @param size 每页条数 + * @return 响应信息 + */ + @GetExchange("/job/page/list") + ResponseEntity>> page(@RequestParam(value = "groupName", required = false) String groupName, + @RequestParam(value = "jobName", required = false) String jobName, + @RequestParam(value = "jobStatus", required = false) Integer jobStatus, + @RequestParam("page") int page, + @RequestParam("size") int size); + + /** + * 新增 + * + * @param req 新增信息 + * @return 响应信息 + */ + @PostExchange("/job") + ResponseEntity> add(@RequestBody JobReq req); + + /** + * 修改 + * + * @param req 修改信息 + * @return 响应信息 + */ + @PutExchange("/job") + ResponseEntity> update(@RequestBody JobReq req); + + /** + * 修改状态 + * + * @param req 修改信息 + * @return 响应信息 + */ + @PutExchange("/job/status") + ResponseEntity> updateStatus(@RequestBody JobStatusReq req); + + /** + * 删除 + * + * @param ids ID 列表 + * @return 响应信息 + */ + @DeleteExchange("/job/ids") + ResponseEntity> delete(@RequestBody Set ids); + + /** + * 执行 + * + * @param id ID + * @return 响应信息 + */ + @PostExchange("/job/trigger/{id}") + ResponseEntity> trigger(@PathVariable("id") Long id); + + /** + * 查询分组列表 + * + * @return 响应信息 + */ + @GetExchange("/group/all/group-name/list") + ResponseEntity>> listGroup(); +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/api/JobBatchApi.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/api/JobBatchApi.java new file mode 100644 index 0000000..59fcd30 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/api/JobBatchApi.java @@ -0,0 +1,100 @@ +package top.ysoft.admin.schedule.api; + +import com.aizuda.snailjob.common.core.model.Result; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.service.annotation.GetExchange; +import org.springframework.web.service.annotation.HttpExchange; +import org.springframework.web.service.annotation.PostExchange; +import top.ysoft.admin.schedule.model.JobInstanceLogPageResult; +import top.ysoft.admin.schedule.model.JobPageResult; +import top.ysoft.admin.schedule.model.resp.JobInstanceResp; +import top.ysoft.admin.schedule.model.resp.JobLogResp; + +import java.util.List; + +/** + * 任务批次 REST API + * + * @author KAI + * @author Charles7c + * @since 2024/6/27 23:03 + */ +@HttpExchange(value = "/job", accept = MediaType.APPLICATION_JSON_VALUE) +public interface JobBatchApi { + + /** + * 分页查询列表 + * + * @param jobId 任务 ID + * @param jobName 任务名称 + * @param groupName 组名称 + * @param taskBatchStatus 任务批次状态 + * @param datetimeRange 时间范围 + * @param page 页码 + * @param size 每页条数 + * @return 响应信息 + */ + @GetExchange("/batch/list") + ResponseEntity>> page(@RequestParam(value = "jobId", required = false) Long jobId, + @RequestParam(value = "jobName", required = false) String jobName, + @RequestParam(value = "groupName", required = false) String groupName, + @RequestParam(value = "taskBatchStatus", required = false) Integer taskBatchStatus, + @RequestParam(value = "datetimeRange", required = false) String[] datetimeRange, + @RequestParam(value = "page") Integer page, + @RequestParam(value = "size") Integer size); + + /** + * 停止 + * + * @param id ID + * @return 响应信息 + */ + @PostExchange("/batch/stop/{id}") + ResponseEntity> stop(@PathVariable("id") Long id); + + /** + * 重试 + * + * @param id ID + * @return 响应信息 + */ + @PostExchange("/batch/retry/{id}") + ResponseEntity> retry(@PathVariable("id") Long id); + + /** + * 分页查询任务实例列表 + * + * @param jobId 任务 ID + * @param taskBatchId 任务批次 ID + * @param page 页码 + * @param size 每页条数 + * @return 响应信息 + */ + @GetExchange("/task/list") + ResponseEntity>> pageTask(@RequestParam(value = "jobId", required = false) Long jobId, + @RequestParam(value = "taskBatchId") Long taskBatchId, + @RequestParam(value = "page") Integer page, + @RequestParam(value = "size") Integer size); + + /** + * 分页查询任务实例日志列表 + * + * @param jobId 任务 ID + * @param taskBatchId 任务批次 ID + * @param taskId 任务实例ID + * @param startId 起始 ID + * @param fromIndex 起始索引 + * @param size 每页条数 + * @return 响应信息 + */ + @GetExchange("/log/list") + ResponseEntity> pageLog(@RequestParam(value = "jobId", required = false) Long jobId, + @RequestParam(value = "taskBatchId") Long taskBatchId, + @RequestParam(value = "taskId") Long taskId, + @RequestParam(value = "startId") Integer startId, + @RequestParam(value = "fromIndex") Integer fromIndex, + @RequestParam(value = "size") Integer size); +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/api/JobClient.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/api/JobClient.java new file mode 100644 index 0000000..5e31ac8 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/api/JobClient.java @@ -0,0 +1,141 @@ +package top.ysoft.admin.schedule.api; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; +import cn.hutool.jwt.JWTUtil; +import cn.hutool.jwt.RegisteredPayload; +import com.aizuda.snailjob.common.core.model.Result; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import top.ysoft.admin.schedule.constant.JobConstants; +import top.ysoft.admin.schedule.model.JobPageResult; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +/** + * 任务调度客户端 + * + * @author Charles7c + * @since 2024/7/4 23:07 + */ +@Slf4j +@Data +public class JobClient { + + public static final Integer STATUS_SUCCESS = 1; + private static final String AUTH_URL = "/auth/login"; + private final String url; + private final String username; + private final String password; + + public JobClient(String url, String username, String password) { + Assert.notBlank(url, "任务调度中心 URL 不能为空"); + Assert.notBlank(username, "任务调度中心用户名不能为空"); + Assert.notBlank(password, "任务调度中心密码不能为空"); + this.url = url; + this.username = username; + this.password = password; + } + + /** + * 请求 + * + * @param apiSupplier API 请求 + * @param 响应类型 + * @return 响应信息 + */ + public T request(Supplier>> apiSupplier) { + ResponseEntity> responseEntity = apiSupplier.get(); + this.checkResponse(responseEntity); + Result result = responseEntity.getBody(); + if (!STATUS_SUCCESS.equals(result.getStatus())) { + throw new IllegalStateException(result.getMessage()); + } + return result.getData(); + } + + /** + * 分页请求 + * + * @param apiSupplier API 请求 + * @param 响应类型 + * @return 分页列表信息 + */ + public PageResp requestPage(Supplier>>> apiSupplier) { + ResponseEntity>> responseEntity = apiSupplier.get(); + this.checkResponse(responseEntity); + JobPageResult> result = responseEntity.getBody(); + if (!STATUS_SUCCESS.equals(result.getStatus())) { + throw new IllegalStateException(result.getMessage()); + } + PageResp page = new PageResp<>(); + page.setList(result.getData()); + page.setTotal(result.getTotal()); + return page; + } + + /** + * 获取 Token + * + * @return Token + */ + public String getToken() { + String token = RedisUtils.get(JobConstants.AUTH_TOKEN_HEADER); + if (StrUtil.isBlank(token)) { + token = this.authenticate(); + Object expiresAtSeconds = JWTUtil.parseToken(token).getPayload(RegisteredPayload.EXPIRES_AT); + RedisUtils.set(JobConstants.AUTH_TOKEN_HEADER, token, Duration.ofSeconds(Convert + .toLong(expiresAtSeconds) - DateUtil.currentSeconds() - 60)); + } + return token; + } + + /** + * 密码认证 + * + * @return Token + */ + private String authenticate() { + Map paramMap = MapUtil.newHashMap(2); + paramMap.put("username", username); + paramMap.put("password", SecureUtil.md5(password)); + HttpRequest httpRequest = HttpUtil.createPost("%s%s".formatted(url, AUTH_URL)); + httpRequest.body(JSONUtil.toJsonStr(paramMap)); + HttpResponse response = httpRequest.execute(); + if (!response.isOk() || response.body() == null) { + throw new IllegalStateException("连接任务调度中心异常"); + } + Result result = JSONUtil.toBean(response.body(), Result.class); + if (!STATUS_SUCCESS.equals(result.getStatus())) { + log.warn("Password Authentication failed, expected a successful response. error msg: {}", result + .getMessage()); + throw new IllegalStateException(result.getMessage()); + } + return JSONUtil.parseObj(result.getData()).getStr("token"); + } + + /** + * 检查响应 + * + * @param responseEntity 响应信息 + */ + private void checkResponse(ResponseEntity responseEntity) { + if (!responseEntity.getStatusCode().is2xxSuccessful() || responseEntity.getBody() == null) { + throw new IllegalStateException("连接任务调度中心异常"); + } + } +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/config/HttpExchangeConfiguration.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/config/HttpExchangeConfiguration.java new file mode 100644 index 0000000..d16be81 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/config/HttpExchangeConfiguration.java @@ -0,0 +1,118 @@ +package top.ysoft.admin.schedule.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.channel.ChannelOption; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.http.codec.json.Jackson2JsonDecoder; +import org.springframework.http.codec.json.Jackson2JsonEncoder; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.support.WebClientAdapter; +import org.springframework.web.service.invoker.HttpServiceProxyFactory; +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClient; +import top.ysoft.admin.schedule.api.JobApi; +import top.ysoft.admin.schedule.api.JobBatchApi; +import top.ysoft.admin.schedule.api.JobClient; +import top.ysoft.admin.schedule.constant.JobConstants; + +/** + * HTTP Exchange 配置 + * + * @author KAI + * @author Charles7c + * @since 2024/6/25 18:03 + */ +@Slf4j +@Configuration +@RequiredArgsConstructor +public class HttpExchangeConfiguration { + + private final ObjectMapper objectMapper; + @Value("${snail-job.namespace}") + private String namespace; + + @Value("${snail-job.server.api.url}") + private String baseUrl; + + @Value("${snail-job.server.api.username}") + private String username; + + @Value("${snail-job.server.api.password}") + private String password; + + @Bean + public JobApi jobApi() { + return httpServiceProxyFactory().createClient(JobApi.class); + } + + @Bean + public JobBatchApi jobBatchApi() { + return httpServiceProxyFactory().createClient(JobBatchApi.class); + } + + @Bean + public HttpServiceProxyFactory httpServiceProxyFactory() { + HttpClient httpClient = HttpClient.create() + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000) + .doOnConnected(conn -> { + conn.addHandlerLast(new ReadTimeoutHandler(10)); + conn.addHandlerLast(new WriteTimeoutHandler(10)); + }) + .wiretap(true); + + WebClient webClient = WebClient.builder() + .codecs(config -> config.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper))) + .codecs(config -> config.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper))) + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .filter(logRequest()) + .filter(logResponse()) + .filter((request, next) -> { + // 设置请求头 + ClientRequest filtered = ClientRequest.from(request) + .header(JobConstants.NAMESPACE_ID_HEADER, namespace) + .header(JobConstants.AUTH_TOKEN_HEADER, jobClient().getToken()) + .build(); + return next.exchange(filtered); + }) + .baseUrl(baseUrl) + .build(); + return HttpServiceProxyFactory.builderFor(WebClientAdapter.create(webClient)).build(); + } + + @Bean + public JobClient jobClient() { + return new JobClient(baseUrl, username, password); + } + + /** + * 打印请求日志 + */ + private ExchangeFilterFunction logRequest() { + return ExchangeFilterFunction.ofRequestProcessor(request -> { + log.info("---> {} {}", request.method(), request.url()); + return Mono.just(request); + }); + } + + /** + * 打印响应日志 + */ + private ExchangeFilterFunction logResponse() { + return ExchangeFilterFunction.ofResponseProcessor(response -> response.bodyToMono(String.class) + .flatMap(body -> { + log.info("<--- {}", response.statusCode()); + log.info(body); + return Mono.just(ClientResponse.from(response).body(body).build()); + })); + } +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/config/SnailJobConfiguration.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/config/SnailJobConfiguration.java new file mode 100644 index 0000000..85173af --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/config/SnailJobConfiguration.java @@ -0,0 +1,36 @@ +package top.ysoft.admin.schedule.config; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import com.aizuda.snailjob.client.common.appender.SnailLogbackAppender; +import com.aizuda.snailjob.client.common.event.SnailClientStartingEvent; +import com.aizuda.snailjob.client.starter.EnableSnailJob; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; + +/** + * Snail Job 配置 + * + * @author KAI + * @since 2024/6/26 9:19 + */ +@Configuration +@EnableSnailJob +@ConditionalOnProperty(prefix = "snail-job", value = "enabled", havingValue = "true", matchIfMissing = true) +public class SnailJobConfiguration { + + /** + * 日志上报 + */ + @EventListener(SnailClientStartingEvent.class) + public void onStarting() { + SnailLogbackAppender appender = new SnailLogbackAppender<>(); + appender.start(); + LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory(); + Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + rootLogger.addAppender(appender); + } +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/constant/JobConstants.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/constant/JobConstants.java new file mode 100644 index 0000000..c69bddd --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/constant/JobConstants.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.schedule.constant; + +/** + * 任务调度常量 + * + * @author KAI + * @since 2024/6/26 9:19 + */ +public class JobConstants { + + /** + * 请求头:命名空间 ID + */ + public static final String NAMESPACE_ID_HEADER = "SNAIL-JOB-NAMESPACE-ID"; + + /** + * 请求头:认证令牌 + */ + public static final String AUTH_TOKEN_HEADER = "Snail-Job-Auth"; + + private JobConstants() { + } +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobBlockStrategyEnum.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobBlockStrategyEnum.java new file mode 100644 index 0000000..bcbebf4 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobBlockStrategyEnum.java @@ -0,0 +1,34 @@ +package top.ysoft.admin.schedule.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 任务阻塞策略枚举 + * + * @author Charles7c + * @since 2024/7/11 22:28 + */ +@Getter +@RequiredArgsConstructor +public enum JobBlockStrategyEnum implements BaseEnum { + + /** + * 丢弃 + */ + DISCARD(1, "丢弃"), + + /** + * 覆盖 + */ + COVER(2, "覆盖"), + + /** + * 并行 + */ + PARALLEL(3, "并行"),; + + private final Integer value; + private final String description; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobExecuteReasonEnum.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobExecuteReasonEnum.java new file mode 100644 index 0000000..3652764 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobExecuteReasonEnum.java @@ -0,0 +1,119 @@ +package top.ysoft.admin.schedule.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 任务执行原因枚举 + * + * @author Charles7c + * @since 2024/7/11 22:28 + */ +@Getter +@RequiredArgsConstructor +public enum JobExecuteReasonEnum implements BaseEnum { + + /** + * 无 + */ + NONE(0, "无"), + + /** + * 任务执行超时 + */ + TIME_OUT(1, "任务执行超时"), + + /** + * 无客户端节点 + */ + CLIENT_NOT_FOUND(2, "无客户端节点"), + + /** + * 任务已关闭 + */ + TASK_CLOSED(3, "任务已关闭"), + + /** + * 任务丢弃 + */ + TASK_DROPPED(4, "任务丢弃"), + + /** + * 任务被覆盖 + */ + TASK_COVERED(5, "任务被覆盖"), + + /** + * 无可执行任务项 + */ + TASK_NONE(6, "无可执行任务项"), + + /** + * 任务执行期间发生非预期异常 + */ + TASK_EXCEPTION(7, "任务执行期间发生非预期异常"), + + /** + * 手动停止 + */ + MANUAL_STOP(8, "手动停止"), + + /** + * 条件节点执行异常 + */ + NODE_EXCEPTION(9, "条件节点执行异常"), + + /** + * 任务中断 + */ + TASK_INTERRUPT(10, "任务中断"), + + /** + * 回调节点执行异常 + */ + CALLBACK_EXCEPTION(11, "回调节点执行异常"), + + /** + * 无需处理 + */ + NO_NEED_PROCESS(12, "无需处理"), + + /** + * 节点关闭跳过执行 + */ + NODE_SKIP(13, "节点关闭跳过执行"), + + /** + * 判定未通过 + */ + NOT_PASS(14, "判定未通过"), + + /** + * 任务已完成 + */ + TASK_FINISHED(15, "任务已完成"), + + /** + * 任务状态 + */ + TASK_RUNNING(16, "任务正在执行"), + + /** + * 任务等待执行 + */ + TASK_WAITING(17, "任务等待执行"), + + /** + * 任务执行失败 + */ + TASK_FAILED(18, "任务执行失败"), + + /** + * 任务执行成功 + */ + TASK_SUCCESS(19, "任务执行成功"),; + + private final Integer value; + private final String description; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobExecuteStatusEnum.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobExecuteStatusEnum.java new file mode 100644 index 0000000..3fba3ba --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobExecuteStatusEnum.java @@ -0,0 +1,51 @@ +package top.ysoft.admin.schedule.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.ysoft.admin.common.constant.UiConstants; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 任务执行状态枚举 + * + * @author Charles7c + * @since 2024/7/11 22:28 + */ +@Getter +@RequiredArgsConstructor +public enum JobExecuteStatusEnum implements BaseEnum { + + /** + * 待处理 + */ + WAITING(1, "待处理", UiConstants.COLOR_PRIMARY), + + /** + * 运行中 + */ + RUNNING(2, "运行中", UiConstants.COLOR_WARNING), + + /** + * 成功 + */ + SUCCEEDED(3, "成功", UiConstants.COLOR_SUCCESS), + + /** + * 已失败 + */ + FAILED(4, "已失败", UiConstants.COLOR_ERROR), + + /** + * 已停止 + */ + STOPPED(5, "已停止", UiConstants.COLOR_ERROR), + + /** + * 已取消 + */ + CANCELED(6, "已取消", UiConstants.COLOR_DEFAULT),; + + private final Integer value; + private final String description; + private final String color; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobRouteStrategyEnum.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobRouteStrategyEnum.java new file mode 100644 index 0000000..c45bca5 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobRouteStrategyEnum.java @@ -0,0 +1,39 @@ +package top.ysoft.admin.schedule.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 任务路由策略枚举 + * + * @author Charles7c + * @since 2024/7/11 22:28 + */ +@Getter +@RequiredArgsConstructor +public enum JobRouteStrategyEnum implements BaseEnum { + + /** + * 轮询 + */ + POLLING(4, "轮询"), + + /** + * 随机 + */ + RANDOM(2, "随机"), + + /** + * 一致性哈希 + */ + HASH(1, "一致性哈希"), + + /** + * LRU + */ + LRU(3, "LRU"),; + + private final Integer value; + private final String description; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobStatusEnum.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobStatusEnum.java new file mode 100644 index 0000000..2ed935f --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobStatusEnum.java @@ -0,0 +1,31 @@ +package top.ysoft.admin.schedule.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.ysoft.admin.common.constant.UiConstants; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 任务状态枚举 + * + * @author Charles7c + * @since 2024/7/11 22:28 + */ +@Getter +@RequiredArgsConstructor +public enum JobStatusEnum implements BaseEnum { + + /** + * 禁用 + */ + DISABLED(0, "禁用", UiConstants.COLOR_ERROR), + + /** + * 启用 + */ + ENABLED(1, "启用", UiConstants.COLOR_SUCCESS),; + + private final Integer value; + private final String description; + private final String color; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobTaskTypeEnum.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobTaskTypeEnum.java new file mode 100644 index 0000000..a4d0d1d --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobTaskTypeEnum.java @@ -0,0 +1,36 @@ +package top.ysoft.admin.schedule.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.ysoft.admin.common.constant.UiConstants; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 任务类型枚举 + * + * @author Charles7c + * @since 2024/7/11 22:28 + */ +@Getter +@RequiredArgsConstructor +public enum JobTaskTypeEnum implements BaseEnum { + + /** + * 集群 + */ + CLUSTER(1, "集群", UiConstants.COLOR_PRIMARY), + + /** + * 广播 + */ + BROADCAST(2, "广播", UiConstants.COLOR_PRIMARY), + + /** + * 静态切片 + */ + SLICE(3, "静态切片", UiConstants.COLOR_PRIMARY),; + + private final Integer value; + private final String description; + private final String color; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobTriggerTypeEnum.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobTriggerTypeEnum.java new file mode 100644 index 0000000..f6599d7 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/enums/JobTriggerTypeEnum.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.schedule.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 任务触发类型枚举 + * + * @author Charles7c + * @since 2024/7/11 22:28 + */ +@Getter +@RequiredArgsConstructor +public enum JobTriggerTypeEnum implements BaseEnum { + + /** + * 固定时间 + */ + FIXED_TIME(2, "固定时间"), + + /** + * CRON + */ + CRON(3, "CRON"); + + private final Integer value; + private final String description; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/JobInstanceLogPageResult.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/JobInstanceLogPageResult.java new file mode 100644 index 0000000..5f47b37 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/JobInstanceLogPageResult.java @@ -0,0 +1,58 @@ +package top.ysoft.admin.schedule.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 任务实例日志分页信息 + * + * @author Charles7c + * @since 2024/7/14 21:51 + */ +@Data +@Schema(description = "任务实例日志分页信息") +public class JobInstanceLogPageResult implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 日志详情 + */ + @Schema(description = "日志详情") + private List message; + + /** + * 异常信息 + */ + @Schema(description = "异常信息") + private String throwable; + + /** + * 是否结束 + */ + @Schema(description = "是否结束", example = "true") + private boolean isFinished; + + /** + * 起始索引 + */ + @Schema(description = "起始索引", example = "0") + private Integer fromIndex; + + /** + * 下一个开始 ID + */ + @Schema(description = "下一个开始ID", example = "9") + private Long nextStartId; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/JobPageResult.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/JobPageResult.java new file mode 100644 index 0000000..b2f1bb2 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/JobPageResult.java @@ -0,0 +1,30 @@ +package top.ysoft.admin.schedule.model; + +import com.aizuda.snailjob.common.core.model.Result; +import lombok.Data; + +/** + * 任务调度服务端分页返回对象 + * + * @author KAI + * @author Charles7c + * @since 2024/6/26 22:27 + */ +@Data +public class JobPageResult extends Result { + + /** + * 页码 + */ + private long page; + + /** + * 每页条数 + */ + private long size; + + /** + * 总条数 + */ + private long total; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobInstanceLogQuery.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobInstanceLogQuery.java new file mode 100644 index 0000000..b6516c2 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobInstanceLogQuery.java @@ -0,0 +1,61 @@ +package top.ysoft.admin.schedule.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import lombok.Data; +import org.hibernate.validator.constraints.Range; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 任务实例日志查询条件 + * + * @author KAI + * @since 2024/6/28 16:58 + */ +@Data +@Schema(description = "任务实例日志查询条件") +public class JobInstanceLogQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务 ID + */ + @Schema(description = "任务ID", example = "1") + private Long jobId; + + /** + * 任务批次 ID + */ + @Schema(description = "任务批次ID", example = "1") + private Long taskBatchId; + + /** + * 任务实例 ID + */ + @Schema(description = "任务实例ID", example = "1") + private Long taskId; + + /** + * 开始 ID + */ + @Schema(description = "开始ID", example = "2850") + private Integer startId; + + /** + * 起始索引 + */ + @Schema(description = "起始索引", example = "0") + @Min(value = 0, message = "起始索引最小值为 {value}") + private Integer fromIndex = 0; + + /** + * 每页条数 + */ + @Schema(description = "每页条数", example = "50") + @Range(min = 1, max = 1000, message = "每页条数(取值范围 {min}-{max})") + private Integer size = 50; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobInstanceQuery.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobInstanceQuery.java new file mode 100644 index 0000000..62aaa85 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobInstanceQuery.java @@ -0,0 +1,33 @@ +package top.ysoft.admin.schedule.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 任务实例查询条件 + * + * @author KAI + * @since 2024/6/28 16:58 + */ +@Data +@Schema(description = "任务实例查询条件") +public class JobInstanceQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务 ID + */ + @Schema(description = "任务ID", example = "1") + private Long jobId; + + /** + * 任务批次 ID + */ + @Schema(description = "任务批次ID", example = "1") + private Long taskBatchId; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobLogQuery.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobLogQuery.java new file mode 100644 index 0000000..2ab8b13 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobLogQuery.java @@ -0,0 +1,74 @@ +package top.ysoft.admin.schedule.model.query; + +import cn.hutool.core.date.DatePattern; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.hibernate.validator.constraints.Range; +import org.springframework.format.annotation.DateTimeFormat; +import top.ysoft.admin.schedule.enums.JobExecuteStatusEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 任务日志查询条件 + * + * @author KAI + * @since 2024/6/27 23:58 + */ +@Data +@Schema(description = "任务日志查询条件") +public class JobLogQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务 ID + */ + @Schema(description = "任务ID", example = "1") + private Long jobId; + + /** + * 任务组 + */ + @Schema(description = "任务组", example = "ysoft-admin") + private String groupName; + + /** + * 任务名称 + */ + @Schema(description = "任务名称", example = "定时任务1") + private String jobName; + + /** + * 任务批次状态 + */ + @Schema(description = "任务批次状态", example = "1") + private JobExecuteStatusEnum taskBatchStatus; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 00:00:00,2023-08-08 23:59:59") + @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + @Size(max = 2, message = "创建时间必须是一个范围") + private LocalDateTime[] datetimeRange; + + /** + * 页码 + */ + @Schema(description = "页码", example = "1") + @Min(value = 1, message = "页码最小值为 {value}") + private Integer page = 1; + + /** + * 每页条数 + */ + @Schema(description = "每页条数", example = "10") + @Range(min = 1, max = 1000, message = "每页条数(取值范围 {min}-{max})") + private Integer size = 10; +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobQuery.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobQuery.java new file mode 100644 index 0000000..c2a8f7a --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/query/JobQuery.java @@ -0,0 +1,56 @@ +package top.ysoft.admin.schedule.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import lombok.Data; +import org.hibernate.validator.constraints.Range; +import top.ysoft.admin.schedule.enums.JobStatusEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 任务查询条件 + * + * @author KAI + * @since 2024/6/25 16:43 + */ +@Data +@Schema(description = "任务查询条件") +public class JobQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务组 + */ + @Schema(description = "任务组", example = "ysoft-admin") + private String groupName; + + /** + * 任务名称 + */ + @Schema(description = "任务名称", example = "定时任务1") + private String jobName; + + /** + * 任务状态 + */ + @Schema(description = "任务状态", example = "1") + private JobStatusEnum jobStatus; + + /** + * 页码 + */ + @Schema(description = "页码", example = "1") + @Min(value = 1, message = "页码最小值为 {value}") + private Integer page = 1; + + /** + * 每页条数 + */ + @Schema(description = "每页条数", example = "10") + @Range(min = 1, max = 1000, message = "每页条数(取值范围 {min}-{max})") + private Integer size = 10; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/req/JobReq.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/req/JobReq.java new file mode 100644 index 0000000..402caf5 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/req/JobReq.java @@ -0,0 +1,147 @@ +package top.ysoft.admin.schedule.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.ysoft.admin.schedule.enums.*; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 创建或修改任务参数 + * + * @author KAI + * @author Charles7c + * @since 2024/6/25 16:40 + */ +@Data +@Schema(description = "创建或修改任务参数") +public class JobReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务组 + */ + @Schema(description = "任务组", example = "ysoft-admin") + @NotBlank(message = "任务组不能为空") + private String groupName; + + /** + * 任务名称 + */ + @Schema(description = "任务名称", example = "定时任务1") + @NotBlank(message = "任务名称不能为空") + @Length(max = 64, message = "任务名称不能超过 {max} 个字符") + private String jobName; + + /** + * 描述 + */ + @Schema(description = "描述", example = "定时任务1的描述") + private String description; + + /** + * 触发类型 + */ + @Schema(description = "触发类型", example = "2") + @NotNull(message = "触发类型非法") + private JobTriggerTypeEnum triggerType; + + /** + * 间隔时长 + */ + @Schema(description = "间隔时长", example = "60") + @NotBlank(message = "间隔时长不能为空") + private String triggerInterval; + + /** + * 执行器类型 + */ + @Schema(description = "执行器类型", example = "1", defaultValue = "1") + private Integer executorType = 1; + + /** + * 任务类型 + */ + @Schema(description = "任务类型", example = "1") + @NotNull(message = "任务类型非法") + private JobTaskTypeEnum taskType; + + /** + * 执行器名称 + */ + @Schema(description = "执行器名称", example = "test") + @NotBlank(message = "执行器名称不能为空") + private String executorInfo; + + /** + * 任务参数 + */ + @Schema(description = "任务参数", example = "") + private String argsStr; + + /** + * 参数类型 + */ + @Schema(description = "参数类型", example = "1") + private Integer argsType; + + /** + * 路由策略 + */ + @Schema(description = "路由策略", example = "4") + @NotNull(message = "路由策略非法") + private JobRouteStrategyEnum routeKey; + + /** + * 阻塞策略 + */ + @Schema(description = "阻塞策略", example = "1") + @NotNull(message = "阻塞策略非法") + private JobBlockStrategyEnum blockStrategy; + + /** + * 超时时间(单位:秒) + */ + @Schema(description = "超时时间(单位:秒)", example = "60") + @NotNull(message = "超时时间不能为空") + private Integer executorTimeout; + + /** + * 最大重试次数 + */ + @Schema(description = "最大重试次数", example = "3") + @NotNull(message = "最大重试次数不能为空") + private Integer maxRetryTimes; + + /** + * 重试间隔(单位:秒) + */ + @Schema(description = "重试间隔(单位:秒)", example = "1") + @NotNull(message = "重试间隔不能为空") + private Integer retryInterval; + + /** + * 并行数 + */ + @Schema(description = "并行数", example = "1") + @NotNull(message = "并行数不能为空") + private Integer parallelNum; + + /** + * 任务状态 + */ + @Schema(description = "任务状态", example = "0", defaultValue = "0") + private JobStatusEnum jobStatus = JobStatusEnum.DISABLED; + + /** + * ID + */ + @Schema(hidden = true) + private Long id; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/req/JobStatusReq.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/req/JobStatusReq.java new file mode 100644 index 0000000..297c0bb --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/req/JobStatusReq.java @@ -0,0 +1,37 @@ +package top.ysoft.admin.schedule.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import top.ysoft.admin.schedule.enums.JobStatusEnum; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 修改任务状态信息 + * + * @author KAI + * @author Charles7c + * @since 2024/6/27 9:24 + */ +@Data +@Schema(description = "修改任务状态信息") +public class JobStatusReq implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务状态 + */ + @Schema(description = "任务状态", example = "1") + @NotNull(message = "任务状态非法") + private JobStatusEnum jobStatus; + + /** + * ID + */ + @Schema(hidden = true) + private Long id; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/resp/JobInstanceResp.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/resp/JobInstanceResp.java new file mode 100644 index 0000000..6ad9438 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/resp/JobInstanceResp.java @@ -0,0 +1,70 @@ +package top.ysoft.admin.schedule.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 任务实例信息 + * + * @author KAI + * @author Charles7c + * @since 2024/6/28 16:58 + */ +@Data +@Schema(description = "任务实例信息") +public class JobInstanceResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 任务组 + */ + @Schema(description = "任务组", example = "ysoft-admin") + private String groupName; + + /** + * 任务 ID + */ + @Schema(description = "任务ID", example = "1") + private Long jobId; + + /** + * 任务批次 ID + */ + @Schema(description = "任务批次ID", example = "1") + private Long taskBatchId; + + /** + * 执行状态 + */ + @Schema(description = "执行状态", example = "1") + private Integer taskStatus; + + /** + * 重试次数 + */ + @Schema(description = "重试次数", example = "1") + private Integer retryCount; + + /** + * 执行结果 + */ + @Schema(description = "执行结果", example = "") + private String resultMessage; + + /** + * 客户端信息 + */ + @Schema(description = "客户端信息", example = "1812406095098114048@192.168.138.48:1789") + private String clientInfo; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/resp/JobLogResp.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/resp/JobLogResp.java new file mode 100644 index 0000000..17ea5f6 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/resp/JobLogResp.java @@ -0,0 +1,85 @@ +package top.ysoft.admin.schedule.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.schedule.enums.JobExecuteReasonEnum; +import top.ysoft.admin.schedule.enums.JobExecuteStatusEnum; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 任务日志信息 + * + * @author KAI + * @author Charles7c + * @since 2024/6/27 22:50 + */ +@Data +@Schema(description = "任务日志信息") +public class JobLogResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 任务组 + */ + @Schema(description = "任务组", example = "ysoft-admin") + private String groupName; + + /** + * 任务名称 + */ + @Schema(description = "任务名称", example = "定时任务1") + private String jobName; + + /** + * 任务 ID + */ + @Schema(description = "任务ID", example = "1") + private Long jobId; + + /** + * 任务状态 + */ + @Schema(description = "任务状态", example = "3") + private JobExecuteStatusEnum taskBatchStatus; + + /** + * 操作原因 + */ + @Schema(description = "操作原因", example = "0") + private JobExecuteReasonEnum operationReason; + + /** + * 执行器类型 + */ + @Schema(description = "执行器类型", example = "1") + private Integer executorType; + + /** + * 执行器名称 + */ + @Schema(description = "执行器名称", example = "test") + private String executorInfo; + + /** + * 执行时间 + */ + @Schema(description = "执行时间", example = "2023-08-08 08:08:08", type = "string") + private LocalDateTime executionAt; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") + private LocalDateTime createDt; +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/resp/JobResp.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/resp/JobResp.java new file mode 100644 index 0000000..0bc7972 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/model/resp/JobResp.java @@ -0,0 +1,150 @@ +package top.ysoft.admin.schedule.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.ysoft.admin.schedule.enums.*; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 任务信息 + * + * @author KAI + * @author Charles7c + * @since 2024/6/25 17:15 + */ +@Data +@Schema(description = "任务信息") +public class JobResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID", example = "1") + private Long id; + + /** + * 任务组 + */ + @Schema(description = "任务组", example = "ysoft-admin") + private String groupName; + + /** + * 任务名称 + */ + @Schema(description = "任务名称", example = "定时任务1") + private String jobName; + + /** + * 描述 + */ + @Schema(description = "描述", example = "定时任务1的描述") + private String description; + + /** + * 触发类型 + */ + @Schema(description = "触发类型", example = "2") + private JobTriggerTypeEnum triggerType; + + /** + * 间隔时长 + */ + @Schema(description = "间隔时长", example = "60") + private String triggerInterval; + + /** + * 执行器类型 + */ + @Schema(description = " 执行器类型", example = "1") + private Integer executorType; + + /** + * 任务类型 + */ + @Schema(description = "任务类型", example = "1") + private JobTaskTypeEnum taskType; + + /** + * 执行器名称 + */ + @Schema(description = "执行器名称", example = "test") + private String executorInfo; + + /** + * 任务参数 + */ + @Schema(description = "任务参数", example = "") + private String argsStr; + + /** + * 参数类型 + */ + @Schema(description = "参数类型", example = "1") + private String argsType; + + /** + * 路由策略 + */ + @Schema(description = "路由策略", example = "1") + private JobRouteStrategyEnum routeKey; + + /** + * 阻塞策略 + */ + @Schema(description = "阻塞策略", example = "1") + private JobBlockStrategyEnum blockStrategy; + + /** + * 超时时间(单位:秒) + */ + @Schema(description = "超时时间(单位:秒)", example = "60") + private Integer executorTimeout; + + /** + * 最大重试次数 + */ + @Schema(description = "最大重试次数", example = "3") + private Integer maxRetryTimes; + + /** + * 重试间隔(单位:秒) + */ + @Schema(description = "重试间隔", example = "1") + private Integer retryInterval; + + /** + * 并行数 + */ + @Schema(description = "并行数", example = "1") + private Integer parallelNum; + + /** + * 任务状态 + */ + @Schema(description = "任务状态", example = "1") + private JobStatusEnum jobStatus; + + /** + * 下次触发时间 + */ + @Schema(description = "下次触发时间", example = "2023-08-08 08:09:00", type = "string") + private LocalDateTime nextTriggerAt; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2023-08-08 08:08:00", type = "string") + private LocalDateTime createDt; + + /** + * 修改时间 + */ + @Schema(description = "修改时间", example = "2023-08-08 08:08:00", type = "string") + private LocalDateTime updateDt; +} \ No newline at end of file diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/JobLogService.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/JobLogService.java new file mode 100644 index 0000000..e068762 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/JobLogService.java @@ -0,0 +1,61 @@ +package top.ysoft.admin.schedule.service; + +import top.ysoft.admin.schedule.model.JobInstanceLogPageResult; +import top.ysoft.admin.schedule.model.query.JobInstanceLogQuery; +import top.ysoft.admin.schedule.model.query.JobInstanceQuery; +import top.ysoft.admin.schedule.model.query.JobLogQuery; +import top.ysoft.admin.schedule.model.resp.JobInstanceResp; +import top.ysoft.admin.schedule.model.resp.JobLogResp; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.util.List; + +/** + * 任务日志业务接口 + * + * @author KAI + * @author Charles7c + * @since 2024/6/27 22:52 + */ +public interface JobLogService { + + /** + * 分页查询列表 + * + * @param query 查询条件 + * @return 分页列表信息 + */ + PageResp page(JobLogQuery query); + + /** + * 停止 + * + * @param id ID + * @return 停止结果 + */ + boolean stop(Long id); + + /** + * 重试 + * + * @param id ID + * @return 重试结果 + */ + boolean retry(Long id); + + /** + * 查询任务实例列表 + * + * @param query 查询条件 + * @return 列表信息 + */ + List listInstance(JobInstanceQuery query); + + /** + * 分页查询任务实例日志列表 + * + * @param query 查询条件 + * @return 分页列表信息 + */ + JobInstanceLogPageResult pageInstanceLog(JobInstanceLogQuery query); +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/JobService.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/JobService.java new file mode 100644 index 0000000..03caab5 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/JobService.java @@ -0,0 +1,76 @@ +package top.ysoft.admin.schedule.service; + +import top.ysoft.admin.schedule.model.query.JobQuery; +import top.ysoft.admin.schedule.model.req.JobReq; +import top.ysoft.admin.schedule.model.req.JobStatusReq; +import top.ysoft.admin.schedule.model.resp.JobResp; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.util.List; + +/** + * 任务业务接口 + * + * @author KAI + * @author Charles7c + * @since 2024/6/25 17:20 + */ +public interface JobService { + + /** + * 分页查询列表 + * + * @param query 查询条件 + * @return 分页列表信息 + */ + PageResp page(JobQuery query); + + /** + * 新增 + * + * @param req 创建信息 + * @return 新增结果 + */ + boolean add(JobReq req); + + /** + * 修改 + * + * @param req 修改信息 + * @param id ID + * @return 修改结果 + */ + boolean update(JobReq req, Long id); + + /** + * 修改状态 + * + * @param req 修改状态信息 + * @param id ID + * @return 修改状态结果 + */ + boolean updateStatus(JobStatusReq req, Long id); + + /** + * 删除 + * + * @param id ID + * @return 删除结果 + */ + boolean delete(Long id); + + /** + * 执行 + * + * @param id ID + * @return 执行结果 + */ + boolean trigger(Long id); + + /** + * 查询分组列表 + * + * @return 分组列表 + */ + List listGroup(); +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/impl/JobLogServiceImpl.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/impl/JobLogServiceImpl.java new file mode 100644 index 0000000..0c7e428 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/impl/JobLogServiceImpl.java @@ -0,0 +1,67 @@ +package top.ysoft.admin.schedule.service.impl; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.schedule.api.JobBatchApi; +import top.ysoft.admin.schedule.api.JobClient; +import top.ysoft.admin.schedule.model.JobInstanceLogPageResult; +import top.ysoft.admin.schedule.model.query.JobInstanceLogQuery; +import top.ysoft.admin.schedule.model.query.JobInstanceQuery; +import top.ysoft.admin.schedule.model.query.JobLogQuery; +import top.ysoft.admin.schedule.model.resp.JobInstanceResp; +import top.ysoft.admin.schedule.model.resp.JobLogResp; +import top.ysoft.admin.schedule.service.JobLogService; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +/** + * 任务日志业务实现 + * + * @author KAI + * @author Charles7c + * @since 2024/6/27 22:54 + */ +@Service +@RequiredArgsConstructor +public class JobLogServiceImpl implements JobLogService { + + private final JobClient jobClient; + private final JobBatchApi jobBatchApi; + + @Override + public PageResp page(JobLogQuery query) { + LocalDateTime[] datetimeRange = query.getDatetimeRange(); + return jobClient.requestPage(() -> jobBatchApi.page(query.getJobId(), query.getJobName(), query + .getGroupName(), query.getTaskBatchStatus() != null + ? query.getTaskBatchStatus().getValue() + : null, new String[] {DateUtil.format(datetimeRange[0], DatePattern.UTC_SIMPLE_PATTERN), DateUtil + .format(datetimeRange[1], DatePattern.UTC_SIMPLE_PATTERN)}, query.getPage(), query.getSize())); + } + + @Override + public boolean stop(Long id) { + return Boolean.TRUE.equals(jobClient.request(() -> jobBatchApi.stop(id))); + } + + @Override + public boolean retry(Long id) { + return Boolean.TRUE.equals(jobClient.request(() -> jobBatchApi.retry(id))); + } + + @Override + public List listInstance(JobInstanceQuery query) { + return jobClient.requestPage(() -> jobBatchApi.pageTask(query.getJobId(), query.getTaskBatchId(), 1, 100)) + .getList(); + } + + @Override + public JobInstanceLogPageResult pageInstanceLog(JobInstanceLogQuery query) { + return Objects.requireNonNull(jobBatchApi.pageLog(query.getJobId(), query.getTaskBatchId(), query + .getTaskId(), query.getStartId(), query.getFromIndex(), query.getSize()).getBody()).getData(); + } +} diff --git a/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/impl/JobServiceImpl.java b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/impl/JobServiceImpl.java new file mode 100644 index 0000000..ab8c9c4 --- /dev/null +++ b/wms-plugin/wms-plugin-schedule/src/main/java/top/wms/admin/schedule/service/impl/JobServiceImpl.java @@ -0,0 +1,68 @@ +package top.ysoft.admin.schedule.service.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.ysoft.admin.schedule.api.JobApi; +import top.ysoft.admin.schedule.api.JobClient; +import top.ysoft.admin.schedule.model.query.JobQuery; +import top.ysoft.admin.schedule.model.req.JobReq; +import top.ysoft.admin.schedule.model.req.JobStatusReq; +import top.ysoft.admin.schedule.model.resp.JobResp; +import top.ysoft.admin.schedule.service.JobService; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.util.Collections; +import java.util.List; + +/** + * 任务业务实现 + * + * @author KAI + * @author Charles7c + * @since 2024/6/25 17:25 + */ +@Service +@RequiredArgsConstructor +public class JobServiceImpl implements JobService { + + private final JobClient jobClient; + private final JobApi jobApi; + + @Override + public PageResp page(JobQuery query) { + return jobClient.requestPage(() -> jobApi.page(query.getGroupName(), query.getJobName(), query + .getJobStatus() != null ? query.getJobStatus().getValue() : null, query.getPage(), query.getSize())); + } + + @Override + public boolean add(JobReq req) { + return Boolean.TRUE.equals(jobClient.request(() -> jobApi.add(req))); + } + + @Override + public boolean update(JobReq req, Long id) { + req.setId(id); + return Boolean.TRUE.equals(jobClient.request(() -> jobApi.update(req))); + } + + @Override + public boolean updateStatus(JobStatusReq req, Long id) { + req.setId(id); + return Boolean.TRUE.equals(jobClient.request(() -> jobApi.updateStatus(req))); + } + + @Override + public boolean delete(Long id) { + return Boolean.TRUE.equals(jobClient.request(() -> jobApi.delete(Collections.singleton(id)))); + } + + @Override + public boolean trigger(Long id) { + return Boolean.TRUE.equals(jobClient.request(() -> jobApi.trigger(id))); + } + + @Override + public List listGroup() { + return jobClient.request(jobApi::listGroup); + } +} \ No newline at end of file diff --git a/wms-webapi/.flattened-pom.xml b/wms-webapi/.flattened-pom.xml new file mode 100644 index 0000000..6ffbce0 --- /dev/null +++ b/wms-webapi/.flattened-pom.xml @@ -0,0 +1,127 @@ + + + 4.0.0 + + top.ysoft + ysoft-admin + 3.6.0-SNAPSHOT + + ysoft-webapi + 3.6.0-SNAPSHOT + API 及打包部署模块 + + + GNU LESSER GENERAL PUBLIC LICENSE + http://www.gnu.org/licenses/lgpl.html + + + + config/ + lib/ + bin/ + top.ysoft.admin.YsoftAdminApplication + + + + top.continew + continew-starter-log-interceptor + + + top.ysoft + ysoft-module-system + + + top.ysoft + ysoft-plugin-schedule + + + top.ysoft + ysoft-plugin-open + + + top.ysoft + ysoft-plugin-generator + + + org.liquibase + liquibase-core + + + org.springframework.boot + spring-boot-starter-test + test + + + + ${project.parent.name} + + + maven-jar-plugin + + + ${config-path} + db/ + templates/ + logback-spring.xml + + + + ${main-class} + ../${lib-path} + true + false + + + ../${config-path} + + + ${project.build.directory}/app/${bin-path} + + + + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/app/${lib-path} + + + + + + maven-resources-plugin + + + copy-resources + package + + copy-resources + + + + + src/main/resources/${config-path} + + + src/main/resources + + db/ + templates/ + logback-spring.xml + + + + ${project.build.directory}/app/${config-path} + + + + + + + diff --git a/wms-webapi/pom.xml b/wms-webapi/pom.xml new file mode 100644 index 0000000..70b7422 --- /dev/null +++ b/wms-webapi/pom.xml @@ -0,0 +1,153 @@ + + + 4.0.0 + + top.ysoft + ysoft-admin + ${revision} + + + ysoft-webapi + API 及打包部署模块 + + + + + top.ysoft.admin.YsoftAdminApplication + + bin/ + + config/ + + lib/ + + + + + + top.continew + continew-starter-log-interceptor + + + + + top.ysoft + ysoft-module-system + + + + + top.ysoft + ysoft-plugin-schedule + + + + + top.ysoft + ysoft-plugin-open + + + + + top.ysoft + ysoft-plugin-generator + + + + + org.liquibase + liquibase-core + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + ${project.parent.name} + + + + org.apache.maven.plugins + maven-jar-plugin + + + + ${config-path} + db/ + templates/ + logback-spring.xml + + + + ${main-class} + + ../${lib-path} + true + + false + + + + ../${config-path} + + + ${project.build.directory}/app/${bin-path} + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/app/${lib-path} + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-resources + package + + copy-resources + + + + + src/main/resources/${config-path} + + + src/main/resources + + db/ + templates/ + logback-spring.xml + + + + ${project.build.directory}/app/${config-path} + + + + + + + \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/YsoftAdminApplication.java b/wms-webapi/src/main/java/top/wms/admin/YsoftAdminApplication.java new file mode 100644 index 0000000..3ce7902 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/YsoftAdminApplication.java @@ -0,0 +1,47 @@ +package top.ysoft.admin; + +import cn.dev33.satoken.annotation.SaIgnore; +import com.alicp.jetcache.anno.config.EnableMethodCache; +import io.swagger.v3.oas.annotations.Hidden; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.spring.EnableFileStorage; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import top.continew.starter.core.autoconfigure.project.ProjectProperties; +import top.continew.starter.extension.crud.annotation.EnableCrudRestController; +import top.continew.starter.web.annotation.EnableGlobalResponse; +import top.continew.starter.web.model.R; + +/** + * 启动程序 + * + * @author Charles7c + * @since 2022/12/8 23:15 + */ +@Slf4j +@EnableFileStorage +@EnableMethodCache(basePackages = "top.ysoft.admin") +@EnableGlobalResponse +@EnableCrudRestController +@RestController +@SpringBootApplication +@RequiredArgsConstructor +public class YsoftAdminApplication { + + private final ProjectProperties projectProperties; + + public static void main(String[] args) { + SpringApplication.run(YsoftAdminApplication.class, args); + } + + @Hidden + @SaIgnore + @GetMapping("/") + public R index() { + return R.ok(projectProperties); + } + +} diff --git a/wms-webapi/src/main/java/top/wms/admin/config/log/LogConfiguration.java b/wms-webapi/src/main/java/top/wms/admin/config/log/LogConfiguration.java new file mode 100644 index 0000000..8ed587e --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/config/log/LogConfiguration.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.config.log; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import top.ysoft.admin.system.mapper.LogMapper; +import top.ysoft.admin.system.service.UserService; +import top.continew.starter.log.annotation.ConditionalOnEnabledLog; +import top.continew.starter.log.dao.LogDao; +import top.continew.starter.web.autoconfigure.trace.TraceProperties; + +/** + * 日志配置 + * + * @author Charles7c + * @since 2022/12/24 23:15 + */ +@Configuration +@ConditionalOnEnabledLog +public class LogConfiguration { + + /** + * 日志持久层接口本地实现类 + */ + @Bean + public LogDao logDao(UserService userService, LogMapper logMapper, TraceProperties traceProperties) { + return new LogDaoLocalImpl(userService, logMapper, traceProperties); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/config/log/LogDaoLocalImpl.java b/wms-webapi/src/main/java/top/wms/admin/config/log/LogDaoLocalImpl.java new file mode 100644 index 0000000..9fb80f0 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/config/log/LogDaoLocalImpl.java @@ -0,0 +1,163 @@ +package top.ysoft.admin.config.log; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.http.HttpStatus; +import cn.hutool.json.JSONUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.scheduling.annotation.Async; +import top.ysoft.admin.auth.enums.AuthTypeEnum; +import top.ysoft.admin.auth.model.req.*; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.system.enums.LogStatusEnum; +import top.ysoft.admin.system.mapper.LogMapper; +import top.ysoft.admin.system.model.entity.LogDO; +import top.ysoft.admin.system.service.UserService; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.util.ExceptionUtils; +import top.continew.starter.core.util.StrUtils; +import top.continew.starter.log.dao.LogDao; +import top.continew.starter.log.model.LogRecord; +import top.continew.starter.log.model.LogRequest; +import top.continew.starter.log.model.LogResponse; +import top.continew.starter.web.autoconfigure.trace.TraceProperties; +import top.continew.starter.web.model.R; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Map; +import java.util.Set; + +/** + * 日志持久层接口本地实现类 + * + * @author Charles7c + * @since 2023/12/16 23:55 + */ +@RequiredArgsConstructor +public class LogDaoLocalImpl implements LogDao { + + private final UserService userService; + private final LogMapper logMapper; + private final TraceProperties traceProperties; + + @Async + @Override + public void add(LogRecord logRecord) { + LogDO logDO = new LogDO(); + // 设置请求信息 + LogRequest logRequest = logRecord.getRequest(); + this.setRequest(logDO, logRequest); + // 设置响应信息 + LogResponse logResponse = logRecord.getResponse(); + this.setResponse(logDO, logResponse); + // 设置基本信息 + logDO.setDescription(logRecord.getDescription()); + logDO.setModule(StrUtils.blankToDefault(logRecord.getModule(), null, m -> m + .replace("API", StringConstants.EMPTY) + .trim())); + logDO.setTimeTaken(logRecord.getTimeTaken().toMillis()); + logDO.setCreateTime(LocalDateTime.ofInstant(logRecord.getTimestamp(), ZoneId.systemDefault())); + // 设置操作人 + this.setCreateUser(logDO, logRequest, logResponse); + logMapper.insert(logDO); + } + + /** + * 设置请求信息 + * + * @param logDO 日志信息 + * @param logRequest 请求信息 + */ + private void setRequest(LogDO logDO, LogRequest logRequest) { + logDO.setRequestMethod(logRequest.getMethod()); + logDO.setRequestUrl(logRequest.getUrl().toString()); + logDO.setRequestHeaders(JSONUtil.toJsonStr(logRequest.getHeaders())); + logDO.setRequestBody(logRequest.getBody()); + logDO.setIp(logRequest.getIp()); + logDO.setAddress(logRequest.getAddress()); + logDO.setBrowser(logRequest.getBrowser()); + logDO.setOs(StrUtil.subBefore(logRequest.getOs(), " or", false)); + } + + /** + * 设置响应信息 + * + * @param logDO 日志信息 + * @param logResponse 响应信息 + */ + private void setResponse(LogDO logDO, LogResponse logResponse) { + Map responseHeaders = logResponse.getHeaders(); + logDO.setResponseHeaders(JSONUtil.toJsonStr(responseHeaders)); + logDO.setTraceId(responseHeaders.get(traceProperties.getTraceIdName())); + String responseBody = logResponse.getBody(); + logDO.setResponseBody(responseBody); + // 状态 + Integer statusCode = logResponse.getStatus(); + logDO.setStatusCode(statusCode); + logDO.setStatus(statusCode >= HttpStatus.HTTP_BAD_REQUEST ? LogStatusEnum.FAILURE : LogStatusEnum.SUCCESS); + if (StrUtil.isNotBlank(responseBody)) { + R result = JSONUtil.toBean(responseBody, R.class); + if (!result.isSuccess()) { + logDO.setStatus(LogStatusEnum.FAILURE); + logDO.setErrorMsg(result.getMsg()); + } + } + } + + /** + * 设置操作人 + * + * @param logDO 日志信息 + * @param logRequest 请求信息 + * @param logResponse 响应信息 + */ + private void setCreateUser(LogDO logDO, LogRequest logRequest, LogResponse logResponse) { + String requestUri = URLUtil.getPath(logDO.getRequestUrl()); + // 解析退出接口信息 + String responseBody = logResponse.getBody(); + if (requestUri.startsWith(SysConstants.LOGOUT_URI) && StrUtil.isNotBlank(responseBody)) { + R result = JSONUtil.toBean(responseBody, R.class); + logDO.setCreateUser(Convert.toLong(result.getData(), null)); + return; + } + // 解析登录接口信息 + if (requestUri.startsWith(SysConstants.LOGIN_URI) && LogStatusEnum.SUCCESS.equals(logDO.getStatus())) { + String requestBody = logRequest.getBody(); + logDO.setDescription(JSONUtil.toBean(requestBody, LoginReq.class).getAuthType().getDescription() + "登录"); + // 解析账号登录用户为操作人 + if (requestBody.contains(AuthTypeEnum.ACCOUNT.getValue())) { + AccountLoginReq authReq = JSONUtil.toBean(requestBody, AccountLoginReq.class); + logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByUsername(authReq.getUsername()) + .getId())); + return; + } else if (requestBody.contains(AuthTypeEnum.EMAIL.getValue())) { + EmailLoginReq authReq = JSONUtil.toBean(requestBody, EmailLoginReq.class); + logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByEmail(authReq.getEmail()).getId())); + return; + } else if (requestBody.contains(AuthTypeEnum.PHONE.getValue())) { + PhoneLoginReq authReq = JSONUtil.toBean(requestBody, PhoneLoginReq.class); + logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByPhone(authReq.getPhone()).getId())); + return; + } + } + // 解析 Token 信息 + Map requestHeaders = logRequest.getHeaders(); + String headerName = HttpHeaders.AUTHORIZATION; + boolean isContainsAuthHeader = CollUtil.containsAny(requestHeaders.keySet(), Set.of(headerName, headerName + .toLowerCase())); + if (MapUtil.isNotEmpty(requestHeaders) && isContainsAuthHeader) { + String authorization = requestHeaders.getOrDefault(headerName, requestHeaders.get(headerName + .toLowerCase())); + String token = authorization.replace(SaManager.getConfig() + .getTokenPrefix() + StringConstants.SPACE, StringConstants.EMPTY); + logDO.setCreateUser(Convert.toLong(StpUtil.getLoginIdByToken(token))); + } + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/config/satoken/LoginPasswordProperties.java b/wms-webapi/src/main/java/top/wms/admin/config/satoken/LoginPasswordProperties.java new file mode 100644 index 0000000..5042e4d --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/config/satoken/LoginPasswordProperties.java @@ -0,0 +1,22 @@ +package top.ysoft.admin.config.satoken; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 密码配置属性 + * + * @author Charles7c + * @since 2024/6/15 22:15 + */ +@Data +@Component +@ConfigurationProperties(prefix = "auth.password") +public class LoginPasswordProperties { + + /** + * 排除(放行)路径配置 + */ + private String[] excludes = new String[0]; +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/config/satoken/SaExtensionInterceptor.java b/wms-webapi/src/main/java/top/wms/admin/config/satoken/SaExtensionInterceptor.java new file mode 100644 index 0000000..dcf972f --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/config/satoken/SaExtensionInterceptor.java @@ -0,0 +1,46 @@ +package top.ysoft.admin.config.satoken; + +import cn.dev33.satoken.fun.SaParamFunction; +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.stp.StpUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.lang.Nullable; +import top.ysoft.admin.common.context.UserContextHolder; + +/** + * Sa-Token 扩展拦截器 + * + * @author Charles7c + * @since 2024/10/10 20:25 + */ +public class SaExtensionInterceptor extends SaInterceptor { + + public SaExtensionInterceptor(SaParamFunction auth) { + super(auth); + } + + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, + Object handler) throws Exception { + boolean flag = super.preHandle(request, response, handler); + if (flag && StpUtil.isLogin()) { + UserContextHolder.getContext(); + UserContextHolder.getExtraContext(); + } + return flag; + } + + @Override + public void afterCompletion(HttpServletRequest request, + HttpServletResponse response, + Object handler, + @Nullable Exception e) throws Exception { + try { + super.afterCompletion(request, response, handler, e); + } finally { + UserContextHolder.clearContext(); + } + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/config/satoken/SaTokenConfiguration.java b/wms-webapi/src/main/java/top/wms/admin/config/satoken/SaTokenConfiguration.java new file mode 100644 index 0000000..4b6eb9d --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/config/satoken/SaTokenConfiguration.java @@ -0,0 +1,77 @@ +package top.ysoft.admin.config.satoken; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.sign.SaSignTemplate; +import cn.dev33.satoken.sign.SaSignUtil; +import cn.dev33.satoken.stp.StpInterface; +import cn.dev33.satoken.stp.StpUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import top.ysoft.admin.common.context.UserContext; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.open.sign.OpenApiSignTemplate; +import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.exception.BusinessException; +import top.continew.starter.core.validation.CheckUtils; + +import java.util.List; + +/** + * Sa-Token 配置 + * + * @author Charles7c + * @author chengzi + * @since 2022/12/19 22:13 + */ +@Configuration +@RequiredArgsConstructor +public class SaTokenConfiguration { + + private final SaTokenExtensionProperties properties; + private final LoginPasswordProperties loginPasswordProperties; + private final OpenApiSignTemplate signTemplate; + + /** + * Sa-Token 权限认证配置 + */ + @Bean + public StpInterface stpInterface() { + return new SaTokenPermissionImpl(); + } + + /** + * SaToken 拦截器配置 + */ + @Bean + public SaInterceptor saInterceptor() { + SaManager.setSaSignTemplate(signTemplate); + return new SaExtensionInterceptor(handle -> SaRouter.match(StringConstants.PATH_PATTERN) + .notMatch(properties.getSecurity().getExcludes()) + .check(r -> { + // 如果包含 sign,进行 API 接口参数签名验证 + SaRequest saRequest = SaHolder.getRequest(); + List paramNames = saRequest.getParamNames(); + if (paramNames.stream().anyMatch(SaSignTemplate.sign::equals)) { + try { + SaSignUtil.checkRequest(saRequest); + } catch (Exception e) { + throw new BusinessException(e.getMessage()); + } + return; + } + // 不包含 sign 参数,进行普通登录验证 + StpUtil.checkLogin(); + if (SaRouter.isMatchCurrURI(loginPasswordProperties.getExcludes())) { + return; + } + UserContext userContext = UserContextHolder.getContext(); + CheckUtils.throwIf(userContext.isPasswordExpired(), "密码已过期,请修改密码"); + })); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/config/satoken/SaTokenPermissionImpl.java b/wms-webapi/src/main/java/top/wms/admin/config/satoken/SaTokenPermissionImpl.java new file mode 100644 index 0000000..efc9d4a --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/config/satoken/SaTokenPermissionImpl.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.config.satoken; + +import cn.dev33.satoken.stp.StpInterface; +import top.ysoft.admin.common.context.UserContext; +import top.ysoft.admin.common.context.UserContextHolder; + +import java.util.ArrayList; +import java.util.List; + +/** + * Sa-Token 权限认证实现 + * + * @author Charles7c + * @since 2023/3/1 22:28 + */ +public class SaTokenPermissionImpl implements StpInterface { + + @Override + public List getPermissionList(Object loginId, String loginType) { + UserContext userContext = UserContextHolder.getContext(); + return new ArrayList<>(userContext.getPermissions()); + } + + @Override + public List getRoleList(Object loginId, String loginType) { + UserContext userContext = UserContextHolder.getContext(); + return new ArrayList<>(userContext.getRoleCodes()); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/auth/AuthController.java b/wms-webapi/src/main/java/top/wms/admin/controller/auth/AuthController.java new file mode 100644 index 0000000..edb2555 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/auth/AuthController.java @@ -0,0 +1,104 @@ +package top.ysoft.admin.controller.auth; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.bean.BeanUtil; +import com.xkcoding.justauth.AuthRequestFactory; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import me.zhyd.oauth.request.AuthRequest; +import me.zhyd.oauth.utils.AuthStateUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import top.ysoft.admin.auth.model.req.LoginReq; +import top.ysoft.admin.auth.model.resp.LoginResp; +import top.ysoft.admin.auth.model.resp.RouteResp; +import top.ysoft.admin.auth.model.resp.SocialAuthAuthorizeResp; +import top.ysoft.admin.auth.model.resp.UserInfoResp; +import top.ysoft.admin.auth.service.AuthService; +import top.ysoft.admin.common.context.UserContext; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.system.model.resp.user.UserDetailResp; +import top.ysoft.admin.system.service.UserService; +import top.continew.starter.core.exception.BadRequestException; +import top.continew.starter.log.annotation.Log; + +import java.util.List; + +/** + * 认证 API + * + * @author Charles7c + * @since 2022/12/21 20:37 + */ +@Tag(name = "认证 API") +@Log(module = "登录") +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/auth") +public class AuthController { + + private final AuthService authService; + private final UserService userService; + private final AuthRequestFactory authRequestFactory; + + @SaIgnore + @Operation(summary = "登录", description = "用户登录") + @PostMapping("/login") + public LoginResp login(@Validated @RequestBody LoginReq req, HttpServletRequest request) { + return authService.login(req, request); + } + + @Operation(summary = "登出", description = "注销用户的当前登录") + @Parameter(name = "Authorization", description = "令牌", required = true, example = "Bearer xxxx-xxxx-xxxx-xxxx", in = ParameterIn.HEADER) + @PostMapping("/logout") + public Object logout() { + Object loginId = StpUtil.getLoginId(-1L); + StpUtil.logout(); + return loginId; + } + + @SaIgnore + @Operation(summary = "三方账号登录授权", description = "三方账号登录授权") + @Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH) + @GetMapping("/{source}") + public SocialAuthAuthorizeResp authorize(@PathVariable String source) { + AuthRequest authRequest = this.getAuthRequest(source); + return SocialAuthAuthorizeResp.builder() + .authorizeUrl(authRequest.authorize(AuthStateUtils.createState())) + .build(); + } + + @Log(ignore = true) + @Operation(summary = "获取用户信息", description = "获取登录用户信息") + @GetMapping("/user/info") + public UserInfoResp getUserInfo() { + UserContext userContext = UserContextHolder.getContext(); + UserDetailResp userDetailResp = userService.get(userContext.getId()); + UserInfoResp userInfoResp = BeanUtil.copyProperties(userDetailResp, UserInfoResp.class); + userInfoResp.setPermissions(userContext.getPermissions()); + userInfoResp.setRoles(userContext.getRoleCodes()); + userInfoResp.setPwdExpired(userContext.isPasswordExpired()); + return userInfoResp; + } + + @Log(ignore = true) + @Operation(summary = "获取路由信息", description = "获取登录用户的路由信息") + @GetMapping("/user/route") + public List listRoute() { + return authService.buildRouteTree(UserContextHolder.getUserId()); + } + + private AuthRequest getAuthRequest(String source) { + try { + return authRequestFactory.get(source); + } catch (Exception e) { + throw new BadRequestException("暂不支持 [%s] 平台账号登录".formatted(source)); + } + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/code/GeneratorController.java b/wms-webapi/src/main/java/top/wms/admin/controller/code/GeneratorController.java new file mode 100644 index 0000000..0534bb5 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/code/GeneratorController.java @@ -0,0 +1,107 @@ +package top.ysoft.admin.controller.code; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import top.ysoft.admin.generator.model.entity.FieldConfigDO; +import top.ysoft.admin.generator.model.entity.GenConfigDO; +import top.ysoft.admin.generator.model.query.GenConfigQuery; +import top.ysoft.admin.generator.model.req.GenConfigReq; +import top.ysoft.admin.generator.model.resp.GeneratePreviewResp; +import top.ysoft.admin.generator.service.GeneratorService; +import top.ysoft.admin.system.service.DictService; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.sql.SQLException; +import java.util.List; + +/** + * 代码生成 API + * + * @author Charles7c + * @since 2023/8/3 22:58 + */ +@Tag(name = "代码生成 API") +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/code/generator") +public class GeneratorController { + + private final GeneratorService baseService; + private final DictService dictService; + + @Operation(summary = "分页查询生成配置", description = "分页查询生成配置列表") + @SaCheckPermission("code:generator:list") + @GetMapping("/config") + public PageResp pageGenConfig(GenConfigQuery query, @Validated PageQuery pageQuery) { + return baseService.pageGenConfig(query, pageQuery); + } + + @Operation(summary = "查询生成配置信息", description = "查询生成配置信息") + @Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH) + @SaCheckPermission("code:generator:list") + @GetMapping("/config/{tableName}") + public GenConfigDO getGenConfig(@PathVariable String tableName) throws SQLException { + return baseService.getGenConfig(tableName); + } + + @Operation(summary = "查询字段配置列表", description = "查询字段配置列表") + @Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH) + @Parameter(name = "requireSync", description = "是否需要同步", example = "false", in = ParameterIn.QUERY) + @SaCheckPermission("code:generator:config") + @GetMapping("/field/{tableName}") + public List listFieldConfig(@PathVariable String tableName, + @RequestParam(required = false, defaultValue = "false") Boolean requireSync) { + return baseService.listFieldConfig(tableName, requireSync); + } + + @Operation(summary = "保存配置信息", description = "保存配置信息") + @Parameter(name = "tableName", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH) + @SaCheckPermission("code:generator:config") + @PostMapping("/config/{tableName}") + public void saveConfig(@Validated @RequestBody GenConfigReq req, @PathVariable String tableName) { + baseService.saveConfig(req, tableName); + } + + @Operation(summary = "生成预览", description = "预览生成代码") + @Parameter(name = "tableNames", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH) + @SaCheckPermission("code:generator:preview") + @GetMapping("/preview/{tableNames}") + public List preview(@PathVariable List tableNames) { + return baseService.preview(tableNames); + } + + @Operation(summary = "生成下载代码", description = "生成下载代码") + @Parameter(name = "tableNames", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH) + @SaCheckPermission("code:generator:generate") + @PostMapping("/{tableNames}/download") + public void downloadCode(@PathVariable List tableNames, HttpServletResponse response) { + baseService.downloadCode(tableNames, response); + } + + @Operation(summary = "生成代码", description = "生成代码") + @Parameter(name = "tableNames", description = "表名称", required = true, example = "sys_user", in = ParameterIn.PATH) + @SaCheckPermission("code:generator:generate") + @PostMapping("/{tableNames}") + public void generateCode(@PathVariable List tableNames) { + baseService.generateCode(tableNames); + } + + @Operation(summary = "查询字典", description = "查询字典列表") + @SaCheckPermission("code:generator:config") + @GetMapping("/dict") + public List listDict() { + List dictList = dictService.listDict(null, null); + dictList.addAll(dictService.listEnumDict()); + return dictList; + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/common/CaptchaController.java b/wms-webapi/src/main/java/top/wms/admin/controller/common/CaptchaController.java new file mode 100644 index 0000000..9542270 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/common/CaptchaController.java @@ -0,0 +1,203 @@ +package top.ysoft.admin.controller.common; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.JakartaServletUtil; +import com.anji.captcha.model.common.RepCodeEnum; +import com.anji.captcha.model.common.ResponseModel; +import com.anji.captcha.model.vo.CaptchaVO; +import com.anji.captcha.service.CaptchaService; +import com.wf.captcha.base.Captcha; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.RequiredArgsConstructor; +import org.dromara.sms4j.api.SmsBlend; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.core.factory.SmsFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import top.ysoft.admin.auth.model.resp.CaptchaResp; +import top.ysoft.admin.common.config.properties.CaptchaProperties; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.common.constant.SysConstants; +import top.ysoft.admin.system.enums.OptionCategoryEnum; +import top.ysoft.admin.system.service.OptionService; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.captcha.graphic.core.GraphicCaptchaService; +import top.continew.starter.core.autoconfigure.project.ProjectProperties; +import top.continew.starter.core.util.TemplateUtils; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.log.annotation.Log; +import top.continew.starter.messaging.mail.util.MailUtils; +import top.continew.starter.security.limiter.annotation.RateLimiter; +import top.continew.starter.security.limiter.annotation.RateLimiters; +import top.continew.starter.security.limiter.enums.LimitType; +import top.continew.starter.web.model.R; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * 验证码 API + * + * @author Charles7c + * @since 2022/12/11 14:00 + */ +@Tag(name = "验证码 API") +@SaIgnore +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/captcha") +public class CaptchaController { + + private final ProjectProperties projectProperties; + private final CaptchaProperties captchaProperties; + private final CaptchaService behaviorCaptchaService; + private final GraphicCaptchaService graphicCaptchaService; + private final OptionService optionService; + + @Log(ignore = true) + @Operation(summary = "获取行为验证码", description = "获取行为验证码(Base64编码)") + @GetMapping("/behavior") + public Object getBehaviorCaptcha(CaptchaVO captchaReq, HttpServletRequest request) { + captchaReq.setBrowserInfo(JakartaServletUtil.getClientIP(request) + request.getHeader(HttpHeaders.USER_AGENT)); + ResponseModel responseModel = behaviorCaptchaService.get(captchaReq); + CheckUtils.throwIf(() -> !StrUtil.equals(RepCodeEnum.SUCCESS.getCode(), responseModel + .getRepCode()), responseModel.getRepMsg()); + return responseModel.getRepData(); + } + + @Log(ignore = true) + @Operation(summary = "校验行为验证码", description = "校验行为验证码") + @PostMapping("/behavior") + public Object checkBehaviorCaptcha(@RequestBody CaptchaVO captchaReq, HttpServletRequest request) { + captchaReq.setBrowserInfo(JakartaServletUtil.getClientIP(request) + request.getHeader(HttpHeaders.USER_AGENT)); + return behaviorCaptchaService.check(captchaReq); + } + + @Log(ignore = true) + @Operation(summary = "获取图片验证码", description = "获取图片验证码(Base64编码,带图片格式:data:image/gif;base64)") + @GetMapping("/image") + public CaptchaResp getImageCaptcha() { + int loginCaptchaEnabled = optionService.getValueByCode2Int("LOGIN_CAPTCHA_ENABLED"); + if (SysConstants.NO.equals(loginCaptchaEnabled)) { + return CaptchaResp.builder().isEnabled(false).build(); + } + String uuid = IdUtil.fastUUID(); + String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + uuid; + Captcha captcha = graphicCaptchaService.getCaptcha(); + long expireTime = LocalDateTimeUtil.toEpochMilli(LocalDateTime.now() + .plusMinutes(captchaProperties.getExpirationInMinutes())); + RedisUtils.set(captchaKey, captcha.text(), Duration.ofMinutes(captchaProperties.getExpirationInMinutes())); + return CaptchaResp.of(uuid, captcha.toBase64(), expireTime); + } + + /** + * 获取邮箱验证码 + * + *

+ * 限流规则:
+ * 1.同一邮箱同一模板,1分钟2条,1小时8条,24小时20条
+ * 2、同一邮箱所有模板 24 小时 100 条
+ * 3、同一 IP 每分钟限制发送 30 条 + *

+ * + * @param email 邮箱 + * @return / + */ + @Operation(summary = "获取邮箱验证码", description = "发送验证码到指定邮箱") + @GetMapping("/mail") + @RateLimiters({ + @RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX + "MIN", key = "#email + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.mail.templatePath')", rate = 2, interval = 1, unit = TimeUnit.MINUTES, message = "获取验证码操作太频繁,请稍后再试"), + @RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX + "HOUR", key = "#email + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.mail.templatePath')", rate = 8, interval = 1, unit = TimeUnit.HOURS, message = "获取验证码操作太频繁,请稍后再试"), + @RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX + "DAY'", key = "#email + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.mail.templatePath')", rate = 20, interval = 24, unit = TimeUnit.HOURS, message = "获取验证码操作太频繁,请稍后再试"), + @RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX, key = "#email", rate = 100, interval = 24, unit = TimeUnit.HOURS, message = "获取验证码操作太频繁,请稍后再试"), + @RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX, key = "#email", rate = 30, interval = 1, unit = TimeUnit.MINUTES, type = LimitType.IP, message = "获取验证码操作太频繁,请稍后再试")}) + public R getMailCaptcha(@NotBlank(message = "邮箱不能为空") @Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误") String email, + CaptchaVO captchaReq) throws MessagingException { + // 行为验证码校验 + ResponseModel verificationRes = behaviorCaptchaService.verification(captchaReq); + ValidationUtils.throwIfNotEqual(verificationRes.getRepCode(), RepCodeEnum.SUCCESS.getCode(), verificationRes + .getRepMsg()); + // 生成验证码 + CaptchaProperties.CaptchaMail captchaMail = captchaProperties.getMail(); + String captcha = RandomUtil.randomNumbers(captchaMail.getLength()); + // 发送验证码 + Long expirationInMinutes = captchaMail.getExpirationInMinutes(); + Map siteConfig = optionService.getByCategory(OptionCategoryEnum.SITE); + String content = TemplateUtils.render(captchaMail.getTemplatePath(), Dict.create() + .set("siteUrl", projectProperties.getUrl()) + .set("siteTitle", siteConfig.get("SITE_TITLE")) + .set("siteCopyright", siteConfig.get("SITE_COPYRIGHT")) + .set("captcha", captcha) + .set("expiration", expirationInMinutes)); + MailUtils.sendHtml(email, "【%s】邮箱验证码".formatted(projectProperties.getName()), content); + // 保存验证码 + String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + email; + RedisUtils.set(captchaKey, captcha, Duration.ofMinutes(expirationInMinutes)); + return R.ok("发送成功,验证码有效期 %s 分钟".formatted(expirationInMinutes)); + } + + /** + * 获取短信验证码 + * + *

+ * 限流规则:
+ * 1.同一号码同一模板,1分钟2条,1小时8条,24小时20条
+ * 2、同一号码所有模板 24 小时 100 条
+ * 3、同一 IP 每分钟限制发送 30 条 + *

+ * + * @param phone 手机号 + * @param captchaReq 行为验证码信息 + * @return / + */ + @Operation(summary = "获取短信验证码", description = "发送验证码到指定手机号") + @GetMapping("/sms") + @RateLimiters({ + @RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX + "MIN", key = "#phone + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.sms.templateId')", rate = 2, interval = 1, unit = TimeUnit.MINUTES, message = "获取验证码操作太频繁,请稍后再试"), + @RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX + "HOUR", key = "#phone + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.sms.templateId')", rate = 8, interval = 1, unit = TimeUnit.HOURS, message = "获取验证码操作太频繁,请稍后再试"), + @RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX + "DAY'", key = "#phone + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.sms.templateId')", rate = 20, interval = 24, unit = TimeUnit.HOURS, message = "获取验证码操作太频繁,请稍后再试"), + @RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX, key = "#phone", rate = 100, interval = 24, unit = TimeUnit.HOURS, message = "获取验证码操作太频繁,请稍后再试"), + @RateLimiter(name = CacheConstants.CAPTCHA_KEY_PREFIX, key = "#phone", rate = 30, interval = 1, unit = TimeUnit.MINUTES, type = LimitType.IP, message = "获取验证码操作太频繁,请稍后再试")}) + public R getSmsCaptcha(@NotBlank(message = "手机号不能为空") @Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误") String phone, + CaptchaVO captchaReq) { + // 行为验证码校验 + ResponseModel verificationRes = behaviorCaptchaService.verification(captchaReq); + ValidationUtils.throwIfNotEqual(verificationRes.getRepCode(), RepCodeEnum.SUCCESS.getCode(), verificationRes + .getRepMsg()); + CaptchaProperties.CaptchaSms captchaSms = captchaProperties.getSms(); + // 生成验证码 + String captcha = RandomUtil.randomNumbers(captchaSms.getLength()); + // 发送验证码 + Long expirationInMinutes = captchaSms.getExpirationInMinutes(); + SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.CLOOPEN); + Map messageMap = MapUtil.newHashMap(2, true); + messageMap.put("captcha", captcha); + messageMap.put("expirationInMinutes", String.valueOf(expirationInMinutes)); + SmsResponse smsResponse = smsBlend.sendMessage(phone, captchaSms + .getTemplateId(), (LinkedHashMap)messageMap); + CheckUtils.throwIf(!smsResponse.isSuccess(), "验证码发送失败"); + // 保存验证码 + String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + phone; + RedisUtils.set(captchaKey, captcha, Duration.ofMinutes(expirationInMinutes)); + return R.ok("发送成功,验证码有效期 %s 分钟".formatted(expirationInMinutes)); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/common/CommonController.java b/wms-webapi/src/main/java/top/wms/admin/controller/common/CommonController.java new file mode 100644 index 0000000..8786d95 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/common/CommonController.java @@ -0,0 +1,188 @@ +package top.ysoft.admin.controller.common; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.util.StrUtil; +import com.alicp.jetcache.anno.Cached; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.dromara.x.file.storage.core.FileInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.common.util.PictureUtils; +import top.ysoft.admin.peopleBranch.model.query.BranchQuery; +import top.ysoft.admin.peopleBranch.service.BranchService; +import top.ysoft.admin.space.model.query.PointQuery; +import top.ysoft.admin.space.model.query.SpaceQuery; +import top.ysoft.admin.space.service.PointService; +import top.ysoft.admin.space.service.SpaceService; +import top.ysoft.admin.system.enums.OptionCategoryEnum; +import top.ysoft.admin.system.model.query.*; +import top.ysoft.admin.system.model.resp.file.FileUploadResp; +import top.ysoft.admin.system.service.*; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.extension.crud.model.query.SortQuery; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.log.annotation.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +/** + * 公共 API + * + * @author Charles7c + * @since 2023/1/22 21:48 + */ +@Tag(name = "公共 API") +@Log(ignore = true) +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/common") +public class CommonController { + + private static final Logger log = LoggerFactory.getLogger(CommonController.class); + private final FileService fileService; + private final DeptService deptService; + private final MenuService menuService; + private final UserService userService; + private final RoleService roleService; + private final BranchService branchService; + private final DictItemService dictItemService; + private final OptionService optionService; + private final SpaceService spaceService; + private final PointService pointService; + + @Operation(summary = "上传文件", description = "上传文件") + @PostMapping("/file") + public FileUploadResp upload(@NotNull(message = "文件不能为空") MultipartFile file) { + ValidationUtils.throwIf(file::isEmpty, "文件不能为空"); + FileInfo fileInfo = fileService.upload(file, true); + return FileUploadResp.builder() + .id(fileInfo.getId()) + .url(fileInfo.getUrl()) + .thUrl(fileInfo.getThUrl()) + .metadata(fileInfo.getMetadata()) + .build(); + } + + @Operation(summary = "上传文件", description = "上传文件") + @PostMapping("/updateUpload") + public FileUploadResp updateUpload(@NotNull(message = "文件不能为空") MultipartFile file, String savePath) { + ValidationUtils.throwIf(file::isEmpty, "文件不能为空"); + FileInfo fileInfo = fileService.upload(file, savePath, null, false, false); + return FileUploadResp.builder() + .id(fileInfo.getId()) + .url(fileInfo.getUrl()) + .thUrl(fileInfo.getThUrl()) + .metadata(fileInfo.getMetadata()) + .build(); + } + + @Operation(summary = "上传文件", description = "上传文件") + @PostMapping("/faceUpload") + public FileUploadResp faceUpload(@NotNull(message = "图片不能为空") MultipartFile file, String savePath) { + ValidationUtils.throwIf(file::isEmpty, "图片不能为空"); + + // 判断是否需要压缩图片 + if ((1024 * 1024 * 0.1) <= file.getSize()) { + try (InputStream inputStream = file.getInputStream()) { + float quality; + // 根据文件大小设置不同的压缩质量 + if ((1024 * 1024 * 0.1) <= file.getSize() && file.getSize() <= (1024 * 1024)) { + quality = 0.4f; // 小于1M的图片 + } else if ((1024 * 1024) < file.getSize() && file.getSize() <= (1024 * 1024 * 2)) { + quality = 0.2f; // 1-2M的图片 + } else { + quality = 0.1f; // 2M以上的图片 + } + + // 直接在内存中压缩图片,避免使用临时文件 + file = PictureUtils.compressImage(inputStream, file.getOriginalFilename(), quality); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + FileInfo fileInfo = fileService.upload(file, savePath, null, false, false); + return FileUploadResp.builder() + .id(fileInfo.getId()) + .url(fileInfo.getUrl()) + .thUrl(fileInfo.getThUrl()) + .metadata(fileInfo.getMetadata()) + .build(); + } + + @Operation(summary = "查询部门树", description = "查询树结构的部门列表") + @GetMapping("/tree/dept") + public List> listDeptTree(DeptQuery query, SortQuery sortQuery) { + return deptService.tree(query, sortQuery, true); + } + + @Operation(summary = "查询部门树", description = "查询树结构的部门列表") + @GetMapping("/tree/branch") + public List> listBranchTree(BranchQuery query) { + return branchService.tree(query); + } + + @Operation(summary = "查询空间树", description = "查询树结构的空间列表") + @GetMapping("/tree/space") + public List> listSpaceTree(SpaceQuery query, SortQuery sortQuery) { + return spaceService.tree(query, sortQuery, true); + } + + @Operation(summary = "查询空间点位树", description = "查询树结构的菜单列表") + @GetMapping("/tree/point") + public List> listPointTree(PointQuery query) { + return pointService.tree(query); + } + + @Operation(summary = "查询菜单树", description = "查询树结构的菜单列表") + @GetMapping("/tree/menu") + public List> listMenuTree(MenuQuery query, SortQuery sortQuery) { + return menuService.tree(query, sortQuery, true); + } + + @Operation(summary = "查询用户字典", description = "查询用户字典列表") + @GetMapping("/dict/user") + public List listUserDict(UserQuery query, SortQuery sortQuery) { + return userService.listDict(query, sortQuery); + } + + @Operation(summary = "查询角色字典", description = "查询角色字典列表") + @GetMapping("/dict/role") + public List listRoleDict(RoleQuery query, SortQuery sortQuery) { + return roleService.listDict(query, sortQuery); + } + + @Operation(summary = "查询字典", description = "查询字典列表") + @Parameter(name = "code", description = "字典编码", example = "notice_type", in = ParameterIn.PATH) + @GetMapping("/dict/{code}") + public List listDict(@PathVariable String code) { + return dictItemService.listByDictCode(code); + } + + @SaIgnore + @Operation(summary = "查询系统配置参数", description = "查询系统配置参数") + @GetMapping("/dict/option/site") + @Cached(key = "'SITE'", name = CacheConstants.OPTION_KEY_PREFIX) + public List> listSiteOptionDict() { + OptionQuery optionQuery = new OptionQuery(); + optionQuery.setCategory(OptionCategoryEnum.SITE.name()); + return optionService.list(optionQuery) + .stream() + .map(option -> new LabelValueResp<>(option.getCode(), StrUtil.nullToDefault(option.getValue(), option + .getDefaultValue()))) + .toList(); + } +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/common/DashboardController.java b/wms-webapi/src/main/java/top/wms/admin/controller/common/DashboardController.java new file mode 100644 index 0000000..1ce3448 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/common/DashboardController.java @@ -0,0 +1,118 @@ +package top.ysoft.admin.controller.common; + +import com.alicp.jetcache.anno.CachePenetrationProtect; +import com.alicp.jetcache.anno.CacheRefresh; +import com.alicp.jetcache.anno.CacheType; +import com.alicp.jetcache.anno.Cached; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.system.model.resp.dashboard.DashboardAccessTrendResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardChartCommonResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardNoticeResp; +import top.ysoft.admin.system.model.resp.dashboard.DashboardOverviewCommonResp; +import top.ysoft.admin.system.service.DashboardService; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.log.annotation.Log; + +import java.io.IOException; +import java.util.List; + +/** + * 仪表盘 API + * + * @author Charles7c + * @since 2023/1/22 21:48 + */ +@Tag(name = "仪表盘 API") +@Log(ignore = true) +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/dashboard") +public class DashboardController { + + private final DashboardService dashboardService; + + @Operation(summary = "查询公告列表", description = "查询公告列表") + @GetMapping("/notice") + public List listNotice() { + return dashboardService.listNotice(); + } + + @Operation(summary = "查询PV总览", description = "查询PV总览") + @GetMapping("/analysis/overview/pv") + public DashboardOverviewCommonResp getOverviewPv() { + return dashboardService.getOverviewPv(); + } + + @Operation(summary = "查询IP总览", description = "查询IP总览") + @GetMapping("/analysis/overview/ip") + public DashboardOverviewCommonResp getOverviewIp() { + return dashboardService.getOverviewIp(); + } + + @Operation(summary = "查询地域分析", description = "查询地域分析") + @GetMapping("/analysis/geo") + @CachePenetrationProtect + @CacheRefresh(refresh = 7200) + @Cached(key = "'GEO'", name = CacheConstants.DASHBOARD_KEY_PREFIX, cacheType = CacheType.BOTH, syncLocal = true) + public List getAnalysisGeo() throws IOException { + return dashboardService.getAnalysisGeo(); + } + + @Operation(summary = "查询访问趋势信息", description = "查询访问趋势信息") + @Parameter(name = "days", description = "日期数", example = "30", in = ParameterIn.PATH) + @GetMapping("/access/trend/{days}") + @CachePenetrationProtect + @CacheRefresh(refresh = 7200) + @Cached(key = "#days", name = CacheConstants.DASHBOARD_KEY_PREFIX, cacheType = CacheType.BOTH, syncLocal = true) + public List listAccessTrend(@PathVariable Integer days) { + ValidationUtils.throwIf(7 != days && 30 != days, "仅支持查询近 7/30 天访问趋势信息"); + return dashboardService.listAccessTrend(days); + } + + @Operation(summary = "查询访问时段分析", description = "查询访问时段分析") + @GetMapping("/analysis/timeslot") + @CachePenetrationProtect + @CacheRefresh(refresh = 7200) + @Cached(key = "'TIMESLOT'", name = CacheConstants.DASHBOARD_KEY_PREFIX, cacheType = CacheType.BOTH, syncLocal = true) + public List getAnalysisTimeslot() { + return dashboardService.getAnalysisTimeslot(); + } + + @Operation(summary = "查询模块分析", description = "查询模块分析") + @GetMapping("/analysis/module") + @CachePenetrationProtect + @CacheRefresh(refresh = 7200) + @Cached(key = "'MODULE'", name = CacheConstants.DASHBOARD_KEY_PREFIX, cacheType = CacheType.BOTH, syncLocal = true) + public List getAnalysisModule() { + return dashboardService.getAnalysisModule(); + } + + @Operation(summary = "查询终端分析", description = "查询终端分析") + @GetMapping("/analysis/os") + @CachePenetrationProtect + @CacheRefresh(refresh = 7200) + @Cached(key = "'OS'", name = CacheConstants.DASHBOARD_KEY_PREFIX, cacheType = CacheType.BOTH, syncLocal = true) + public List getAnalysisOs() { + return dashboardService.getAnalysisOs(); + } + + @Operation(summary = "查询浏览器分析", description = "查询浏览器分析") + @GetMapping("/analysis/browser") + @CachePenetrationProtect + @CacheRefresh(refresh = 7200) + @Cached(key = "'BROWSER'", name = CacheConstants.DASHBOARD_KEY_PREFIX, cacheType = CacheType.BOTH, syncLocal = true) + public List getAnalysisBrowser() { + return dashboardService.getAnalysisBrowser(); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/consume/DownRecordController.java b/wms-webapi/src/main/java/top/wms/admin/controller/consume/DownRecordController.java new file mode 100644 index 0000000..fa5aeeb --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/consume/DownRecordController.java @@ -0,0 +1,42 @@ +package top.ysoft.admin.controller.consume; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import top.continew.starter.extension.crud.enums.Api; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.log.annotation.Log; +import top.continew.starter.web.model.R; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.common.enums.OperTypeEnum; +import top.ysoft.admin.consume.model.query.DownRecordQuery; +import top.ysoft.admin.consume.model.req.DownRecordReq; +import top.ysoft.admin.consume.model.resp.DownRecordResp; +import top.ysoft.admin.consume.service.DownRecordService; + +/** + * 消费下发记录管理 API + * + * @author zc + * @since 2026/02/25 11:01 + */ +@Tag(name = "消费下发记录管理 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/consume/downRecord", api = {Api.PAGE, Api.EXPORT}) +public class DownRecordController extends BaseController { + + @Operation(summary = "下发", description = "下发") + @SaCheckPermission("consume:downRecord:down") + @PatchMapping("/down/{equipmentId}/{peopleId}") + public R down(@PathVariable Long equipmentId, @PathVariable Long peopleId) { + baseService.down(equipmentId, peopleId); + return R.ok(); + } + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/consume/GroupController.java b/wms-webapi/src/main/java/top/wms/admin/controller/consume/GroupController.java new file mode 100644 index 0000000..b2bd415 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/consume/GroupController.java @@ -0,0 +1,26 @@ +package top.ysoft.admin.controller.consume; + +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RestController; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.consume.model.query.GroupQuery; +import top.ysoft.admin.consume.model.req.GroupReq; +import top.ysoft.admin.consume.model.resp.GroupResp; +import top.ysoft.admin.consume.service.GroupService; + +/** + * 消费组管理 API + * + * @author zc + * @since 2026/01/27 13:48 + */ +@Tag(name = "消费批量充值 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/consume/group", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}) +public class GroupController extends BaseController { + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/consume/RechargeRecordController.java b/wms-webapi/src/main/java/top/wms/admin/controller/consume/RechargeRecordController.java new file mode 100644 index 0000000..7c4cd7f --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/consume/RechargeRecordController.java @@ -0,0 +1,26 @@ +package top.ysoft.admin.controller.consume; + +import top.continew.starter.extension.crud.enums.Api; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; +import lombok.RequiredArgsConstructor; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.consume.model.query.RechargeRecordQuery; +import top.ysoft.admin.consume.model.req.RechargeRecordReq; +import top.ysoft.admin.consume.model.resp.RechargeRecordResp; +import top.ysoft.admin.consume.service.RechargeRecordService; + +/** + * 充值记录管理 API + * + * @author zc + * @since 2026/01/29 16:11 + */ +@Tag(name = "充值记录管理 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/admin/rechargeRecord", api = {Api.PAGE, Api.DELETE, Api.EXPORT}) +public class RechargeRecordController extends BaseController { + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/consume/RecordController.java b/wms-webapi/src/main/java/top/wms/admin/controller/consume/RecordController.java new file mode 100644 index 0000000..eae0660 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/consume/RecordController.java @@ -0,0 +1,29 @@ +package top.ysoft.admin.controller.consume; + +import top.continew.starter.extension.crud.enums.Api; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.consume.model.query.RecordQuery; +import top.ysoft.admin.consume.model.req.RecordReq; +import top.ysoft.admin.consume.model.resp.RecordResp; +import top.ysoft.admin.consume.service.RecordService; + +/** + * 消费记录管理 API + * + * @author zc + * @since 2026/01/21 17:53 + */ +@Tag(name = "消费记录管理 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/consume/record", api = {Api.PAGE, Api.DETAIL, Api.DELETE, Api.EXPORT}) +public class RecordController extends BaseController { + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/consume/ReportController.java b/wms-webapi/src/main/java/top/wms/admin/controller/consume/ReportController.java new file mode 100644 index 0000000..b3d7d13 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/consume/ReportController.java @@ -0,0 +1,73 @@ +package top.ysoft.admin.controller.consume; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.web.model.R; +import top.ysoft.admin.consume.model.query.ReportQuery; +import top.ysoft.admin.consume.model.resp.report.BranchReport; +import top.ysoft.admin.consume.model.resp.report.MealReport; +import top.ysoft.admin.consume.model.resp.report.MerchantReport; +import top.ysoft.admin.consume.model.resp.report.PeopleReport; +import top.ysoft.admin.consume.service.ReportService; + +import java.util.List; + +@Tag(name = "报表管理 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/consume/report") +public class ReportController { + + private final ReportService reportService; + + @GetMapping("/merchant") + public R> queryMerchantReport(ReportQuery query) { + List list = reportService.queryMerchantReport(query); + return R.ok(list); + } + + @GetMapping("/meal") + public R> queryMealReport(ReportQuery query) { + List list = reportService.queryMealReport(query); + return R.ok(list); + } + + @GetMapping("/branch") + public R> queryBranchReport(ReportQuery query) { + List list = reportService.queryBranchReport(query); + return R.ok(list); + } + + @GetMapping("/people") + public PageResp queryPeopleReport(ReportQuery query, @Validated PageQuery pageQuery) { + return reportService.page(query, pageQuery); + } + + @GetMapping("/exportMerchant") + public void exportMerchant(ReportQuery query, HttpServletResponse response) { + reportService.exportMerchant(query, response); + } + + @GetMapping("/exportMeal") + public void exportMeal(ReportQuery query, HttpServletResponse response) { + reportService.exportMeal(query, response); + } + + @GetMapping("/exportBranch") + public void exportBranch(ReportQuery query, HttpServletResponse response) { + reportService.exportBranch(query, response); + } + + @GetMapping("/exportPeople") + public void exportPeople(ReportQuery query, HttpServletResponse response) { + reportService.exportPeople(query, response); + } + +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/consume/TenantController.java b/wms-webapi/src/main/java/top/wms/admin/controller/consume/TenantController.java new file mode 100644 index 0000000..3ca4305 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/consume/TenantController.java @@ -0,0 +1,30 @@ +package top.ysoft.admin.controller.consume; + +import top.continew.starter.extension.crud.enums.Api; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.consume.model.query.TenantQuery; +import top.ysoft.admin.consume.model.req.TenantReq; +import top.ysoft.admin.consume.model.resp.TenantResp; +import top.ysoft.admin.consume.service.TenantService; + +/** + * 商户信息管理 API + * + * @author zc + * @since 2026/01/30 14:55 + */ +@Tag(name = "商户信息管理 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/consume/tenant", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, + Api.EXPORT}) +public class TenantController extends BaseController { + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/consume/TimeIntervalController.java b/wms-webapi/src/main/java/top/wms/admin/controller/consume/TimeIntervalController.java new file mode 100644 index 0000000..4e73ea0 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/consume/TimeIntervalController.java @@ -0,0 +1,30 @@ +package top.ysoft.admin.controller.consume; + +import top.continew.starter.extension.crud.enums.Api; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.consume.model.query.TimeIntervalQuery; +import top.ysoft.admin.consume.model.req.TimeIntervalReq; +import top.ysoft.admin.consume.model.resp.TimeIntervalResp; +import top.ysoft.admin.consume.service.TimeIntervalService; + +/** + * 消费时段管理 API + * + * @author zc + * @since 2026/01/30 14:56 + */ +@Tag(name = "消费时段管理 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/consume/timeInterval", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, + Api.EXPORT}) +public class TimeIntervalController extends BaseController { + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/equipment/EquipmentController.java b/wms-webapi/src/main/java/top/wms/admin/controller/equipment/EquipmentController.java new file mode 100644 index 0000000..7047d0d --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/equipment/EquipmentController.java @@ -0,0 +1,35 @@ +package top.ysoft.admin.controller.equipment; + +import lombok.RequiredArgsConstructor; +import top.continew.starter.extension.crud.enums.Api; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.equipment.model.query.EquipmentQuery; +import top.ysoft.admin.equipment.model.req.EquipmentReq; +import top.ysoft.admin.equipment.model.resp.EquipmentDetailResp; +import top.ysoft.admin.equipment.model.resp.EquipmentResp; +import top.ysoft.admin.equipment.service.EquipmentService; + +import java.util.List; + +/** + * 设备信息管理 API + * + * @author zc + * @since 2025/04/09 15:20 + */ +@Tag(name = "设备信息管理 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/system/equipment", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, + Api.EXPORT}) +public class EquipmentController extends BaseController { + + @GetMapping(value = "/getEquipmentNameList") + public List getEquipmentNameList(EquipmentReq equipmentReq) { + return baseService.getEquipmentNameList(equipmentReq); + } +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/equipment/PeopleEquipmentController.java b/wms-webapi/src/main/java/top/wms/admin/controller/equipment/PeopleEquipmentController.java new file mode 100644 index 0000000..dea1085 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/equipment/PeopleEquipmentController.java @@ -0,0 +1,26 @@ +package top.ysoft.admin.controller.equipment; + +import top.continew.starter.extension.crud.enums.Api; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; + +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.system.model.query.PeopleEquipmentQuery; +import top.ysoft.admin.system.model.req.PeopleEquipmentReq; +import top.ysoft.admin.system.model.resp.PeopleEquipmentResp; +import top.ysoft.admin.system.service.PeopleEquipmentService; + +/** + * 人员设备下发信息管理 API + * + * @author zc + * @since 2025/03/27 14:59 + */ +@Tag(name = "人员设备下发信息管理 API") +@RestController +@CrudRequestMapping(value = "/system/peopleEquipment", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, + Api.EXPORT}) +public class PeopleEquipmentController extends BaseController {} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/equipment/ProductController.java b/wms-webapi/src/main/java/top/wms/admin/controller/equipment/ProductController.java new file mode 100644 index 0000000..9a8e49e --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/equipment/ProductController.java @@ -0,0 +1,37 @@ +package top.ysoft.admin.controller.equipment; + +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.equipment.model.query.ProductQuery; +import top.ysoft.admin.equipment.model.req.ProductReq; +import top.ysoft.admin.equipment.model.resp.ProductResp; +import top.ysoft.admin.equipment.service.ProductService; + +import java.util.List; + +/** + * 产品信息管理 API + * + * @author zc + * @since 2025/05/26 15:18 + */ +@Tag(name = "产品信息管理 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/equipment/product", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, + Api.EXPORT}) +public class ProductController extends BaseController { + + private final ProductService productService; + + @GetMapping(value = "/getProductNameList") + public List getProductNameList() { + return productService.getProductNameList(); + } +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/monitor/OnlineUserController.java b/wms-webapi/src/main/java/top/wms/admin/controller/monitor/OnlineUserController.java new file mode 100644 index 0000000..d7f9b60 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/monitor/OnlineUserController.java @@ -0,0 +1,49 @@ +package top.ysoft.admin.controller.monitor; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.stp.StpUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import top.ysoft.admin.auth.model.query.OnlineUserQuery; +import top.ysoft.admin.auth.model.resp.OnlineUserResp; +import top.ysoft.admin.auth.service.OnlineUserService; +import top.continew.starter.core.validation.CheckUtils; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; + +/** + * 在线用户 API + * + * @author Charles7c + * @since 2023/1/20 21:51 + */ +@Tag(name = "在线用户 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/monitor/online") +public class OnlineUserController { + + private final OnlineUserService baseService; + + @Operation(summary = "分页查询列表", description = "分页查询列表") + @SaCheckPermission("monitor:online:list") + @GetMapping + public PageResp page(OnlineUserQuery query, @Validated PageQuery pageQuery) { + return baseService.page(query, pageQuery); + } + + @Operation(summary = "强退在线用户", description = "强退在线用户") + @Parameter(name = "token", description = "令牌", example = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjEsInJuU3RyIjoiTUd6djdyOVFoeHEwdVFqdFAzV3M5YjVJRzh4YjZPSEUifQ.7q7U3ouoN7WPhH2kUEM7vPe5KF3G_qavSG-vRgIxKvE", in = ParameterIn.PATH) + @SaCheckPermission("monitor:online:kickout") + @DeleteMapping("/{token}") + public void kickout(@PathVariable String token) { + String currentToken = StpUtil.getTokenValue(); + CheckUtils.throwIfEqual(token, currentToken, "不能强退自己"); + StpUtil.kickoutByTokenValue(token); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/open/AppController.java b/wms-webapi/src/main/java/top/wms/admin/controller/open/AppController.java new file mode 100644 index 0000000..17fa6ae --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/open/AppController.java @@ -0,0 +1,51 @@ +package top.ysoft.admin.controller.open; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.open.model.query.AppQuery; +import top.ysoft.admin.open.model.req.AppReq; +import top.ysoft.admin.open.model.resp.AppDetailResp; +import top.ysoft.admin.open.model.resp.AppResp; +import top.ysoft.admin.open.model.resp.AppSecretResp; +import top.ysoft.admin.open.service.AppService; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; + +/** + * 应用管理 API + * + * @author chengzi + * @author Charles7c + * @since 2024/10/17 16:03 + */ +@Tag(name = "应用管理 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/open/app", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}) +public class AppController extends BaseController { + + @Operation(summary = "获取密钥", description = "获取应用密钥") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("open:app:secret") + @GetMapping("/{id}/secret") + public AppSecretResp getSecret(@PathVariable Long id) { + return baseService.getSecret(id); + } + + @Operation(summary = "重置密钥", description = "重置应用密钥") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("open:app:resetSecret") + @PatchMapping("/{id}/secret") + public void resetSecret(@PathVariable Long id) { + baseService.resetSecret(id); + } +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/peopleBranch/BranchController.java b/wms-webapi/src/main/java/top/wms/admin/controller/peopleBranch/BranchController.java new file mode 100644 index 0000000..ef1d5c1 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/peopleBranch/BranchController.java @@ -0,0 +1,82 @@ +package top.ysoft.admin.controller.peopleBranch; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.lang.tree.Tree; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import top.continew.starter.log.annotation.Log; +import top.ysoft.admin.peopleBranch.model.query.BranchQuery; +import top.ysoft.admin.peopleBranch.model.req.BranchReq; +import top.ysoft.admin.peopleBranch.model.resp.BranchResp; +import top.ysoft.admin.peopleBranch.service.BranchService; + +import java.util.List; + +/** + * 部门管理管理 API + * + * @author zc + * @since 2025/03/19 17:46 + */ +@Tag(name = "部门管理管理 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/sysPeople/branch") +public class BranchController { + + private final BranchService branchService; + + /** + * 查询部门树形结构 + * + * @param branchQuery + * @return + */ + @Log(ignore = true) + @SaCheckPermission("sysPeople:branch:tree") + @GetMapping("/tree") + public List> selectBranchTree(BranchQuery branchQuery) { + return branchService.selectBranchTree(branchQuery); + } + + /** + * 获取部门管理详细信息 + */ + @SaCheckPermission("sysPeople:branch:query") + @GetMapping(value = "/{id}") + public BranchResp getInfo(@PathVariable("id") String id) { + return branchService.selectBranchById(id); + } + + /** + * 新增部门管理 + */ + @SaCheckPermission("sysPeople:branch:add") + @Log(module = "新增部门") + @PostMapping + public String add(@RequestBody BranchReq branchReq) { + return branchService.insertBranch(branchReq); + } + + /** + * 修改部门管理 + */ + @SaCheckPermission("sysPeople:branch:edit") + @Log(module = "修改部门") + @PutMapping("/{id}") + public void edit(@PathVariable("id") String id, @RequestBody BranchReq branchReq) { + branchService.updateBranch(branchReq, id); + } + + /** + * 删除部门管理 + */ + @SaCheckPermission("sysPeople:branch:remove") + @Log(module = "删除部门") + @DeleteMapping("/{id}") + public void remove(@PathVariable("id") String id) { + branchService.deleteBranchById(id); + } + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/peopleBranch/PeopleController.java b/wms-webapi/src/main/java/top/wms/admin/controller/peopleBranch/PeopleController.java new file mode 100644 index 0000000..24bbc2e --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/peopleBranch/PeopleController.java @@ -0,0 +1,198 @@ +package top.ysoft.admin.controller.peopleBranch; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.StrUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.log.annotation.Log; +import top.continew.starter.web.model.R; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.consume.model.req.PeopleDepositImportReq; +import top.ysoft.admin.consume.model.resp.PeopleDepositImportParseResp; +import top.ysoft.admin.peopleBranch.model.query.PeopleQuery; +import top.ysoft.admin.peopleBranch.model.req.PeopleImportReq; +import top.ysoft.admin.peopleBranch.model.req.PeopleReq; +import top.ysoft.admin.peopleBranch.model.resp.ConsumePeopleResp; +import top.ysoft.admin.peopleBranch.model.resp.PeopleImportParseResp; +import top.ysoft.admin.peopleBranch.model.resp.PeopleImportResp; +import top.ysoft.admin.peopleBranch.model.resp.PeopleResp; +import top.ysoft.admin.peopleBranch.service.PeopleService; +import top.ysoft.admin.system.model.req.user.UserImportReq; +import top.ysoft.admin.system.model.resp.user.UserImportResp; +import top.ysoft.admin.system.service.ImportExcelService; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * 人员管理管理 API + * + * @author zc + * @since 2025/03/21 18:10 + */ +@Tag(name = "人员管理管理 API") +@RequiredArgsConstructor +@RestController +@Slf4j +@CrudRequestMapping(value = "/sysPeople/people", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.EXPORT}) +public class PeopleController extends BaseController { + + private final ImportExcelService importExcelService; + + @GetMapping(value = "/getPeopleNameList") + public List getPeopleNameList(PeopleQuery peopleQuery) { + return baseService.getPeopleNameList(peopleQuery); + } + + @GetMapping("/down/{ids}") + public R down(@PathVariable Long[] ids) { + String failNames = baseService.down(ids); + if (StrUtil.isNotBlank(failNames)) { + return R.fail("500", "存在失败记录,人员名称:" + failNames); + } + return R.ok(); + } + + @DeleteMapping("/{ids}") + public R del(@PathVariable Long[] ids) { + String failNames = baseService.del(Arrays.asList(ids)); + if (StrUtil.isNotBlank(failNames)) { + return R.fail("500", "设备删除失败,人员名称:" + failNames); + } + return R.ok(); + } + + @Log(ignore = true) + @Operation(summary = "下载导入模板", description = "下载导入模板") + @SaCheckPermission("sysPeople:people:import") + @GetMapping(value = "/import/template", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public void downloadImportTemplate(HttpServletResponse response) throws IOException { + importExcelService.downloadImportTemplate(response, "people.xlsx", "人员导入模板.xlsx"); + } + + //导入相关 + @Log(ignore = true) + @Operation(summary = "解析导入数据", description = "解析导入数据") + @SaCheckPermission("sysPeople:people:import") + @PostMapping("/import/parse") + public PeopleImportParseResp parseImport(@NotNull(message = "文件不能为空") MultipartFile file) { + ValidationUtils.throwIf(file::isEmpty, "文件不能为空"); + return baseService.parseImport(file); + } + + @Log(ignore = true) + @Operation(summary = "导入数据", description = "导入数据") + @SaCheckPermission("sysPeople:people:import") + @PostMapping(value = "/import") + public PeopleImportResp importUser(@Validated @RequestBody PeopleImportReq req) { + return baseService.importPeople(req); + } + + + //消费相关 + @Log(ignore = true) + @Operation(summary = "分页查询人员消费列表", description = "分页查询列表") + @GetMapping("/pageConsume") + @SaCheckPermission("consume:PeopleDeposit:refund") + public PageResp pageConsume(PeopleQuery query, @Validated PageQuery pageQuery) { + return baseService.pageConsume(query, pageQuery); + } + + + @Log(ignore = true) + @Operation(summary = "导出人员充值列表", description = "导出人员充值列表") + @GetMapping("/exportConsume") + @SaCheckPermission("consume:PeopleDeposit:export") + public void exportConsume(PeopleQuery query, HttpServletResponse response) { + baseService.exportConsume(query, response); + } + + @Operation(summary = "补贴", description = "补贴") + @PostMapping("/subsidy") + @SaCheckPermission("consume:PeopleDeposit:subsidy") + public R subsidy(@RequestBody PeopleReq req) { + baseService.subsidy(req); + return R.ok(); + } + + @Operation(summary = "充值", description = "充值") + @PostMapping("/deposit") + @SaCheckPermission("consume:PeopleDeposit:deposit") + public R deposit(@RequestBody PeopleReq req) { + baseService.deposit(req); + return R.ok(); + } + + @Operation(summary = "退款", description = "退款") + @PostMapping("/refund") + @SaCheckPermission("consume:PeopleDeposit:refund") + public R refund(@RequestBody PeopleReq req) { + baseService.refund(req); + return R.ok(); + } + + @Operation(summary = "消费冻结", description = "消费冻结") + @PostMapping("/freeze") + @SaCheckPermission("consume:PeopleDeposit:freeze") + public R freeze(@RequestBody List ids) { + baseService.freeze(ids); + return R.ok(); + } + + @Operation(summary = "消费解冻", description = "消费解冻") + @PostMapping("/unFreeze") + @SaCheckPermission("consume:PeopleDeposit:unfreeze") + public R unFreeze(@RequestBody List ids) { + baseService.unFreeze(ids); + return R.ok(); + } + + @Operation(summary = "消费一键清零", description = "消费一键清零") + @PostMapping("/resetMoney") + @SaCheckPermission("consume:PeopleDeposit:import") + public R resetMoney(@RequestBody PeopleReq req) { + baseService.resetMoney(req); + return R.ok(); + } + + @Log(ignore = true) + @Operation(summary = "下载人员充值导入模板", description = "下载人员充值导入模板") + @SaCheckPermission("consume:PeopleDeposit:import") + @GetMapping(value = "/consume/import/template", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public void downloadConsumeImportTemplate(HttpServletResponse response) throws IOException { + baseService.downloadConsumeImportTemplate(response); + } + + @Log(ignore = true) + @Operation(summary = "解析人员充值导入数据", description = "解析人员充值导入数据") + @SaCheckPermission("consume:PeopleDeposit:import") + @PostMapping("/consume/import/parse") + public PeopleDepositImportParseResp parseConsumeImport(@NotNull(message = "文件不能为空") MultipartFile file) { + ValidationUtils.throwIf(file::isEmpty, "文件不能为空"); + return baseService.parseConsumeImport(file); + } + + + @Operation(summary = "导入数据", description = "导入数据") + @SaCheckPermission("consume:PeopleDeposit:import") + @PostMapping(value = "/consume/import") + public UserImportResp importUser(@Validated @RequestBody PeopleDepositImportReq req) { + return baseService.importConsumePeople(req); + } + +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/peopleDownRecord/PeopleDownController.java b/wms-webapi/src/main/java/top/wms/admin/controller/peopleDownRecord/PeopleDownController.java new file mode 100644 index 0000000..93eb4e0 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/peopleDownRecord/PeopleDownController.java @@ -0,0 +1,38 @@ +package top.ysoft.admin.controller.peopleDownRecord; + +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; +import top.continew.starter.web.model.R; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.common.enums.OperTypeEnum; +import top.ysoft.admin.peopleDownRecord.model.query.PeopleDownQuery; +import top.ysoft.admin.peopleDownRecord.model.req.PeopleDownReq; +import top.ysoft.admin.peopleDownRecord.model.resp.PeopleDownResp; +import top.ysoft.admin.peopleDownRecord.service.PeopleDownService; +import top.ysoft.admin.yfApi.service.ISysYFService; + +@Tag(name = "下发管理 API") +@RequiredArgsConstructor +@RestController +@Slf4j +@CrudRequestMapping(value = "/sysDownRecord/record", api = {Api.PAGE, Api.DETAIL, Api.UPDATE, Api.DELETE, Api.EXPORT}) +public class PeopleDownController extends BaseController { + + private final ISysYFService sysYFService; + + @GetMapping("/down/{equipmentId}/{ruleId}/{peopleId}") + public R down(@PathVariable Long equipmentId, @PathVariable Long ruleId, @PathVariable Long peopleId) { + Boolean success = sysYFService.authDevice(peopleId, equipmentId, ruleId, OperTypeEnum.DOWN.getValue()); + if (!success) { + return R.fail("500", "下发设备失败"); + } + return R.ok(); + } + +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/rule/EmpowerRecordController.java b/wms-webapi/src/main/java/top/wms/admin/controller/rule/EmpowerRecordController.java new file mode 100644 index 0000000..eac7db2 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/rule/EmpowerRecordController.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.controller.rule; + +import top.continew.starter.extension.crud.enums.Api; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; + +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.rule.model.query.EmpowerRecordQuery; +import top.ysoft.admin.rule.model.req.EmpowerRecordReq; +import top.ysoft.admin.rule.model.resp.EmpowerRecordResp; +import top.ysoft.admin.rule.service.EmpowerRecordService; + +/** + * 授权记录管理 API + * + * @author zc + * @since 2025/04/08 23:08 + */ +@Tag(name = "授权记录管理 API") +@RestController +@CrudRequestMapping(value = "/rule/empowerRecord", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, + Api.EXPORT}) +public class EmpowerRecordController extends BaseController { + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/rule/PeopleRecordController.java b/wms-webapi/src/main/java/top/wms/admin/controller/rule/PeopleRecordController.java new file mode 100644 index 0000000..99635f8 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/rule/PeopleRecordController.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.controller.rule; + +import top.continew.starter.extension.crud.enums.Api; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; + +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.rule.model.query.PeopleRecordQuery; +import top.ysoft.admin.rule.model.req.PeopleRecordReq; +import top.ysoft.admin.rule.model.resp.PeopleRecordResp; +import top.ysoft.admin.rule.service.PeopleRecordService; + +/** + * 人员识别记录管理 API + * + * @author zc + * @since 2025/04/08 23:06 + */ +@Tag(name = "人员识别记录管理 API") +@RestController +@CrudRequestMapping(value = "/rule/peopleRecord", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, + Api.EXPORT}) +public class PeopleRecordController extends BaseController { + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/rule/RuleController.java b/wms-webapi/src/main/java/top/wms/admin/controller/rule/RuleController.java new file mode 100644 index 0000000..cebd3f1 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/rule/RuleController.java @@ -0,0 +1,53 @@ +package top.ysoft.admin.controller.rule; + +import lombok.RequiredArgsConstructor; + +import top.continew.starter.extension.crud.enums.Api; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; + +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import top.continew.starter.web.model.R; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.rule.model.query.RuleQuery; +import top.ysoft.admin.rule.model.req.RuleReq; +import top.ysoft.admin.rule.model.resp.RuleResp; +import top.ysoft.admin.rule.service.RuleService; + +import java.util.List; + +/** + * 通行规则管理 API + * + * @author zc + * @since 2025/04/07 14:20 + */ +@Tag(name = "通行规则管理 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/rule/rule", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}) +public class RuleController extends BaseController { + + private final RuleService ruleService; + + @GetMapping(value = "/getRuleNameList") + public List getRuleNameList(RuleReq RuleReq) { + return ruleService.getRuleNameList(RuleReq); + } + + @PostMapping("/add1") + public R addRule1(@RequestBody RuleReq ruleReq) { + String result = baseService.addRule1(ruleReq); + return R.ok(result); + } + + @PutMapping("/update1/{id}") + public R updateRule1(@PathVariable("id") Long id, @RequestBody RuleReq ruleReq) { + String result = baseService.updateRule1(ruleReq, id); + return R.ok(result); + } + +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/schedule/DemoEnvironmentJob.java b/wms-webapi/src/main/java/top/wms/admin/controller/schedule/DemoEnvironmentJob.java new file mode 100644 index 0000000..78b8a05 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/schedule/DemoEnvironmentJob.java @@ -0,0 +1,160 @@ +package top.ysoft.admin.controller.schedule; + +import cn.hutool.core.util.StrUtil; +import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; +import com.aizuda.snailjob.common.log.SnailJobLog; +import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.open.mapper.AppMapper; +import top.ysoft.admin.open.model.entity.AppDO; +import top.ysoft.admin.system.mapper.*; +import top.ysoft.admin.system.model.entity.*; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.constant.StringConstants; + +import java.util.List; +import java.util.function.BooleanSupplier; + +/** + * 演示环境任务(任务示例) + * + * @author Charles7c + * @since 2024/8/4 15:30 + */ +@Component +@RequiredArgsConstructor +public class DemoEnvironmentJob { + + private final DictItemMapper dictItemMapper; + private final DictMapper dictMapper; + private final StorageMapper storageMapper; + private final NoticeMapper noticeMapper; + private final MessageMapper messageMapper; + private final MessageUserMapper messageUserMapper; + private final UserMapper userMapper; + private final UserRoleMapper userRoleMapper; + private final UserSocialMapper userSocialMapper; + private final RoleMapper roleMapper; + private final RoleDeptMapper roleDeptMapper; + private final RoleMenuMapper roleMenuMapper; + private final MenuMapper menuMapper; + private final DeptMapper deptMapper; + + private final AppMapper appMapper; + private final ClientMapper clientsMapper; + + private static final Long DELETE_FLAG = 10000L; + private static final Long MESSAGE_FLAG = 0L; + private static final List USER_FLAG = List + .of(1L, 547889293968801822L, 547889293968801823L, 547889293968801824L, 547889293968801825L, 547889293968801826L, 547889293968801827L, 547889293968801828L, 547889293968801829L, 547889293968801830L, 547889293968801831L); + private static final List ROLE_FLAG = List.of(1L, 547888897925840927L, 547888897925840928L); + private static final Long DEPT_FLAG = 547887852587843611L; + + /** + * 重置演示环境数据 + */ + @JobExecutor(name = "ResetEnvironmentData") + @Transactional(rollbackFor = Exception.class) + public void resetEnvironmentData() { + try { + SnailJobLog.REMOTE.info("定时任务 [重置演示环境数据] 开始执行。"); + // 检测待清理数据 + SnailJobLog.REMOTE.info("开始检测演示环境待清理数据项,请稍候..."); + Long dictItemCount = dictItemMapper.lambdaQuery().gt(DictItemDO::getId, DELETE_FLAG).count(); + this.log(dictItemCount, "字典项"); + Long dictCount = dictMapper.lambdaQuery().gt(DictDO::getId, DELETE_FLAG).count(); + this.log(dictCount, "字典"); + Long storageCount = storageMapper.lambdaQuery().gt(StorageDO::getId, DELETE_FLAG).count(); + this.log(storageCount, "存储"); + Long noticeCount = noticeMapper.lambdaQuery().gt(NoticeDO::getId, DELETE_FLAG).count(); + this.log(noticeCount, "公告"); + Long messageCount = messageMapper.lambdaQuery().count(); + this.log(messageCount, "通知"); + Long userCount = userMapper.lambdaQuery().notIn(UserDO::getId, USER_FLAG).count(); + this.log(userCount, "用户"); + Long roleCount = roleMapper.lambdaQuery().notIn(RoleDO::getId, ROLE_FLAG).count(); + this.log(roleCount, "角色"); + Long menuCount = menuMapper.lambdaQuery().gt(MenuDO::getId, DELETE_FLAG).count(); + this.log(menuCount, "菜单"); + Long deptCount = deptMapper.lambdaQuery().gt(DeptDO::getId, DEPT_FLAG).count(); + this.log(deptCount, "部门"); + Long appCount = appMapper.lambdaQuery().gt(AppDO::getId, DELETE_FLAG).count(); + this.log(appCount, "应用"); + Long clientCount = clientsMapper.lambdaQuery().gt(ClientDO::getId, DELETE_FLAG).count(); + this.log(clientCount, "终端"); + InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().blockAttack(true).build()); + SnailJobLog.REMOTE.info("演示环境待清理数据项检测完成,开始执行清理。"); + // 清理关联数据 + messageUserMapper.lambdaUpdate().gt(MessageUserDO::getMessageId, MESSAGE_FLAG).remove(); + userRoleMapper.lambdaUpdate().notIn(UserRoleDO::getRoleId, ROLE_FLAG).remove(); + userRoleMapper.lambdaUpdate().notIn(UserRoleDO::getUserId, USER_FLAG).remove(); + roleDeptMapper.lambdaUpdate().notIn(RoleDeptDO::getRoleId, ROLE_FLAG).remove(); + roleMenuMapper.lambdaUpdate().notIn(RoleMenuDO::getRoleId, ROLE_FLAG).remove(); + userSocialMapper.lambdaUpdate().notIn(UserSocialDO::getUserId, USER_FLAG).remove(); + // 清理具体数据 + this.clean(dictItemCount, "字典项", null, () -> dictItemMapper.lambdaUpdate() + .gt(DictItemDO::getId, DELETE_FLAG) + .remove()); + this.clean(dictCount, "字典", CacheConstants.DICT_KEY_PREFIX, () -> dictMapper.lambdaUpdate() + .gt(DictDO::getId, DELETE_FLAG) + .remove()); + this.clean(storageCount, "存储", null, () -> storageMapper.lambdaUpdate() + .gt(StorageDO::getId, DELETE_FLAG) + .remove()); + this.clean(noticeCount, "公告", null, () -> noticeMapper.lambdaUpdate() + .gt(NoticeDO::getId, DELETE_FLAG) + .remove()); + this.clean(messageCount, "通知", null, () -> messageMapper.lambdaUpdate() + .gt(MessageDO::getId, MESSAGE_FLAG) + .remove()); + this.clean(userCount, "用户", null, () -> userMapper.lambdaUpdate().notIn(UserDO::getId, USER_FLAG).remove()); + this.clean(roleCount, "角色", null, () -> roleMapper.lambdaUpdate().notIn(RoleDO::getId, ROLE_FLAG).remove()); + this.clean(menuCount, "菜单", CacheConstants.ROLE_MENU_KEY_PREFIX, () -> menuMapper.lambdaUpdate() + .gt(MenuDO::getId, DELETE_FLAG) + .remove()); + this.clean(deptCount, "部门", null, () -> deptMapper.lambdaUpdate().gt(DeptDO::getId, DEPT_FLAG).remove()); + this.clean(appCount, "应用", null, () -> appMapper.lambdaUpdate().gt(AppDO::getId, DEPT_FLAG).remove()); + this.clean(clientCount, "终端", null, () -> clientsMapper.lambdaUpdate() + .gt(ClientDO::getId, DEPT_FLAG) + .remove()); + SnailJobLog.REMOTE.info("演示环境数据已清理完成。"); + SnailJobLog.REMOTE.info("定时任务 [重置演示环境数据] 执行结束。"); + } finally { + InterceptorIgnoreHelper.clearIgnoreStrategy(); + } + } + + /** + * 输出日志 + * + * @param count 待清理数据项数量 + * @param resource 资源名称 + */ + private void log(Long count, String resource) { + if (count > 0) { + SnailJobLog.REMOTE.info("检测到 [{}] 待清理数据项:{}条", resource, count); + } + } + + /** + * 清理数据 + * + * @param count 待清理数据项数量 + * @param resource 资源名称 + * @param cacheKey 缓存键 + * @param supplier 清理数据项函数 + */ + private void clean(Long count, String resource, String cacheKey, BooleanSupplier supplier) { + if (count > 0 && supplier.getAsBoolean()) { + SnailJobLog.REMOTE.info("[{}] 数据项清理完成。", resource); + if (StrUtil.isNotBlank(cacheKey)) { + RedisUtils.deleteByPattern(cacheKey + StringConstants.ASTERISK); + SnailJobLog.REMOTE.info("[{}] 数据项缓存清理完成。", resource); + } + } + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/schedule/JobController.java b/wms-webapi/src/main/java/top/wms/admin/controller/schedule/JobController.java new file mode 100644 index 0000000..777976f --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/schedule/JobController.java @@ -0,0 +1,90 @@ +package top.ysoft.admin.controller.schedule; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import top.ysoft.admin.schedule.model.query.JobQuery; +import top.ysoft.admin.schedule.model.req.JobReq; +import top.ysoft.admin.schedule.model.req.JobStatusReq; +import top.ysoft.admin.schedule.model.resp.JobResp; +import top.ysoft.admin.schedule.service.JobService; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.extension.crud.validation.CrudValidationGroup; +import top.continew.starter.log.annotation.Log; + +import java.util.List; + +/** + * 任务 API + * + * @author KAI + * @author Charles7c + * @since 2024/6/25 22:24 + */ +@Tag(name = " 任务 API") +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/schedule/job") +public class JobController { + + private final JobService baseService; + + @Operation(summary = "分页查询任务列表", description = "分页查询任务列表") + @SaCheckPermission("schedule:job:list") + @GetMapping + public PageResp page(JobQuery query) { + return baseService.page(query); + } + + @Operation(summary = "新增任务", description = "新增任务") + @SaCheckPermission("schedule:job:add") + @PostMapping + public void add(@Validated(CrudValidationGroup.Add.class) @RequestBody JobReq req) { + baseService.add(req); + } + + @Operation(summary = "修改任务", description = "修改任务") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("schedule:job:update") + @PutMapping("/{id}") + public void update(@Validated(CrudValidationGroup.Update.class) @RequestBody JobReq req, @PathVariable Long id) { + baseService.update(req, id); + } + + @Operation(summary = "修改任务状态", description = "修改任务状态") + @SaCheckPermission("schedule:job:update") + @PatchMapping("/{id}/status") + public void updateStatus(@Validated @RequestBody JobStatusReq req, @PathVariable Long id) { + baseService.updateStatus(req, id); + } + + @Operation(summary = "删除任务", description = "删除任务") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("schedule:job:delete") + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + baseService.delete(id); + } + + @Operation(summary = "执行任务", description = "执行任务") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("schedule:job:trigger") + @PostMapping("/trigger/{id}") + public void trigger(@PathVariable Long id) { + baseService.trigger(id); + } + + @Log(ignore = true) + @Operation(summary = "查询任务分组列表", description = "查询任务分组列表") + @SaCheckPermission("schedule:job:list") + @GetMapping("/group") + public List listGroup() { + return baseService.listGroup(); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/schedule/JobLogController.java b/wms-webapi/src/main/java/top/wms/admin/controller/schedule/JobLogController.java new file mode 100644 index 0000000..357b120 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/schedule/JobLogController.java @@ -0,0 +1,74 @@ +package top.ysoft.admin.controller.schedule; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import top.ysoft.admin.schedule.model.JobInstanceLogPageResult; +import top.ysoft.admin.schedule.model.query.JobInstanceLogQuery; +import top.ysoft.admin.schedule.model.query.JobInstanceQuery; +import top.ysoft.admin.schedule.model.query.JobLogQuery; +import top.ysoft.admin.schedule.model.resp.JobInstanceResp; +import top.ysoft.admin.schedule.model.resp.JobLogResp; +import top.ysoft.admin.schedule.service.JobLogService; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.util.List; + +/** + * 任务日志 API + * + * @author KAI + * @author Charles7c + * @since 2024/6/27 22:24 + */ +@Tag(name = " 任务日志 API") +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/schedule/log") +public class JobLogController { + + private final JobLogService baseService; + + @Operation(summary = "分页查询任务日志列表", description = "分页查询任务日志列表") + @SaCheckPermission("schedule:log:list") + @GetMapping + public PageResp page(JobLogQuery query) { + return baseService.page(query); + } + + @Operation(summary = "停止任务", description = "停止任务") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("schedule:log:stop") + @PostMapping("/stop/{id}") + public void stop(@PathVariable Long id) { + baseService.stop(id); + } + + @Operation(summary = "重试任务", description = "重试任务") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("schedule:log:retry") + @PostMapping("/retry/{id}") + public void retry(@PathVariable Long id) { + baseService.retry(id); + } + + @Operation(summary = "查询任务实例列表", description = "查询任务实例列表") + @SaCheckPermission("schedule:log:list") + @GetMapping("/instance") + public List listInstance(JobInstanceQuery query) { + return baseService.listInstance(query); + } + + @Operation(summary = "分页查询任务实例日志列表", description = "分页查询任务实例日志列表") + @SaCheckPermission("schedule:log:list") + @GetMapping("/instance/log") + public JobInstanceLogPageResult pageInstanceLog(JobInstanceLogQuery query) { + return baseService.pageInstanceLog(query); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/space/PointController.java b/wms-webapi/src/main/java/top/wms/admin/controller/space/PointController.java new file mode 100644 index 0000000..b77d0df --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/space/PointController.java @@ -0,0 +1,24 @@ +package top.ysoft.admin.controller.space; + +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.RestController; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.space.model.query.PointQuery; +import top.ysoft.admin.space.model.req.PointReq; +import top.ysoft.admin.space.model.resp.PointResp; +import top.ysoft.admin.space.service.PointService; + +/** + * 点位管理管理 API + * + * @author zc + * @since 2025/04/03 15:01 + */ +@Tag(name = "点位管理管理 API") +@RestController +@CrudRequestMapping(value = "/space/point", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}) +public class PointController extends BaseController { + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/space/SpaceController.java b/wms-webapi/src/main/java/top/wms/admin/controller/space/SpaceController.java new file mode 100644 index 0000000..db98603 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/space/SpaceController.java @@ -0,0 +1,28 @@ +package top.ysoft.admin.controller.space; + +import top.continew.starter.extension.crud.enums.Api; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; + +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.space.model.query.SpaceQuery; +import top.ysoft.admin.space.model.req.SpaceReq; +import top.ysoft.admin.space.model.resp.SpaceResp; +import top.ysoft.admin.space.model.resp.SpaceResp; +import top.ysoft.admin.space.service.SpaceService; + +/** + * 空间管理管理 API + * + * @author zc + * @since 2025/04/03 10:47 + */ +@Tag(name = "空间管理管理 API") +@RestController +@CrudRequestMapping(value = "/space/space", api = {Api.TREE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}) +public class SpaceController extends BaseController { + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/ClientController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/ClientController.java new file mode 100644 index 0000000..91a79f9 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/ClientController.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.controller.system; + +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.RestController; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.system.model.query.ClientQuery; +import top.ysoft.admin.system.model.req.ClientReq; +import top.ysoft.admin.system.model.resp.ClientResp; +import top.ysoft.admin.system.service.ClientService; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; + +/** + * 终端管理 API + * + * @author KAI + * @since 2024/12/03 16:04 + */ +@Tag(name = "终端管理 API") +@RestController +@CrudRequestMapping(value = "/system/client", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE}) +public class ClientController extends BaseController { +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/ConfigController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/ConfigController.java new file mode 100644 index 0000000..6f9fd92 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/ConfigController.java @@ -0,0 +1,27 @@ +package top.ysoft.admin.controller.system; + +import top.continew.starter.extension.crud.enums.Api; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; + +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.system.model.query.ConfigQuery; +import top.ysoft.admin.system.model.req.ConfigReq; +import top.ysoft.admin.system.model.resp.ConfigResp; +import top.ysoft.admin.system.service.ConfigService; + +/** + * 参数配置管理 API + * + * @author zc + * @since 2025/12/30 12:44 + */ +@Tag(name = "参数配置管理 API") +@RestController +@CrudRequestMapping(value = "/system/config", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}) +public class ConfigController extends BaseController { + +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/DeptController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/DeptController.java new file mode 100644 index 0000000..a57f53c --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/DeptController.java @@ -0,0 +1,23 @@ +package top.ysoft.admin.controller.system; + +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.RestController; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.system.model.query.DeptQuery; +import top.ysoft.admin.system.model.req.DeptReq; +import top.ysoft.admin.system.model.resp.DeptResp; +import top.ysoft.admin.system.service.DeptService; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; + +/** + * 部门管理 API + * + * @author Charles7c + * @since 2023/1/22 17:50 + */ +@Tag(name = "部门管理 API") +@RestController +@CrudRequestMapping(value = "/system/dept", api = {Api.TREE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}) +public class DeptController extends BaseController { +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/DictController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/DictController.java new file mode 100644 index 0000000..b7e8642 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/DictController.java @@ -0,0 +1,36 @@ +package top.ysoft.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.system.model.query.DictQuery; +import top.ysoft.admin.system.model.req.DictReq; +import top.ysoft.admin.system.model.resp.DictResp; +import top.ysoft.admin.system.service.DictService; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; + +/** + * 字典管理 API + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Tag(name = "字典管理 API") +@RestController +@CrudRequestMapping(value = "/system/dict", api = {Api.LIST, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE}) +public class DictController extends BaseController { + + @Operation(summary = "清除缓存", description = "清除缓存") + @SaCheckPermission("system:dict:clearCache") + @DeleteMapping("/cache/{code}") + public void clearCache(@PathVariable String code) { + RedisUtils.deleteByPattern(CacheConstants.DICT_KEY_PREFIX + code); + } +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/DictItemController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/DictItemController.java new file mode 100644 index 0000000..7d8d0bb --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/DictItemController.java @@ -0,0 +1,25 @@ +package top.ysoft.admin.controller.system; + +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.RestController; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.system.model.query.DictItemQuery; +import top.ysoft.admin.system.model.req.DictItemReq; +import top.ysoft.admin.system.model.resp.DictItemResp; +import top.ysoft.admin.system.service.DictItemService; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; +import top.continew.starter.log.annotation.Log; + +/** + * 字典项管理 API + * + * @author Charles7c + * @since 2023/9/11 21:29 + */ +@Log(module = "字典管理") +@Tag(name = "字典项管理 API") +@RestController +@CrudRequestMapping(value = "/system/dict/item", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE}) +public class DictItemController extends BaseController { +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/FileController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/FileController.java new file mode 100644 index 0000000..aeab822 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/FileController.java @@ -0,0 +1,38 @@ +package top.ysoft.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.system.model.query.FileQuery; +import top.ysoft.admin.system.model.req.FileReq; +import top.ysoft.admin.system.model.resp.file.FileResp; +import top.ysoft.admin.system.model.resp.file.FileStatisticsResp; +import top.ysoft.admin.system.service.FileService; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; +import top.continew.starter.log.annotation.Log; + +/** + * 文件管理 API + * + * @author Charles7c + * @since 2023/12/23 10:38 + */ +@Tag(name = "文件管理 API") +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/system/file", api = {Api.PAGE, Api.UPDATE, Api.DELETE}) +public class FileController extends BaseController { + + @Log(ignore = true) + @Operation(summary = "查询文件资源统计", description = "查询文件资源统计") + @SaCheckPermission("system:file:list") + @GetMapping("/statistics") + public FileStatisticsResp statistics() { + return baseService.statistics(); + } +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/LogController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/LogController.java new file mode 100644 index 0000000..b2a1902 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/LogController.java @@ -0,0 +1,68 @@ +package top.ysoft.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.feiniaojin.gracefulresponse.api.ExcludeFromGracefulResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import top.ysoft.admin.system.model.query.LogQuery; +import top.ysoft.admin.system.model.resp.log.LogDetailResp; +import top.ysoft.admin.system.model.resp.log.LogResp; +import top.ysoft.admin.system.service.LogService; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.query.SortQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; + +/** + * 系统日志 API + * + * @author Charles7c + * @since 2023/1/18 23:55 + */ +@Tag(name = "系统日志 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/system/log") +public class LogController { + + private final LogService baseService; + + @Operation(summary = "分页查询列表", description = "分页查询列表") + @SaCheckPermission("monitor:log:list") + @GetMapping + public PageResp page(LogQuery query, @Validated PageQuery pageQuery) { + return baseService.page(query, pageQuery); + } + + @Operation(summary = "查询详情", description = "查询详情") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("monitor:log:detail") + @GetMapping("/{id}") + public LogDetailResp get(@PathVariable Long id) { + return baseService.get(id); + } + + @ExcludeFromGracefulResponse + @Operation(summary = "导出登录日志", description = "导出登录日志") + @SaCheckPermission("monitor:log:export") + @GetMapping("/export/login") + public void exportLoginLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) { + baseService.exportLoginLog(query, sortQuery, response); + } + + @ExcludeFromGracefulResponse + @Operation(summary = "导出操作日志", description = "导出操作日志") + @SaCheckPermission("monitor:log:export") + @GetMapping("/export/operation") + public void exportOperationLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) { + baseService.exportOperationLog(query, sortQuery, response); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/MenuController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/MenuController.java new file mode 100644 index 0000000..5697126 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/MenuController.java @@ -0,0 +1,64 @@ +package top.ysoft.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RestController; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.system.model.query.MenuQuery; +import top.ysoft.admin.system.model.req.MenuReq; +import top.ysoft.admin.system.model.resp.MenuResp; +import top.ysoft.admin.system.service.MenuService; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.util.URLUtils; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.extension.crud.annotation.CrudApi; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; + +import java.lang.reflect.Method; + +/** + * 菜单管理 API + * + * @author Charles7c + * @since 2023/2/15 20:35 + */ +@Tag(name = "菜单管理 API") +@RestController +@CrudRequestMapping(value = "/system/menu", api = {Api.TREE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE}) +public class MenuController extends BaseController { + + @Operation(summary = "清除缓存", description = "清除缓存") + @SaCheckPermission("system:menu:clearCache") + @DeleteMapping("/cache") + public void clearCache() { + RedisUtils.deleteByPattern(CacheConstants.ROLE_MENU_KEY_PREFIX + StringConstants.ASTERISK); + } + + @Override + public void preHandle(CrudApi crudApi, Object[] args, Method targetMethod, Class targetClass) throws Exception { + super.preHandle(crudApi, args, targetMethod, targetClass); + Api api = crudApi.value(); + if (!(Api.ADD.equals(api) || Api.UPDATE.equals(api))) { + return; + } + MenuReq req = (MenuReq)args[0]; + Boolean isExternal = ObjectUtil.defaultIfNull(req.getIsExternal(), false); + String path = req.getPath(); + ValidationUtils.throwIf(Boolean.TRUE.equals(isExternal) && !URLUtils + .isHttpUrl(path), "路由地址格式错误,请以 http:// 或 https:// 开头"); + // 非外链菜单参数修正 + if (Boolean.FALSE.equals(isExternal)) { + ValidationUtils.throwIf(URLUtils.isHttpUrl(path), "路由地址格式错误"); + req.setPath(StrUtil.isBlank(path) ? path : StrUtil.prependIfMissing(path, StringConstants.SLASH)); + req.setName(StrUtil.removePrefix(req.getName(), StringConstants.SLASH)); + req.setComponent(StrUtil.removePrefix(req.getComponent(), StringConstants.SLASH)); + } + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/MessageController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/MessageController.java new file mode 100644 index 0000000..cc88866 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/MessageController.java @@ -0,0 +1,65 @@ +package top.ysoft.admin.controller.system; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.system.model.query.MessageQuery; +import top.ysoft.admin.system.model.resp.message.MessageResp; +import top.ysoft.admin.system.model.resp.message.MessageUnreadResp; +import top.ysoft.admin.system.service.MessageService; +import top.ysoft.admin.system.service.MessageUserService; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; +import top.continew.starter.log.annotation.Log; + +import java.util.List; + +/** + * 消息管理 API + * + * @author Bull-BCLS + * @since 2023/10/15 19:05 + */ +@Tag(name = "消息管理 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/system/message") +public class MessageController { + + private final MessageService baseService; + private final MessageUserService messageUserService; + + @Operation(summary = "分页查询列表", description = "分页查询列表") + @GetMapping + public PageResp page(MessageQuery query, @Validated PageQuery pageQuery) { + query.setUserId(UserContextHolder.getUserId()); + return baseService.page(query, pageQuery); + } + + @Operation(summary = "删除数据", description = "删除数据") + @Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH) + @DeleteMapping("/{ids}") + public void delete(@PathVariable List ids) { + baseService.delete(ids); + } + + @Operation(summary = "标记已读", description = "将消息标记为已读状态") + @Parameter(name = "ids", description = "消息ID列表", example = "1,2", in = ParameterIn.QUERY) + @PatchMapping("/read") + public void readMessage(@RequestParam(required = false) List ids) { + messageUserService.readMessage(ids); + } + + @Log(ignore = true) + @Operation(summary = "查询未读消息数量", description = "查询当前用户的未读消息数量") + @Parameter(name = "isDetail", description = "是否查询详情", example = "true", in = ParameterIn.QUERY) + @GetMapping("/unread") + public MessageUnreadResp countUnreadMessage(@RequestParam(required = false) Boolean detail) { + return messageUserService.countUnreadMessageByUserId(UserContextHolder.getUserId(), detail); + } +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/NoticeController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/NoticeController.java new file mode 100644 index 0000000..e92b39c --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/NoticeController.java @@ -0,0 +1,50 @@ +package top.ysoft.admin.controller.system; + +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.RestController; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.system.enums.NoticeScopeEnum; +import top.ysoft.admin.system.model.query.NoticeQuery; +import top.ysoft.admin.system.model.req.NoticeReq; +import top.ysoft.admin.system.model.resp.NoticeDetailResp; +import top.ysoft.admin.system.model.resp.NoticeResp; +import top.ysoft.admin.system.service.NoticeService; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.extension.crud.annotation.CrudApi; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; + +import java.lang.reflect.Method; +import java.time.LocalDateTime; + +/** + * 公告管理 API + * + * @author Charles7c + * @since 2023/8/20 10:55 + */ +@Tag(name = "公告管理 API") +@RestController +@CrudRequestMapping(value = "/system/notice", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE}) +public class NoticeController extends BaseController { + + @Override + public void preHandle(CrudApi crudApi, Object[] args, Method targetMethod, Class targetClass) throws Exception { + super.preHandle(crudApi, args, targetMethod, targetClass); + Api api = crudApi.value(); + if (!(Api.ADD.equals(api) || Api.UPDATE.equals(api))) { + return; + } + NoticeReq req = (NoticeReq)args[0]; + // 校验生效时间 + LocalDateTime effectiveTime = req.getEffectiveTime(); + LocalDateTime terminateTime = req.getTerminateTime(); + if (null != effectiveTime && null != terminateTime) { + ValidationUtils.throwIf(terminateTime.isBefore(effectiveTime), "终止时间必须晚于生效时间"); + } + // 校验通知范围 + if (NoticeScopeEnum.USER.equals(req.getNoticeScope())) { + ValidationUtils.throwIfEmpty(req.getNoticeUsers(), "通知用户不能为空"); + } + } +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/OptionController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/OptionController.java new file mode 100644 index 0000000..94e4604 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/OptionController.java @@ -0,0 +1,53 @@ +package top.ysoft.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import top.ysoft.admin.system.model.query.OptionQuery; +import top.ysoft.admin.system.model.req.OptionReq; +import top.ysoft.admin.system.model.req.OptionResetValueReq; +import top.ysoft.admin.system.model.resp.OptionResp; +import top.ysoft.admin.system.service.OptionService; + +import java.util.List; + +/** + * 参数管理 API + * + * @author Bull-BCLS + * @since 2023/8/26 19:38 + */ +@Tag(name = "参数管理 API") +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/system/option") +public class OptionController { + + private final OptionService baseService; + + @Operation(summary = "查询参数列表", description = "查询参数列表") + @SaCheckPermission("system:config:list") + @GetMapping + public List list(@Validated OptionQuery query) { + return baseService.list(query); + } + + @Operation(summary = "修改参数", description = "修改参数") + @SaCheckPermission("system:config:update") + @PutMapping + public void update(@Valid @RequestBody List options) { + baseService.update(options); + } + + @Operation(summary = "重置参数", description = "重置参数") + @SaCheckPermission("system:config:reset") + @PatchMapping("/value") + public void resetValue(@Validated @RequestBody OptionResetValueReq req) { + baseService.resetValue(req); + } +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/RoleController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/RoleController.java new file mode 100644 index 0000000..e142f3f --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/RoleController.java @@ -0,0 +1,84 @@ +package top.ysoft.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotEmpty; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.system.model.query.RoleQuery; +import top.ysoft.admin.system.model.query.RoleUserQuery; +import top.ysoft.admin.system.model.req.RoleReq; +import top.ysoft.admin.system.model.req.RoleUpdatePermissionReq; +import top.ysoft.admin.system.model.resp.role.RoleDetailResp; +import top.ysoft.admin.system.model.resp.role.RoleResp; +import top.ysoft.admin.system.model.resp.role.RoleUserResp; +import top.ysoft.admin.system.service.RoleService; +import top.ysoft.admin.system.service.UserRoleService; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; +import top.continew.starter.extension.crud.model.query.PageQuery; +import top.continew.starter.extension.crud.model.resp.PageResp; + +import java.util.List; + +/** + * 角色管理 API + * + * @author Charles7c + * @since 2023/2/8 23:11 + */ +@Tag(name = "角色管理 API") +@Validated +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/system/role", api = {Api.LIST, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE}) +public class RoleController extends BaseController { + + private final UserRoleService userRoleService; + + @Operation(summary = "修改权限", description = "修改角色的功能权限") + @SaCheckPermission("system:role:updatePermission") + @PutMapping("/{id}/permission") + public void updatePermission(@PathVariable("id") Long id, @Validated @RequestBody RoleUpdatePermissionReq req) { + baseService.updatePermission(id, req); + } + + @Operation(summary = "分页查询关联用户", description = "分页查询角色关联的用户列表") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("system:role:list") + @GetMapping("/{id}/user") + public PageResp pageUser(@PathVariable("id") Long id, + @Validated RoleUserQuery query, + @Validated PageQuery pageQuery) { + query.setRoleId(id); + return userRoleService.pageUser(query, pageQuery); + } + + @Operation(summary = "分配用户", description = "批量分配角色给用户") + @SaCheckPermission("system:role:assign") + @PostMapping("/{id}/user") + public void assignToUsers(@PathVariable("id") Long id, + @Validated @NotEmpty(message = "用户ID列表不能为空") @RequestBody List userIds) { + baseService.assignToUsers(id, userIds); + } + + @Operation(summary = "取消分配用户", description = "批量取消分配角色给用户") + @SaCheckPermission("system:role:unassign") + @DeleteMapping("/user") + public void unassignFromUsers(@Validated @NotEmpty(message = "用户列表不能为空") @RequestBody List userRoleIds) { + userRoleService.deleteByIds(userRoleIds); + } + + @Operation(summary = "查询关联用户ID", description = "查询角色关联的用户ID列表") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("system:role:list") + @GetMapping("/{id}/user/id") + public List listUserId(@PathVariable("id") Long id) { + return userRoleService.listUserIdByRoleId(id); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/StorageController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/StorageController.java new file mode 100644 index 0000000..af74fe4 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/StorageController.java @@ -0,0 +1,49 @@ +package top.ysoft.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.common.model.req.CommonStatusUpdateReq; +import top.ysoft.admin.system.model.query.StorageQuery; +import top.ysoft.admin.system.model.req.StorageReq; +import top.ysoft.admin.system.model.resp.StorageResp; +import top.ysoft.admin.system.service.StorageService; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; + +/** + * 存储管理 API + * + * @author Charles7c + * @since 2023/12/26 22:09 + */ +@Tag(name = "存储管理 API") +@Validated +@RestController +@CrudRequestMapping(value = "/system/storage", api = {Api.LIST, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE}) +public class StorageController extends BaseController { + + @Operation(summary = "修改状态", description = "修改状态") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("system:storage:updateStatus") + @PutMapping({"/{id}/status"}) + public void updateStatus(@Validated @RequestBody CommonStatusUpdateReq req, @PathVariable("id") Long id) { + baseService.updateStatus(req, id); + } + + @Operation(summary = "设为默认存储", description = "设为默认存储") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("system:storage:setDefault") + @PutMapping({"/{id}/default"}) + public void setDefault(@PathVariable("id") Long id) { + baseService.setDefault(id); + } +} \ No newline at end of file diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/UserCenterController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/UserCenterController.java new file mode 100644 index 0000000..a292114 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/UserCenterController.java @@ -0,0 +1,140 @@ +package top.ysoft.admin.controller.system; + +import com.xkcoding.justauth.AuthRequestFactory; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthRequest; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import top.ysoft.admin.common.constant.CacheConstants; +import top.ysoft.admin.common.context.UserContextHolder; +import top.ysoft.admin.common.util.SecureUtils; +import top.ysoft.admin.system.enums.SocialSourceEnum; +import top.ysoft.admin.system.model.entity.UserSocialDO; +import top.ysoft.admin.system.model.req.user.UserBasicInfoUpdateReq; +import top.ysoft.admin.system.model.req.user.UserEmailUpdateRequest; +import top.ysoft.admin.system.model.req.user.UserPasswordUpdateReq; +import top.ysoft.admin.system.model.req.user.UserPhoneUpdateReq; +import top.ysoft.admin.system.model.resp.AvatarResp; +import top.ysoft.admin.system.model.resp.user.UserSocialBindResp; +import top.ysoft.admin.system.service.UserService; +import top.ysoft.admin.system.service.UserSocialService; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.util.ExceptionUtils; +import top.continew.starter.core.validation.ValidationUtils; + +import java.io.IOException; +import java.util.List; + +/** + * 个人中心 API + * + * @author Charles7c + * @since 2023/1/2 11:41 + */ +@Tag(name = "个人中心 API") +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/system/user") +public class UserCenterController { + + private static final String DECRYPT_FAILED = "当前密码解密失败"; + private static final String CAPTCHA_EXPIRED = "验证码已失效"; + private final UserService userService; + private final UserSocialService userSocialService; + private final AuthRequestFactory authRequestFactory; + + @Operation(summary = "修改头像", description = "用户修改个人头像") + @PostMapping("/avatar") + public AvatarResp updateAvatar(@NotNull(message = "头像不能为空") MultipartFile avatarFile) throws IOException { + ValidationUtils.throwIf(avatarFile::isEmpty, "头像不能为空"); + String newAvatar = userService.updateAvatar(avatarFile, UserContextHolder.getUserId()); + return AvatarResp.builder().avatar(newAvatar).build(); + } + + @Operation(summary = "修改基础信息", description = "修改用户基础信息") + @PatchMapping("/basic/info") + public void updateBasicInfo(@Validated @RequestBody UserBasicInfoUpdateReq req) { + userService.updateBasicInfo(req, UserContextHolder.getUserId()); + } + + @Operation(summary = "修改密码", description = "修改用户登录密码") + @PatchMapping("/password") + public void updatePassword(@Validated @RequestBody UserPasswordUpdateReq updateReq) { + String rawOldPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq + .getOldPassword())); + ValidationUtils.throwIfNull(rawOldPassword, DECRYPT_FAILED); + String rawNewPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq + .getNewPassword())); + ValidationUtils.throwIfNull(rawNewPassword, "新密码解密失败"); + userService.updatePassword(rawOldPassword, rawNewPassword, UserContextHolder.getUserId()); + } + + @Operation(summary = "修改手机号", description = "修改手机号") + @PatchMapping("/phone") + public void updatePhone(@Validated @RequestBody UserPhoneUpdateReq updateReq) { + String rawOldPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq + .getOldPassword())); + ValidationUtils.throwIfBlank(rawOldPassword, DECRYPT_FAILED); + String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + updateReq.getPhone(); + String captcha = RedisUtils.get(captchaKey); + ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); + ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误"); + RedisUtils.delete(captchaKey); + userService.updatePhone(updateReq.getPhone(), rawOldPassword, UserContextHolder.getUserId()); + } + + @Operation(summary = "修改邮箱", description = "修改用户邮箱") + @PatchMapping("/email") + public void updateEmail(@Validated @RequestBody UserEmailUpdateRequest updateReq) { + String rawOldPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq + .getOldPassword())); + ValidationUtils.throwIfBlank(rawOldPassword, DECRYPT_FAILED); + String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + updateReq.getEmail(); + String captcha = RedisUtils.get(captchaKey); + ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); + ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误"); + RedisUtils.delete(captchaKey); + userService.updateEmail(updateReq.getEmail(), rawOldPassword, UserContextHolder.getUserId()); + } + + @Operation(summary = "查询绑定的三方账号", description = "查询绑定的三方账号") + @GetMapping("/social") + public List listSocialBind() { + List userSocialList = userSocialService.listByUserId(UserContextHolder.getUserId()); + return userSocialList.stream().map(userSocial -> { + String source = userSocial.getSource(); + UserSocialBindResp userSocialBind = new UserSocialBindResp(); + userSocialBind.setSource(source); + userSocialBind.setDescription(SocialSourceEnum.valueOf(source).getDescription()); + return userSocialBind; + }).toList(); + } + + @Operation(summary = "绑定三方账号", description = "绑定三方账号") + @Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH) + @PostMapping("/social/{source}") + public void bindSocial(@PathVariable String source, @RequestBody AuthCallback callback) { + AuthRequest authRequest = authRequestFactory.get(source); + AuthResponse response = authRequest.login(callback); + ValidationUtils.throwIf(!response.ok(), response.getMsg()); + AuthUser authUser = response.getData(); + userSocialService.bind(authUser, UserContextHolder.getUserId()); + } + + @Operation(summary = "解绑三方账号", description = "解绑三方账号") + @Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH) + @DeleteMapping("/social/{source}") + public void unbindSocial(@PathVariable String source) { + userSocialService.deleteBySourceAndUserId(source, UserContextHolder.getUserId()); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/UserController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/UserController.java new file mode 100644 index 0000000..99fcbd5 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/UserController.java @@ -0,0 +1,105 @@ +package top.ysoft.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.ReUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import top.ysoft.admin.common.controller.BaseController; +import top.ysoft.admin.common.constant.RegexConstants; +import top.ysoft.admin.common.util.SecureUtils; +import top.ysoft.admin.system.model.query.UserQuery; +import top.ysoft.admin.system.model.req.user.UserImportReq; +import top.ysoft.admin.system.model.req.user.UserPasswordResetReq; +import top.ysoft.admin.system.model.req.user.UserReq; +import top.ysoft.admin.system.model.req.user.UserRoleUpdateReq; +import top.ysoft.admin.system.model.resp.user.UserDetailResp; +import top.ysoft.admin.system.model.resp.user.UserImportParseResp; +import top.ysoft.admin.system.model.resp.user.UserImportResp; +import top.ysoft.admin.system.model.resp.user.UserResp; +import top.ysoft.admin.system.service.UserService; +import top.continew.starter.core.util.ExceptionUtils; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; +import top.continew.starter.extension.crud.model.resp.BaseIdResp; +import top.continew.starter.extension.crud.validation.CrudValidationGroup; + +import java.io.IOException; + +/** + * 用户管理 API + * + * @author Charles7c + * @since 2023/2/20 21:00 + */ +@Tag(name = "用户管理 API") +@Validated +@RestController +@RequiredArgsConstructor +@CrudRequestMapping(value = "/system/user", api = {Api.PAGE, Api.LIST, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, + Api.EXPORT}) +public class UserController extends BaseController { + + @Override + @Operation(summary = "新增数据", description = "新增数据") + public BaseIdResp add(@Validated(CrudValidationGroup.Add.class) @RequestBody UserReq req) { + String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getPassword())); + ValidationUtils.throwIfNull(rawPassword, "密码解密失败"); + ValidationUtils.throwIf(!ReUtil + .isMatch(RegexConstants.PASSWORD, rawPassword), "密码长度为 8-32 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字"); + req.setPassword(rawPassword); + return super.add(req); + } + + @Operation(summary = "下载导入模板", description = "下载导入模板") + @SaCheckPermission("system:user:import") + @GetMapping(value = "/import/template", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public void downloadImportTemplate(HttpServletResponse response) throws IOException { + baseService.downloadImportTemplate(response); + } + + @Operation(summary = "解析导入数据", description = "解析导入数据") + @SaCheckPermission("system:user:import") + @PostMapping("/import/parse") + public UserImportParseResp parseImport(@NotNull(message = "文件不能为空") MultipartFile file) { + ValidationUtils.throwIf(file::isEmpty, "文件不能为空"); + return baseService.parseImport(file); + } + + @Operation(summary = "导入数据", description = "导入数据") + @SaCheckPermission("system:user:import") + @PostMapping(value = "/import") + public UserImportResp importUser(@Validated @RequestBody UserImportReq req) { + return baseService.importUser(req); + } + + @Operation(summary = "重置密码", description = "重置用户登录密码") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("system:user:resetPwd") + @PatchMapping("/{id}/password") + public void resetPassword(@Validated @RequestBody UserPasswordResetReq req, @PathVariable Long id) { + String rawNewPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getNewPassword())); + ValidationUtils.throwIfNull(rawNewPassword, "新密码解密失败"); + ValidationUtils.throwIf(!ReUtil + .isMatch(RegexConstants.PASSWORD, rawNewPassword), "密码长度为 8-32 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字"); + req.setNewPassword(rawNewPassword); + baseService.resetPassword(req, id); + } + + @Operation(summary = "分配角色", description = "为用户新增或移除角色") + @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) + @SaCheckPermission("system:user:updateRole") + @PatchMapping("/{id}/role") + public void updateRole(@Validated @RequestBody UserRoleUpdateReq updateReq, @PathVariable Long id) { + baseService.updateRole(updateReq, id); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/zctest.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/zctest.java new file mode 100644 index 0000000..ae946f1 --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/zctest.java @@ -0,0 +1,40 @@ +package top.ysoft.admin.controller.system; + +import org.dromara.x.file.storage.core.FileInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import top.ysoft.admin.common.util.PictureUtils; +// import top.ysoft.admin.controller.file.SysFileController; +// import top.ysoft.admin.file.domain.SysFile; +import top.ysoft.admin.system.model.resp.file.FileUploadResp; +import top.ysoft.admin.system.service.FileService; + +@RestController +@RequestMapping("/test") +public class zctest { + + // @Autowired + // private SysFileController fileController; + + @Autowired + private FileService fileService; + + @PostMapping() + public FileUploadResp test() { + String urls = "http://bpic.588ku.com/element_origin_min_pic/19/03/15/75076c485081d15ed9c224ad3e4ce4a1.jpg"; + MultipartFile file = PictureUtils.createMultipartFile(urls, "zctest11111.jpg"); + // SysFile sysFile = fileController.uploadMinio(file, "zctest11111"); + // return sysFile; + + FileInfo fileInfo = fileService.upload(file, "zctest", "minio", false, false); + return FileUploadResp.builder() + .id(fileInfo.getId()) + .url(fileInfo.getUrl()) + .thUrl(fileInfo.getThUrl()) + .metadata(fileInfo.getMetadata()) + .build(); + } +} diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/yfApi/YFListenController.java b/wms-webapi/src/main/java/top/wms/admin/controller/yfApi/YFListenController.java new file mode 100644 index 0000000..49a94cf --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/yfApi/YFListenController.java @@ -0,0 +1,59 @@ +package top.ysoft.admin.controller.yfApi; + +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.feiniaojin.gracefulresponse.api.ExcludeFromGracefulResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import top.continew.starter.log.annotation.Log; +import top.ysoft.admin.yfApi.domain.BackInfo; +import top.ysoft.admin.yfApi.domain.BackResult; +import top.ysoft.admin.yfApi.domain.HeartInfo; +import top.ysoft.admin.yfApi.service.IYFListenService; + +/** + * 宇泛人脸设备回调 + * + * @author zc + * @since 2025/03/21 18:10 + */ +@Tag(name = "宇泛人脸设备回调 API") +@RestController +@Slf4j +@RequiredArgsConstructor +@RequestMapping("/sdk") +public class YFListenController { + + private final IYFListenService iyfListenService; + + @Log(ignore = true) + @PostMapping("/backUrlHeart") + public void heart(HeartInfo info) { + iyfListenService.heart(info); + } + + /** + * 宇泛人脸设备回调地址 + * + * @return 回调信息 + */ + @Log(ignore = true) + @ExcludeFromGracefulResponse + @PostMapping("/backUrl") + public BackResult backUrl(@RequestBody BackInfo info) { + log.info("宇泛人脸设备识别回调:{}", JSON.toJSONString(info)); + if (!StrUtil.equals(info.getPersonId(), "STRANGERBABY")) { + //异步处理接收到的人员信息 + ThreadUtil.execAsync(() -> { + iyfListenService.processPeopleRecord(info); + }); + } + return new BackResult(1, true); + } +} \ No newline at end of file diff --git a/wms-webapi/src/main/resources/banner.txt b/wms-webapi/src/main/resources/banner.txt new file mode 100644 index 0000000..302f8af --- /dev/null +++ b/wms-webapi/src/main/resources/banner.txt @@ -0,0 +1,9 @@ + ____ _ _ _ _ _ _ _ + / ___| ___ _ __ | |_ (_)| \ | | ___ __ __ / \ __| | _ __ ___ (_) _ __ + | | / _ \ | '_ \ | __|| || \| | / _ \\ \ /\ / /_____ / _ \ / _` || '_ ` _ \ | || '_ \ + | |___| (_) || | | || |_ | || |\ || __/ \ V V /|_____|/ ___ \| (_| || | | | | || || | | | + \____|\___/ |_| |_| \__||_||_| \_| \___| \_/\_/ /_/ \_\\__,_||_| |_| |_||_||_| |_| + + :: ${project.name} :: v${project.version} + :: ContiNew Starter :: v2.9.0 + :: Spring Boot :: v${spring-boot.version} diff --git a/wms-webapi/src/main/resources/china.json b/wms-webapi/src/main/resources/china.json new file mode 100644 index 0000000..c16c767 --- /dev/null +++ b/wms-webapi/src/main/resources/china.json @@ -0,0 +1 @@ +{"code":100000,"name":"中国","fullname":"中华人民共和国","level":"country","filename":"100000","children":[{"code":110000,"name":"北京","fullname":"北京市","level":"province","filename":"110000","children":[{"code":110101,"name":"东城","fullname":"东城区","level":"district","filename":"110000/110101"},{"code":110102,"name":"西城","fullname":"西城区","level":"district","filename":"110000/110102"},{"code":110105,"name":"朝阳","fullname":"朝阳区","level":"district","filename":"110000/110105"},{"code":110106,"name":"丰台","fullname":"丰台区","level":"district","filename":"110000/110106"},{"code":110107,"name":"石景山","fullname":"石景山区","level":"district","filename":"110000/110107"},{"code":110108,"name":"海淀","fullname":"海淀区","level":"district","filename":"110000/110108"},{"code":110109,"name":"门头沟","fullname":"门头沟区","level":"district","filename":"110000/110109"},{"code":110111,"name":"房山","fullname":"房山区","level":"district","filename":"110000/110111"},{"code":110112,"name":"通州","fullname":"通州区","level":"district","filename":"110000/110112"},{"code":110113,"name":"顺义","fullname":"顺义区","level":"district","filename":"110000/110113"},{"code":110114,"name":"昌平","fullname":"昌平区","level":"district","filename":"110000/110114"},{"code":110115,"name":"大兴","fullname":"大兴区","level":"district","filename":"110000/110115"},{"code":110116,"name":"怀柔","fullname":"怀柔区","level":"district","filename":"110000/110116"},{"code":110117,"name":"平谷","fullname":"平谷区","level":"district","filename":"110000/110117"},{"code":110118,"name":"密云","fullname":"密云区","level":"district","filename":"110000/110118"},{"code":110119,"name":"延庆","fullname":"延庆区","level":"district","filename":"110000/110119"}]},{"code":120000,"name":"天津","fullname":"天津市","level":"province","filename":"120000","children":[{"code":120101,"name":"和平","fullname":"和平区","level":"district","filename":"120000/120101"},{"code":120102,"name":"河东","fullname":"河东区","level":"district","filename":"120000/120102"},{"code":120103,"name":"河西","fullname":"河西区","level":"district","filename":"120000/120103"},{"code":120104,"name":"南开","fullname":"南开区","level":"district","filename":"120000/120104"},{"code":120105,"name":"河北","fullname":"河北区","level":"district","filename":"120000/120105"},{"code":120106,"name":"红桥","fullname":"红桥区","level":"district","filename":"120000/120106"},{"code":120110,"name":"东丽","fullname":"东丽区","level":"district","filename":"120000/120110"},{"code":120111,"name":"西青","fullname":"西青区","level":"district","filename":"120000/120111"},{"code":120112,"name":"津南","fullname":"津南区","level":"district","filename":"120000/120112"},{"code":120113,"name":"北辰","fullname":"北辰区","level":"district","filename":"120000/120113"},{"code":120114,"name":"武清","fullname":"武清区","level":"district","filename":"120000/120114"},{"code":120115,"name":"宝坻","fullname":"宝坻区","level":"district","filename":"120000/120115"},{"code":120116,"name":"滨海","fullname":"滨海新区","level":"district","filename":"120000/120116"},{"code":120117,"name":"宁河","fullname":"宁河区","level":"district","filename":"120000/120117"},{"code":120118,"name":"静海","fullname":"静海区","level":"district","filename":"120000/120118"},{"code":120119,"name":"蓟州","fullname":"蓟州区","level":"district","filename":"120000/120119"}]},{"code":130000,"name":"河北","fullname":"河北省","level":"province","filename":"130000","children":[{"code":130100,"name":"石家庄","fullname":"石家庄市","level":"city","filename":"130000/130100","children":[{"code":130102,"name":"长安","fullname":"长安区","level":"district","filename":"130000/130100/130102"},{"code":130104,"name":"桥西","fullname":"桥西区","level":"district","filename":"130000/130100/130104"},{"code":130105,"name":"新华","fullname":"新华区","level":"district","filename":"130000/130100/130105"},{"code":130107,"name":"井陉","fullname":"井陉矿区","level":"district","filename":"130000/130100/130107"},{"code":130108,"name":"裕华","fullname":"裕华区","level":"district","filename":"130000/130100/130108"},{"code":130109,"name":"藁城","fullname":"藁城区","level":"district","filename":"130000/130100/130109"},{"code":130110,"name":"鹿泉","fullname":"鹿泉区","level":"district","filename":"130000/130100/130110"},{"code":130111,"name":"栾城","fullname":"栾城区","level":"district","filename":"130000/130100/130111"},{"code":130121,"name":"井陉","fullname":"井陉县","level":"district","filename":"130000/130100/130121"},{"code":130123,"name":"正定","fullname":"正定县","level":"district","filename":"130000/130100/130123"},{"code":130125,"name":"行唐","fullname":"行唐县","level":"district","filename":"130000/130100/130125"},{"code":130126,"name":"灵寿","fullname":"灵寿县","level":"district","filename":"130000/130100/130126"},{"code":130127,"name":"高邑","fullname":"高邑县","level":"district","filename":"130000/130100/130127"},{"code":130128,"name":"深泽","fullname":"深泽县","level":"district","filename":"130000/130100/130128"},{"code":130129,"name":"赞皇","fullname":"赞皇县","level":"district","filename":"130000/130100/130129"},{"code":130130,"name":"无极","fullname":"无极县","level":"district","filename":"130000/130100/130130"},{"code":130131,"name":"平山","fullname":"平山县","level":"district","filename":"130000/130100/130131"},{"code":130132,"name":"元氏","fullname":"元氏县","level":"district","filename":"130000/130100/130132"},{"code":130133,"name":"赵县","fullname":"赵县","level":"district","filename":"130000/130100/130133"},{"code":130181,"name":"辛集","fullname":"辛集市","level":"district","filename":"130000/130100/130181"},{"code":130183,"name":"晋州","fullname":"晋州市","level":"district","filename":"130000/130100/130183"},{"code":130184,"name":"新乐","fullname":"新乐市","level":"district","filename":"130000/130100/130184"}]},{"code":130200,"name":"唐山","fullname":"唐山市","level":"city","filename":"130000/130200","children":[{"code":130202,"name":"路南","fullname":"路南区","level":"district","filename":"130000/130200/130202"},{"code":130203,"name":"路北","fullname":"路北区","level":"district","filename":"130000/130200/130203"},{"code":130204,"name":"古冶","fullname":"古冶区","level":"district","filename":"130000/130200/130204"},{"code":130205,"name":"开平","fullname":"开平区","level":"district","filename":"130000/130200/130205"},{"code":130207,"name":"丰南","fullname":"丰南区","level":"district","filename":"130000/130200/130207"},{"code":130208,"name":"丰润","fullname":"丰润区","level":"district","filename":"130000/130200/130208"},{"code":130209,"name":"曹妃甸","fullname":"曹妃甸区","level":"district","filename":"130000/130200/130209"},{"code":130224,"name":"滦南","fullname":"滦南县","level":"district","filename":"130000/130200/130224"},{"code":130225,"name":"乐亭","fullname":"乐亭县","level":"district","filename":"130000/130200/130225"},{"code":130227,"name":"迁西","fullname":"迁西县","level":"district","filename":"130000/130200/130227"},{"code":130229,"name":"玉田","fullname":"玉田县","level":"district","filename":"130000/130200/130229"},{"code":130281,"name":"遵化","fullname":"遵化市","level":"district","filename":"130000/130200/130281"},{"code":130283,"name":"迁安","fullname":"迁安市","level":"district","filename":"130000/130200/130283"},{"code":130284,"name":"滦州","fullname":"滦州市","level":"district","filename":"130000/130200/130284"}]},{"code":130300,"name":"秦皇岛","fullname":"秦皇岛市","level":"city","filename":"130000/130300","children":[{"code":130302,"name":"海港","fullname":"海港区","level":"district","filename":"130000/130300/130302"},{"code":130303,"name":"山海关","fullname":"山海关区","level":"district","filename":"130000/130300/130303"},{"code":130304,"name":"北戴河","fullname":"北戴河区","level":"district","filename":"130000/130300/130304"},{"code":130306,"name":"抚宁","fullname":"抚宁区","level":"district","filename":"130000/130300/130306"},{"code":130321,"name":"青龙","fullname":"青龙满族自治县","level":"district","filename":"130000/130300/130321"},{"code":130322,"name":"昌黎","fullname":"昌黎县","level":"district","filename":"130000/130300/130322"},{"code":130324,"name":"卢龙","fullname":"卢龙县","level":"district","filename":"130000/130300/130324"}]},{"code":130400,"name":"邯郸","fullname":"邯郸市","level":"city","filename":"130000/130400","children":[{"code":130402,"name":"邯山","fullname":"邯山区","level":"district","filename":"130000/130400/130402"},{"code":130403,"name":"丛台","fullname":"丛台区","level":"district","filename":"130000/130400/130403"},{"code":130404,"name":"复兴","fullname":"复兴区","level":"district","filename":"130000/130400/130404"},{"code":130406,"name":"峰峰","fullname":"峰峰矿区","level":"district","filename":"130000/130400/130406"},{"code":130407,"name":"肥乡","fullname":"肥乡区","level":"district","filename":"130000/130400/130407"},{"code":130408,"name":"永年","fullname":"永年区","level":"district","filename":"130000/130400/130408"},{"code":130423,"name":"临漳","fullname":"临漳县","level":"district","filename":"130000/130400/130423"},{"code":130424,"name":"成安","fullname":"成安县","level":"district","filename":"130000/130400/130424"},{"code":130425,"name":"大名","fullname":"大名县","level":"district","filename":"130000/130400/130425"},{"code":130426,"name":"涉县","fullname":"涉县","level":"district","filename":"130000/130400/130426"},{"code":130427,"name":"磁县","fullname":"磁县","level":"district","filename":"130000/130400/130427"},{"code":130430,"name":"邱县","fullname":"邱县","level":"district","filename":"130000/130400/130430"},{"code":130431,"name":"鸡泽","fullname":"鸡泽县","level":"district","filename":"130000/130400/130431"},{"code":130432,"name":"广平","fullname":"广平县","level":"district","filename":"130000/130400/130432"},{"code":130433,"name":"馆陶","fullname":"馆陶县","level":"district","filename":"130000/130400/130433"},{"code":130434,"name":"魏县","fullname":"魏县","level":"district","filename":"130000/130400/130434"},{"code":130435,"name":"曲周","fullname":"曲周县","level":"district","filename":"130000/130400/130435"},{"code":130481,"name":"武安","fullname":"武安市","level":"district","filename":"130000/130400/130481"}]},{"code":130500,"name":"邢台","fullname":"邢台市","level":"city","filename":"130000/130500","children":[{"code":130502,"name":"襄都","fullname":"襄都区","level":"district","filename":"130000/130500/130502"},{"code":130503,"name":"信都","fullname":"信都区","level":"district","filename":"130000/130500/130503"},{"code":130505,"name":"任泽","fullname":"任泽区","level":"district","filename":"130000/130500/130505"},{"code":130506,"name":"南和","fullname":"南和区","level":"district","filename":"130000/130500/130506"},{"code":130522,"name":"临城","fullname":"临城县","level":"district","filename":"130000/130500/130522"},{"code":130523,"name":"内丘","fullname":"内丘县","level":"district","filename":"130000/130500/130523"},{"code":130524,"name":"柏乡","fullname":"柏乡县","level":"district","filename":"130000/130500/130524"},{"code":130525,"name":"隆尧","fullname":"隆尧县","level":"district","filename":"130000/130500/130525"},{"code":130528,"name":"宁晋","fullname":"宁晋县","level":"district","filename":"130000/130500/130528"},{"code":130529,"name":"巨鹿","fullname":"巨鹿县","level":"district","filename":"130000/130500/130529"},{"code":130530,"name":"新河","fullname":"新河县","level":"district","filename":"130000/130500/130530"},{"code":130531,"name":"广宗","fullname":"广宗县","level":"district","filename":"130000/130500/130531"},{"code":130532,"name":"平乡","fullname":"平乡县","level":"district","filename":"130000/130500/130532"},{"code":130533,"name":"威县","fullname":"威县","level":"district","filename":"130000/130500/130533"},{"code":130534,"name":"清河","fullname":"清河县","level":"district","filename":"130000/130500/130534"},{"code":130535,"name":"临西","fullname":"临西县","level":"district","filename":"130000/130500/130535"},{"code":130581,"name":"南宫","fullname":"南宫市","level":"district","filename":"130000/130500/130581"},{"code":130582,"name":"沙河","fullname":"沙河市","level":"district","filename":"130000/130500/130582"}]},{"code":130600,"name":"保定","fullname":"保定市","level":"city","filename":"130000/130600","children":[{"code":130602,"name":"竞秀","fullname":"竞秀区","level":"district","filename":"130000/130600/130602"},{"code":130606,"name":"莲池","fullname":"莲池区","level":"district","filename":"130000/130600/130606"},{"code":130607,"name":"满城","fullname":"满城区","level":"district","filename":"130000/130600/130607"},{"code":130608,"name":"清苑","fullname":"清苑区","level":"district","filename":"130000/130600/130608"},{"code":130609,"name":"徐水","fullname":"徐水区","level":"district","filename":"130000/130600/130609"},{"code":130623,"name":"涞水","fullname":"涞水县","level":"district","filename":"130000/130600/130623"},{"code":130624,"name":"阜平","fullname":"阜平县","level":"district","filename":"130000/130600/130624"},{"code":130626,"name":"定兴","fullname":"定兴县","level":"district","filename":"130000/130600/130626"},{"code":130627,"name":"唐县","fullname":"唐县","level":"district","filename":"130000/130600/130627"},{"code":130628,"name":"高阳","fullname":"高阳县","level":"district","filename":"130000/130600/130628"},{"code":130629,"name":"容城","fullname":"容城县","level":"district","filename":"130000/130600/130629"},{"code":130630,"name":"涞源","fullname":"涞源县","level":"district","filename":"130000/130600/130630"},{"code":130631,"name":"望都","fullname":"望都县","level":"district","filename":"130000/130600/130631"},{"code":130632,"name":"安新","fullname":"安新县","level":"district","filename":"130000/130600/130632"},{"code":130633,"name":"易县","fullname":"易县","level":"district","filename":"130000/130600/130633"},{"code":130634,"name":"曲阳","fullname":"曲阳县","level":"district","filename":"130000/130600/130634"},{"code":130635,"name":"蠡县","fullname":"蠡县","level":"district","filename":"130000/130600/130635"},{"code":130636,"name":"顺平","fullname":"顺平县","level":"district","filename":"130000/130600/130636"},{"code":130637,"name":"博野","fullname":"博野县","level":"district","filename":"130000/130600/130637"},{"code":130638,"name":"雄县","fullname":"雄县","level":"district","filename":"130000/130600/130638"},{"code":130681,"name":"涿州","fullname":"涿州市","level":"district","filename":"130000/130600/130681"},{"code":130682,"name":"定州","fullname":"定州市","level":"district","filename":"130000/130600/130682"},{"code":130683,"name":"安国","fullname":"安国市","level":"district","filename":"130000/130600/130683"},{"code":130684,"name":"高碑店","fullname":"高碑店市","level":"district","filename":"130000/130600/130684"}]},{"code":130700,"name":"张家口","fullname":"张家口市","level":"city","filename":"130000/130700","children":[{"code":130702,"name":"桥东","fullname":"桥东区","level":"district","filename":"130000/130700/130702"},{"code":130703,"name":"桥西","fullname":"桥西区","level":"district","filename":"130000/130700/130703"},{"code":130705,"name":"宣化","fullname":"宣化区","level":"district","filename":"130000/130700/130705"},{"code":130706,"name":"下花园","fullname":"下花园区","level":"district","filename":"130000/130700/130706"},{"code":130708,"name":"万全","fullname":"万全区","level":"district","filename":"130000/130700/130708"},{"code":130709,"name":"崇礼","fullname":"崇礼区","level":"district","filename":"130000/130700/130709"},{"code":130722,"name":"张北","fullname":"张北县","level":"district","filename":"130000/130700/130722"},{"code":130723,"name":"康保","fullname":"康保县","level":"district","filename":"130000/130700/130723"},{"code":130724,"name":"沽源","fullname":"沽源县","level":"district","filename":"130000/130700/130724"},{"code":130725,"name":"尚义","fullname":"尚义县","level":"district","filename":"130000/130700/130725"},{"code":130726,"name":"蔚县","fullname":"蔚县","level":"district","filename":"130000/130700/130726"},{"code":130727,"name":"阳原","fullname":"阳原县","level":"district","filename":"130000/130700/130727"},{"code":130728,"name":"怀安","fullname":"怀安县","level":"district","filename":"130000/130700/130728"},{"code":130730,"name":"怀来","fullname":"怀来县","level":"district","filename":"130000/130700/130730"},{"code":130731,"name":"涿鹿","fullname":"涿鹿县","level":"district","filename":"130000/130700/130731"},{"code":130732,"name":"赤城","fullname":"赤城县","level":"district","filename":"130000/130700/130732"}]},{"code":130800,"name":"承德","fullname":"承德市","level":"city","filename":"130000/130800","children":[{"code":130802,"name":"双桥","fullname":"双桥区","level":"district","filename":"130000/130800/130802"},{"code":130803,"name":"双滦","fullname":"双滦区","level":"district","filename":"130000/130800/130803"},{"code":130804,"name":"鹰手营子","fullname":"鹰手营子矿区","level":"district","filename":"130000/130800/130804"},{"code":130821,"name":"承德","fullname":"承德县","level":"district","filename":"130000/130800/130821"},{"code":130822,"name":"兴隆","fullname":"兴隆县","level":"district","filename":"130000/130800/130822"},{"code":130824,"name":"滦平","fullname":"滦平县","level":"district","filename":"130000/130800/130824"},{"code":130825,"name":"隆化","fullname":"隆化县","level":"district","filename":"130000/130800/130825"},{"code":130826,"name":"丰宁","fullname":"丰宁满族自治县","level":"district","filename":"130000/130800/130826"},{"code":130827,"name":"宽城","fullname":"宽城满族自治县","level":"district","filename":"130000/130800/130827"},{"code":130828,"name":"围场","fullname":"围场满族蒙古族自治县","level":"district","filename":"130000/130800/130828"},{"code":130881,"name":"平泉","fullname":"平泉市","level":"district","filename":"130000/130800/130881"}]},{"code":130900,"name":"沧州","fullname":"沧州市","level":"city","filename":"130000/130900","children":[{"code":130902,"name":"新华","fullname":"新华区","level":"district","filename":"130000/130900/130902"},{"code":130903,"name":"运河","fullname":"运河区","level":"district","filename":"130000/130900/130903"},{"code":130921,"name":"沧县","fullname":"沧县","level":"district","filename":"130000/130900/130921"},{"code":130922,"name":"青县","fullname":"青县","level":"district","filename":"130000/130900/130922"},{"code":130923,"name":"东光","fullname":"东光县","level":"district","filename":"130000/130900/130923"},{"code":130924,"name":"海兴","fullname":"海兴县","level":"district","filename":"130000/130900/130924"},{"code":130925,"name":"盐山","fullname":"盐山县","level":"district","filename":"130000/130900/130925"},{"code":130926,"name":"肃宁","fullname":"肃宁县","level":"district","filename":"130000/130900/130926"},{"code":130927,"name":"南皮","fullname":"南皮县","level":"district","filename":"130000/130900/130927"},{"code":130928,"name":"吴桥","fullname":"吴桥县","level":"district","filename":"130000/130900/130928"},{"code":130929,"name":"献县","fullname":"献县","level":"district","filename":"130000/130900/130929"},{"code":130930,"name":"孟村","fullname":"孟村回族自治县","level":"district","filename":"130000/130900/130930"},{"code":130981,"name":"泊头","fullname":"泊头市","level":"district","filename":"130000/130900/130981"},{"code":130982,"name":"任丘","fullname":"任丘市","level":"district","filename":"130000/130900/130982"},{"code":130983,"name":"黄骅","fullname":"黄骅市","level":"district","filename":"130000/130900/130983"},{"code":130984,"name":"河间","fullname":"河间市","level":"district","filename":"130000/130900/130984"}]},{"code":131000,"name":"廊坊","fullname":"廊坊市","level":"city","filename":"130000/131000","children":[{"code":131002,"name":"安次","fullname":"安次区","level":"district","filename":"130000/131000/131002"},{"code":131003,"name":"广阳","fullname":"广阳区","level":"district","filename":"130000/131000/131003"},{"code":131022,"name":"固安","fullname":"固安县","level":"district","filename":"130000/131000/131022"},{"code":131023,"name":"永清","fullname":"永清县","level":"district","filename":"130000/131000/131023"},{"code":131024,"name":"香河","fullname":"香河县","level":"district","filename":"130000/131000/131024"},{"code":131025,"name":"大城","fullname":"大城县","level":"district","filename":"130000/131000/131025"},{"code":131026,"name":"文安","fullname":"文安县","level":"district","filename":"130000/131000/131026"},{"code":131028,"name":"大厂","fullname":"大厂回族自治县","level":"district","filename":"130000/131000/131028"},{"code":131081,"name":"霸州","fullname":"霸州市","level":"district","filename":"130000/131000/131081"},{"code":131082,"name":"三河","fullname":"三河市","level":"district","filename":"130000/131000/131082"}]},{"code":131100,"name":"衡水","fullname":"衡水市","level":"city","filename":"130000/131100","children":[{"code":131102,"name":"桃城","fullname":"桃城区","level":"district","filename":"130000/131100/131102"},{"code":131103,"name":"冀州","fullname":"冀州区","level":"district","filename":"130000/131100/131103"},{"code":131121,"name":"枣强","fullname":"枣强县","level":"district","filename":"130000/131100/131121"},{"code":131122,"name":"武邑","fullname":"武邑县","level":"district","filename":"130000/131100/131122"},{"code":131123,"name":"武强","fullname":"武强县","level":"district","filename":"130000/131100/131123"},{"code":131124,"name":"饶阳","fullname":"饶阳县","level":"district","filename":"130000/131100/131124"},{"code":131125,"name":"安平","fullname":"安平县","level":"district","filename":"130000/131100/131125"},{"code":131126,"name":"故城","fullname":"故城县","level":"district","filename":"130000/131100/131126"},{"code":131127,"name":"景县","fullname":"景县","level":"district","filename":"130000/131100/131127"},{"code":131128,"name":"阜城","fullname":"阜城县","level":"district","filename":"130000/131100/131128"},{"code":131182,"name":"深州","fullname":"深州市","level":"district","filename":"130000/131100/131182"}]}]},{"code":140000,"name":"山西","fullname":"山西省","level":"province","filename":"140000","children":[{"code":140100,"name":"太原","fullname":"太原市","level":"city","filename":"140000/140100","children":[{"code":140105,"name":"小店","fullname":"小店区","level":"district","filename":"140000/140100/140105"},{"code":140106,"name":"迎泽","fullname":"迎泽区","level":"district","filename":"140000/140100/140106"},{"code":140107,"name":"杏花岭","fullname":"杏花岭区","level":"district","filename":"140000/140100/140107"},{"code":140108,"name":"尖草坪","fullname":"尖草坪区","level":"district","filename":"140000/140100/140108"},{"code":140109,"name":"万柏林","fullname":"万柏林区","level":"district","filename":"140000/140100/140109"},{"code":140110,"name":"晋源","fullname":"晋源区","level":"district","filename":"140000/140100/140110"},{"code":140121,"name":"清徐","fullname":"清徐县","level":"district","filename":"140000/140100/140121"},{"code":140122,"name":"阳曲","fullname":"阳曲县","level":"district","filename":"140000/140100/140122"},{"code":140123,"name":"娄烦","fullname":"娄烦县","level":"district","filename":"140000/140100/140123"},{"code":140181,"name":"古交","fullname":"古交市","level":"district","filename":"140000/140100/140181"}]},{"code":140200,"name":"大同","fullname":"大同市","level":"city","filename":"140000/140200","children":[{"code":140212,"name":"新荣","fullname":"新荣区","level":"district","filename":"140000/140200/140212"},{"code":140213,"name":"平城","fullname":"平城区","level":"district","filename":"140000/140200/140213"},{"code":140214,"name":"云冈","fullname":"云冈区","level":"district","filename":"140000/140200/140214"},{"code":140215,"name":"云州","fullname":"云州区","level":"district","filename":"140000/140200/140215"},{"code":140221,"name":"阳高","fullname":"阳高县","level":"district","filename":"140000/140200/140221"},{"code":140222,"name":"天镇","fullname":"天镇县","level":"district","filename":"140000/140200/140222"},{"code":140223,"name":"广灵","fullname":"广灵县","level":"district","filename":"140000/140200/140223"},{"code":140224,"name":"灵丘","fullname":"灵丘县","level":"district","filename":"140000/140200/140224"},{"code":140225,"name":"浑源","fullname":"浑源县","level":"district","filename":"140000/140200/140225"},{"code":140226,"name":"左云","fullname":"左云县","level":"district","filename":"140000/140200/140226"}]},{"code":140300,"name":"阳泉","fullname":"阳泉市","level":"city","filename":"140000/140300","children":[{"code":140302,"name":"城区","fullname":"城区","level":"district","filename":"140000/140300/140302"},{"code":140303,"name":"矿区","fullname":"矿区","level":"district","filename":"140000/140300/140303"},{"code":140311,"name":"郊区","fullname":"郊区","level":"district","filename":"140000/140300/140311"},{"code":140321,"name":"平定","fullname":"平定县","level":"district","filename":"140000/140300/140321"},{"code":140322,"name":"盂县","fullname":"盂县","level":"district","filename":"140000/140300/140322"}]},{"code":140400,"name":"长治","fullname":"长治市","level":"city","filename":"140000/140400","children":[{"code":140403,"name":"潞州","fullname":"潞州区","level":"district","filename":"140000/140400/140403"},{"code":140404,"name":"上党","fullname":"上党区","level":"district","filename":"140000/140400/140404"},{"code":140405,"name":"屯留","fullname":"屯留区","level":"district","filename":"140000/140400/140405"},{"code":140406,"name":"潞城","fullname":"潞城区","level":"district","filename":"140000/140400/140406"},{"code":140423,"name":"襄垣","fullname":"襄垣县","level":"district","filename":"140000/140400/140423"},{"code":140425,"name":"平顺","fullname":"平顺县","level":"district","filename":"140000/140400/140425"},{"code":140426,"name":"黎城","fullname":"黎城县","level":"district","filename":"140000/140400/140426"},{"code":140427,"name":"壶关","fullname":"壶关县","level":"district","filename":"140000/140400/140427"},{"code":140428,"name":"长子","fullname":"长子县","level":"district","filename":"140000/140400/140428"},{"code":140429,"name":"武乡","fullname":"武乡县","level":"district","filename":"140000/140400/140429"},{"code":140430,"name":"沁县","fullname":"沁县","level":"district","filename":"140000/140400/140430"},{"code":140431,"name":"沁源","fullname":"沁源县","level":"district","filename":"140000/140400/140431"}]},{"code":140500,"name":"晋城","fullname":"晋城市","level":"city","filename":"140000/140500","children":[{"code":140502,"name":"城区","fullname":"城区","level":"district","filename":"140000/140500/140502"},{"code":140521,"name":"沁水","fullname":"沁水县","level":"district","filename":"140000/140500/140521"},{"code":140522,"name":"阳城","fullname":"阳城县","level":"district","filename":"140000/140500/140522"},{"code":140524,"name":"陵川","fullname":"陵川县","level":"district","filename":"140000/140500/140524"},{"code":140525,"name":"泽州","fullname":"泽州县","level":"district","filename":"140000/140500/140525"},{"code":140581,"name":"高平","fullname":"高平市","level":"district","filename":"140000/140500/140581"}]},{"code":140600,"name":"朔州","fullname":"朔州市","level":"city","filename":"140000/140600","children":[{"code":140602,"name":"朔城","fullname":"朔城区","level":"district","filename":"140000/140600/140602"},{"code":140603,"name":"平鲁","fullname":"平鲁区","level":"district","filename":"140000/140600/140603"},{"code":140621,"name":"山阴","fullname":"山阴县","level":"district","filename":"140000/140600/140621"},{"code":140622,"name":"应县","fullname":"应县","level":"district","filename":"140000/140600/140622"},{"code":140623,"name":"右玉","fullname":"右玉县","level":"district","filename":"140000/140600/140623"},{"code":140681,"name":"怀仁","fullname":"怀仁市","level":"district","filename":"140000/140600/140681"}]},{"code":140700,"name":"晋中","fullname":"晋中市","level":"city","filename":"140000/140700","children":[{"code":140702,"name":"榆次","fullname":"榆次区","level":"district","filename":"140000/140700/140702"},{"code":140703,"name":"太谷","fullname":"太谷区","level":"district","filename":"140000/140700/140703"},{"code":140721,"name":"榆社","fullname":"榆社县","level":"district","filename":"140000/140700/140721"},{"code":140722,"name":"左权","fullname":"左权县","level":"district","filename":"140000/140700/140722"},{"code":140723,"name":"和顺","fullname":"和顺县","level":"district","filename":"140000/140700/140723"},{"code":140724,"name":"昔阳","fullname":"昔阳县","level":"district","filename":"140000/140700/140724"},{"code":140725,"name":"寿阳","fullname":"寿阳县","level":"district","filename":"140000/140700/140725"},{"code":140727,"name":"祁县","fullname":"祁县","level":"district","filename":"140000/140700/140727"},{"code":140728,"name":"平遥","fullname":"平遥县","level":"district","filename":"140000/140700/140728"},{"code":140729,"name":"灵石","fullname":"灵石县","level":"district","filename":"140000/140700/140729"},{"code":140781,"name":"介休","fullname":"介休市","level":"district","filename":"140000/140700/140781"}]},{"code":140800,"name":"运城","fullname":"运城市","level":"city","filename":"140000/140800","children":[{"code":140802,"name":"盐湖","fullname":"盐湖区","level":"district","filename":"140000/140800/140802"},{"code":140821,"name":"临猗","fullname":"临猗县","level":"district","filename":"140000/140800/140821"},{"code":140822,"name":"万荣","fullname":"万荣县","level":"district","filename":"140000/140800/140822"},{"code":140823,"name":"闻喜","fullname":"闻喜县","level":"district","filename":"140000/140800/140823"},{"code":140824,"name":"稷山","fullname":"稷山县","level":"district","filename":"140000/140800/140824"},{"code":140825,"name":"新绛","fullname":"新绛县","level":"district","filename":"140000/140800/140825"},{"code":140826,"name":"绛县","fullname":"绛县","level":"district","filename":"140000/140800/140826"},{"code":140827,"name":"垣曲","fullname":"垣曲县","level":"district","filename":"140000/140800/140827"},{"code":140828,"name":"夏县","fullname":"夏县","level":"district","filename":"140000/140800/140828"},{"code":140829,"name":"平陆","fullname":"平陆县","level":"district","filename":"140000/140800/140829"},{"code":140830,"name":"芮城","fullname":"芮城县","level":"district","filename":"140000/140800/140830"},{"code":140881,"name":"永济","fullname":"永济市","level":"district","filename":"140000/140800/140881"},{"code":140882,"name":"河津","fullname":"河津市","level":"district","filename":"140000/140800/140882"}]},{"code":140900,"name":"忻州","fullname":"忻州市","level":"city","filename":"140000/140900","children":[{"code":140902,"name":"忻府","fullname":"忻府区","level":"district","filename":"140000/140900/140902"},{"code":140921,"name":"定襄","fullname":"定襄县","level":"district","filename":"140000/140900/140921"},{"code":140922,"name":"五台","fullname":"五台县","level":"district","filename":"140000/140900/140922"},{"code":140923,"name":"代县","fullname":"代县","level":"district","filename":"140000/140900/140923"},{"code":140924,"name":"繁峙","fullname":"繁峙县","level":"district","filename":"140000/140900/140924"},{"code":140925,"name":"宁武","fullname":"宁武县","level":"district","filename":"140000/140900/140925"},{"code":140926,"name":"静乐","fullname":"静乐县","level":"district","filename":"140000/140900/140926"},{"code":140927,"name":"神池","fullname":"神池县","level":"district","filename":"140000/140900/140927"},{"code":140928,"name":"五寨","fullname":"五寨县","level":"district","filename":"140000/140900/140928"},{"code":140929,"name":"岢岚","fullname":"岢岚县","level":"district","filename":"140000/140900/140929"},{"code":140930,"name":"河曲","fullname":"河曲县","level":"district","filename":"140000/140900/140930"},{"code":140931,"name":"保德","fullname":"保德县","level":"district","filename":"140000/140900/140931"},{"code":140932,"name":"偏关","fullname":"偏关县","level":"district","filename":"140000/140900/140932"},{"code":140981,"name":"原平","fullname":"原平市","level":"district","filename":"140000/140900/140981"}]},{"code":141000,"name":"临汾","fullname":"临汾市","level":"city","filename":"140000/141000","children":[{"code":141002,"name":"尧都","fullname":"尧都区","level":"district","filename":"140000/141000/141002"},{"code":141021,"name":"曲沃","fullname":"曲沃县","level":"district","filename":"140000/141000/141021"},{"code":141022,"name":"翼城","fullname":"翼城县","level":"district","filename":"140000/141000/141022"},{"code":141023,"name":"襄汾","fullname":"襄汾县","level":"district","filename":"140000/141000/141023"},{"code":141024,"name":"洪洞","fullname":"洪洞县","level":"district","filename":"140000/141000/141024"},{"code":141025,"name":"古县","fullname":"古县","level":"district","filename":"140000/141000/141025"},{"code":141026,"name":"安泽","fullname":"安泽县","level":"district","filename":"140000/141000/141026"},{"code":141027,"name":"浮山","fullname":"浮山县","level":"district","filename":"140000/141000/141027"},{"code":141028,"name":"吉县","fullname":"吉县","level":"district","filename":"140000/141000/141028"},{"code":141029,"name":"乡宁","fullname":"乡宁县","level":"district","filename":"140000/141000/141029"},{"code":141030,"name":"大宁","fullname":"大宁县","level":"district","filename":"140000/141000/141030"},{"code":141031,"name":"隰县","fullname":"隰县","level":"district","filename":"140000/141000/141031"},{"code":141032,"name":"永和","fullname":"永和县","level":"district","filename":"140000/141000/141032"},{"code":141033,"name":"蒲县","fullname":"蒲县","level":"district","filename":"140000/141000/141033"},{"code":141034,"name":"汾西","fullname":"汾西县","level":"district","filename":"140000/141000/141034"},{"code":141081,"name":"侯马","fullname":"侯马市","level":"district","filename":"140000/141000/141081"},{"code":141082,"name":"霍州","fullname":"霍州市","level":"district","filename":"140000/141000/141082"}]},{"code":141100,"name":"吕梁","fullname":"吕梁市","level":"city","filename":"140000/141100","children":[{"code":141102,"name":"离石","fullname":"离石区","level":"district","filename":"140000/141100/141102"},{"code":141121,"name":"文水","fullname":"文水县","level":"district","filename":"140000/141100/141121"},{"code":141122,"name":"交城","fullname":"交城县","level":"district","filename":"140000/141100/141122"},{"code":141123,"name":"兴县","fullname":"兴县","level":"district","filename":"140000/141100/141123"},{"code":141124,"name":"临县","fullname":"临县","level":"district","filename":"140000/141100/141124"},{"code":141125,"name":"柳林","fullname":"柳林县","level":"district","filename":"140000/141100/141125"},{"code":141126,"name":"石楼","fullname":"石楼县","level":"district","filename":"140000/141100/141126"},{"code":141127,"name":"岚县","fullname":"岚县","level":"district","filename":"140000/141100/141127"},{"code":141128,"name":"方山","fullname":"方山县","level":"district","filename":"140000/141100/141128"},{"code":141129,"name":"中阳","fullname":"中阳县","level":"district","filename":"140000/141100/141129"},{"code":141130,"name":"交口","fullname":"交口县","level":"district","filename":"140000/141100/141130"},{"code":141181,"name":"孝义","fullname":"孝义市","level":"district","filename":"140000/141100/141181"},{"code":141182,"name":"汾阳","fullname":"汾阳市","level":"district","filename":"140000/141100/141182"}]}]},{"code":150000,"name":"内蒙古","fullname":"内蒙古自治区","level":"province","filename":"150000","children":[{"code":150100,"name":"呼和浩特","fullname":"呼和浩特市","level":"city","filename":"150000/150100","children":[{"code":150102,"name":"新城","fullname":"新城区","level":"district","filename":"150000/150100/150102"},{"code":150103,"name":"回民","fullname":"回民区","level":"district","filename":"150000/150100/150103"},{"code":150104,"name":"玉泉","fullname":"玉泉区","level":"district","filename":"150000/150100/150104"},{"code":150105,"name":"赛罕","fullname":"赛罕区","level":"district","filename":"150000/150100/150105"},{"code":150121,"name":"土默特左","fullname":"土默特左旗","level":"district","filename":"150000/150100/150121"},{"code":150122,"name":"托克托","fullname":"托克托县","level":"district","filename":"150000/150100/150122"},{"code":150123,"name":"和林格尔","fullname":"和林格尔县","level":"district","filename":"150000/150100/150123"},{"code":150124,"name":"清水河","fullname":"清水河县","level":"district","filename":"150000/150100/150124"},{"code":150125,"name":"武川","fullname":"武川县","level":"district","filename":"150000/150100/150125"}]},{"code":150200,"name":"包头","fullname":"包头市","level":"city","filename":"150000/150200","children":[{"code":150202,"name":"东河","fullname":"东河区","level":"district","filename":"150000/150200/150202"},{"code":150203,"name":"昆都仑","fullname":"昆都仑区","level":"district","filename":"150000/150200/150203"},{"code":150204,"name":"青山","fullname":"青山区","level":"district","filename":"150000/150200/150204"},{"code":150205,"name":"石拐","fullname":"石拐区","level":"district","filename":"150000/150200/150205"},{"code":150206,"name":"白云鄂博","fullname":"白云鄂博矿区","level":"district","filename":"150000/150200/150206"},{"code":150207,"name":"九原","fullname":"九原区","level":"district","filename":"150000/150200/150207"},{"code":150221,"name":"土默特右","fullname":"土默特右旗","level":"district","filename":"150000/150200/150221"},{"code":150222,"name":"固阳","fullname":"固阳县","level":"district","filename":"150000/150200/150222"},{"code":150223,"name":"达尔罕茂明安","fullname":"达尔罕茂明安联合旗","level":"district","filename":"150000/150200/150223"}]},{"code":150300,"name":"乌海","fullname":"乌海市","level":"city","filename":"150000/150300","children":[{"code":150302,"name":"海勃湾","fullname":"海勃湾区","level":"district","filename":"150000/150300/150302"},{"code":150303,"name":"海南","fullname":"海南区","level":"district","filename":"150000/150300/150303"},{"code":150304,"name":"乌达","fullname":"乌达区","level":"district","filename":"150000/150300/150304"}]},{"code":150400,"name":"赤峰","fullname":"赤峰市","level":"city","filename":"150000/150400","children":[{"code":150402,"name":"红山","fullname":"红山区","level":"district","filename":"150000/150400/150402"},{"code":150403,"name":"元宝山","fullname":"元宝山区","level":"district","filename":"150000/150400/150403"},{"code":150404,"name":"松山","fullname":"松山区","level":"district","filename":"150000/150400/150404"},{"code":150421,"name":"阿鲁科尔沁","fullname":"阿鲁科尔沁旗","level":"district","filename":"150000/150400/150421"},{"code":150422,"name":"巴林左","fullname":"巴林左旗","level":"district","filename":"150000/150400/150422"},{"code":150423,"name":"巴林右","fullname":"巴林右旗","level":"district","filename":"150000/150400/150423"},{"code":150424,"name":"林西","fullname":"林西县","level":"district","filename":"150000/150400/150424"},{"code":150425,"name":"克什克腾","fullname":"克什克腾旗","level":"district","filename":"150000/150400/150425"},{"code":150426,"name":"翁牛特","fullname":"翁牛特旗","level":"district","filename":"150000/150400/150426"},{"code":150428,"name":"喀喇沁","fullname":"喀喇沁旗","level":"district","filename":"150000/150400/150428"},{"code":150429,"name":"宁城","fullname":"宁城县","level":"district","filename":"150000/150400/150429"},{"code":150430,"name":"敖汉","fullname":"敖汉旗","level":"district","filename":"150000/150400/150430"}]},{"code":150500,"name":"通辽","fullname":"通辽市","level":"city","filename":"150000/150500","children":[{"code":150502,"name":"科尔沁","fullname":"科尔沁区","level":"district","filename":"150000/150500/150502"},{"code":150521,"name":"科尔沁左翼中","fullname":"科尔沁左翼中旗","level":"district","filename":"150000/150500/150521"},{"code":150522,"name":"科尔沁左翼后","fullname":"科尔沁左翼后旗","level":"district","filename":"150000/150500/150522"},{"code":150523,"name":"开鲁","fullname":"开鲁县","level":"district","filename":"150000/150500/150523"},{"code":150524,"name":"库伦","fullname":"库伦旗","level":"district","filename":"150000/150500/150524"},{"code":150525,"name":"奈曼","fullname":"奈曼旗","level":"district","filename":"150000/150500/150525"},{"code":150526,"name":"扎鲁特","fullname":"扎鲁特旗","level":"district","filename":"150000/150500/150526"},{"code":150581,"name":"霍林郭勒","fullname":"霍林郭勒市","level":"district","filename":"150000/150500/150581"}]},{"code":150600,"name":"鄂尔多斯","fullname":"鄂尔多斯市","level":"city","filename":"150000/150600","children":[{"code":150602,"name":"东胜","fullname":"东胜区","level":"district","filename":"150000/150600/150602"},{"code":150603,"name":"康巴什","fullname":"康巴什区","level":"district","filename":"150000/150600/150603"},{"code":150621,"name":"达拉特","fullname":"达拉特旗","level":"district","filename":"150000/150600/150621"},{"code":150622,"name":"准格尔","fullname":"准格尔旗","level":"district","filename":"150000/150600/150622"},{"code":150623,"name":"鄂托克前","fullname":"鄂托克前旗","level":"district","filename":"150000/150600/150623"},{"code":150624,"name":"鄂托克","fullname":"鄂托克旗","level":"district","filename":"150000/150600/150624"},{"code":150625,"name":"杭锦","fullname":"杭锦旗","level":"district","filename":"150000/150600/150625"},{"code":150626,"name":"乌审","fullname":"乌审旗","level":"district","filename":"150000/150600/150626"},{"code":150627,"name":"伊金霍洛","fullname":"伊金霍洛旗","level":"district","filename":"150000/150600/150627"}]},{"code":150700,"name":"呼伦贝尔","fullname":"呼伦贝尔市","level":"city","filename":"150000/150700","children":[{"code":150702,"name":"海拉尔","fullname":"海拉尔区","level":"district","filename":"150000/150700/150702"},{"code":150703,"name":"扎赉诺尔","fullname":"扎赉诺尔区","level":"district","filename":"150000/150700/150703"},{"code":150721,"name":"阿荣","fullname":"阿荣旗","level":"district","filename":"150000/150700/150721"},{"code":150722,"name":"莫力达瓦","fullname":"莫力达瓦达斡尔族自治旗","level":"district","filename":"150000/150700/150722"},{"code":150723,"name":"鄂伦春","fullname":"鄂伦春自治旗","level":"district","filename":"150000/150700/150723"},{"code":150724,"name":"鄂温克族","fullname":"鄂温克族自治旗","level":"district","filename":"150000/150700/150724"},{"code":150725,"name":"陈巴尔虎","fullname":"陈巴尔虎旗","level":"district","filename":"150000/150700/150725"},{"code":150726,"name":"新巴尔虎左","fullname":"新巴尔虎左旗","level":"district","filename":"150000/150700/150726"},{"code":150727,"name":"新巴尔虎右","fullname":"新巴尔虎右旗","level":"district","filename":"150000/150700/150727"},{"code":150781,"name":"满洲里","fullname":"满洲里市","level":"district","filename":"150000/150700/150781"},{"code":150782,"name":"牙克石","fullname":"牙克石市","level":"district","filename":"150000/150700/150782"},{"code":150783,"name":"扎兰屯","fullname":"扎兰屯市","level":"district","filename":"150000/150700/150783"},{"code":150784,"name":"额尔古纳","fullname":"额尔古纳市","level":"district","filename":"150000/150700/150784"},{"code":150785,"name":"根河","fullname":"根河市","level":"district","filename":"150000/150700/150785"}]},{"code":150800,"name":"巴彦淖尔","fullname":"巴彦淖尔市","level":"city","filename":"150000/150800","children":[{"code":150802,"name":"临河","fullname":"临河区","level":"district","filename":"150000/150800/150802"},{"code":150821,"name":"五原","fullname":"五原县","level":"district","filename":"150000/150800/150821"},{"code":150822,"name":"磴口","fullname":"磴口县","level":"district","filename":"150000/150800/150822"},{"code":150823,"name":"乌拉特前","fullname":"乌拉特前旗","level":"district","filename":"150000/150800/150823"},{"code":150824,"name":"乌拉特中","fullname":"乌拉特中旗","level":"district","filename":"150000/150800/150824"},{"code":150825,"name":"乌拉特后","fullname":"乌拉特后旗","level":"district","filename":"150000/150800/150825"},{"code":150826,"name":"杭锦后","fullname":"杭锦后旗","level":"district","filename":"150000/150800/150826"}]},{"code":150900,"name":"乌兰察布","fullname":"乌兰察布市","level":"city","filename":"150000/150900","children":[{"code":150902,"name":"集宁","fullname":"集宁区","level":"district","filename":"150000/150900/150902"},{"code":150921,"name":"卓资","fullname":"卓资县","level":"district","filename":"150000/150900/150921"},{"code":150922,"name":"化德","fullname":"化德县","level":"district","filename":"150000/150900/150922"},{"code":150923,"name":"商都","fullname":"商都县","level":"district","filename":"150000/150900/150923"},{"code":150924,"name":"兴和","fullname":"兴和县","level":"district","filename":"150000/150900/150924"},{"code":150925,"name":"凉城","fullname":"凉城县","level":"district","filename":"150000/150900/150925"},{"code":150926,"name":"察哈尔右翼前","fullname":"察哈尔右翼前旗","level":"district","filename":"150000/150900/150926"},{"code":150927,"name":"察哈尔右翼中","fullname":"察哈尔右翼中旗","level":"district","filename":"150000/150900/150927"},{"code":150928,"name":"察哈尔右翼后","fullname":"察哈尔右翼后旗","level":"district","filename":"150000/150900/150928"},{"code":150929,"name":"四子王","fullname":"四子王旗","level":"district","filename":"150000/150900/150929"},{"code":150981,"name":"丰镇","fullname":"丰镇市","level":"district","filename":"150000/150900/150981"}]},{"code":152200,"name":"兴安","fullname":"兴安盟","level":"city","filename":"150000/152200","children":[{"code":152201,"name":"乌兰浩特","fullname":"乌兰浩特市","level":"district","filename":"150000/152200/152201"},{"code":152202,"name":"阿尔山","fullname":"阿尔山市","level":"district","filename":"150000/152200/152202"},{"code":152221,"name":"科尔沁右翼前","fullname":"科尔沁右翼前旗","level":"district","filename":"150000/152200/152221"},{"code":152222,"name":"科尔沁右翼中","fullname":"科尔沁右翼中旗","level":"district","filename":"150000/152200/152222"},{"code":152223,"name":"扎赉特","fullname":"扎赉特旗","level":"district","filename":"150000/152200/152223"},{"code":152224,"name":"突泉","fullname":"突泉县","level":"district","filename":"150000/152200/152224"}]},{"code":152500,"name":"锡林郭勒","fullname":"锡林郭勒盟","level":"city","filename":"150000/152500","children":[{"code":152501,"name":"二连浩特","fullname":"二连浩特市","level":"district","filename":"150000/152500/152501"},{"code":152502,"name":"锡林浩特","fullname":"锡林浩特市","level":"district","filename":"150000/152500/152502"},{"code":152522,"name":"阿巴嘎","fullname":"阿巴嘎旗","level":"district","filename":"150000/152500/152522"},{"code":152523,"name":"苏尼特左","fullname":"苏尼特左旗","level":"district","filename":"150000/152500/152523"},{"code":152524,"name":"苏尼特右","fullname":"苏尼特右旗","level":"district","filename":"150000/152500/152524"},{"code":152525,"name":"东乌珠穆沁","fullname":"东乌珠穆沁旗","level":"district","filename":"150000/152500/152525"},{"code":152526,"name":"西乌珠穆沁","fullname":"西乌珠穆沁旗","level":"district","filename":"150000/152500/152526"},{"code":152527,"name":"太仆寺","fullname":"太仆寺旗","level":"district","filename":"150000/152500/152527"},{"code":152528,"name":"镶黄","fullname":"镶黄旗","level":"district","filename":"150000/152500/152528"},{"code":152529,"name":"正镶白","fullname":"正镶白旗","level":"district","filename":"150000/152500/152529"},{"code":152530,"name":"正蓝","fullname":"正蓝旗","level":"district","filename":"150000/152500/152530"},{"code":152531,"name":"多伦","fullname":"多伦县","level":"district","filename":"150000/152500/152531"}]},{"code":152900,"name":"阿拉善","fullname":"阿拉善盟","level":"city","filename":"150000/152900","children":[{"code":152921,"name":"阿拉善左","fullname":"阿拉善左旗","level":"district","filename":"150000/152900/152921"},{"code":152922,"name":"阿拉善右","fullname":"阿拉善右旗","level":"district","filename":"150000/152900/152922"},{"code":152923,"name":"额济纳","fullname":"额济纳旗","level":"district","filename":"150000/152900/152923"}]}]},{"code":210000,"name":"辽宁","fullname":"辽宁省","level":"province","filename":"210000","children":[{"code":210100,"name":"沈阳","fullname":"沈阳市","level":"city","filename":"210000/210100","children":[{"code":210102,"name":"和平","fullname":"和平区","level":"district","filename":"210000/210100/210102"},{"code":210103,"name":"沈河","fullname":"沈河区","level":"district","filename":"210000/210100/210103"},{"code":210104,"name":"大东","fullname":"大东区","level":"district","filename":"210000/210100/210104"},{"code":210105,"name":"皇姑","fullname":"皇姑区","level":"district","filename":"210000/210100/210105"},{"code":210106,"name":"铁西","fullname":"铁西区","level":"district","filename":"210000/210100/210106"},{"code":210111,"name":"苏家屯","fullname":"苏家屯区","level":"district","filename":"210000/210100/210111"},{"code":210112,"name":"浑南","fullname":"浑南区","level":"district","filename":"210000/210100/210112"},{"code":210113,"name":"沈北","fullname":"沈北新区","level":"district","filename":"210000/210100/210113"},{"code":210114,"name":"于洪","fullname":"于洪区","level":"district","filename":"210000/210100/210114"},{"code":210115,"name":"辽中","fullname":"辽中区","level":"district","filename":"210000/210100/210115"},{"code":210123,"name":"康平","fullname":"康平县","level":"district","filename":"210000/210100/210123"},{"code":210124,"name":"法库","fullname":"法库县","level":"district","filename":"210000/210100/210124"},{"code":210181,"name":"新民","fullname":"新民市","level":"district","filename":"210000/210100/210181"}]},{"code":210200,"name":"大连","fullname":"大连市","level":"city","filename":"210000/210200","children":[{"code":210202,"name":"中山","fullname":"中山区","level":"district","filename":"210000/210200/210202"},{"code":210203,"name":"西岗","fullname":"西岗区","level":"district","filename":"210000/210200/210203"},{"code":210204,"name":"沙河口","fullname":"沙河口区","level":"district","filename":"210000/210200/210204"},{"code":210211,"name":"甘井子","fullname":"甘井子区","level":"district","filename":"210000/210200/210211"},{"code":210212,"name":"旅顺口","fullname":"旅顺口区","level":"district","filename":"210000/210200/210212"},{"code":210213,"name":"金州","fullname":"金州区","level":"district","filename":"210000/210200/210213"},{"code":210214,"name":"普兰店","fullname":"普兰店区","level":"district","filename":"210000/210200/210214"},{"code":210224,"name":"长海","fullname":"长海县","level":"district","filename":"210000/210200/210224"},{"code":210281,"name":"瓦房店","fullname":"瓦房店市","level":"district","filename":"210000/210200/210281"},{"code":210283,"name":"庄河","fullname":"庄河市","level":"district","filename":"210000/210200/210283"}]},{"code":210300,"name":"鞍山","fullname":"鞍山市","level":"city","filename":"210000/210300","children":[{"code":210302,"name":"铁东","fullname":"铁东区","level":"district","filename":"210000/210300/210302"},{"code":210303,"name":"铁西","fullname":"铁西区","level":"district","filename":"210000/210300/210303"},{"code":210304,"name":"立山","fullname":"立山区","level":"district","filename":"210000/210300/210304"},{"code":210311,"name":"千山","fullname":"千山区","level":"district","filename":"210000/210300/210311"},{"code":210321,"name":"台安","fullname":"台安县","level":"district","filename":"210000/210300/210321"},{"code":210323,"name":"岫岩","fullname":"岫岩满族自治县","level":"district","filename":"210000/210300/210323"},{"code":210381,"name":"海城","fullname":"海城市","level":"district","filename":"210000/210300/210381"}]},{"code":210400,"name":"抚顺","fullname":"抚顺市","level":"city","filename":"210000/210400","children":[{"code":210402,"name":"新抚","fullname":"新抚区","level":"district","filename":"210000/210400/210402"},{"code":210403,"name":"东洲","fullname":"东洲区","level":"district","filename":"210000/210400/210403"},{"code":210404,"name":"望花","fullname":"望花区","level":"district","filename":"210000/210400/210404"},{"code":210411,"name":"顺城","fullname":"顺城区","level":"district","filename":"210000/210400/210411"},{"code":210421,"name":"抚顺","fullname":"抚顺县","level":"district","filename":"210000/210400/210421"},{"code":210422,"name":"新宾","fullname":"新宾满族自治县","level":"district","filename":"210000/210400/210422"},{"code":210423,"name":"清原","fullname":"清原满族自治县","level":"district","filename":"210000/210400/210423"}]},{"code":210500,"name":"本溪","fullname":"本溪市","level":"city","filename":"210000/210500","children":[{"code":210502,"name":"平山","fullname":"平山区","level":"district","filename":"210000/210500/210502"},{"code":210503,"name":"溪湖","fullname":"溪湖区","level":"district","filename":"210000/210500/210503"},{"code":210504,"name":"明山","fullname":"明山区","level":"district","filename":"210000/210500/210504"},{"code":210505,"name":"南芬","fullname":"南芬区","level":"district","filename":"210000/210500/210505"},{"code":210521,"name":"本溪","fullname":"本溪满族自治县","level":"district","filename":"210000/210500/210521"},{"code":210522,"name":"桓仁","fullname":"桓仁满族自治县","level":"district","filename":"210000/210500/210522"}]},{"code":210600,"name":"丹东","fullname":"丹东市","level":"city","filename":"210000/210600","children":[{"code":210602,"name":"元宝","fullname":"元宝区","level":"district","filename":"210000/210600/210602"},{"code":210603,"name":"振兴","fullname":"振兴区","level":"district","filename":"210000/210600/210603"},{"code":210604,"name":"振安","fullname":"振安区","level":"district","filename":"210000/210600/210604"},{"code":210624,"name":"宽甸","fullname":"宽甸满族自治县","level":"district","filename":"210000/210600/210624"},{"code":210681,"name":"东港","fullname":"东港市","level":"district","filename":"210000/210600/210681"},{"code":210682,"name":"凤城","fullname":"凤城市","level":"district","filename":"210000/210600/210682"}]},{"code":210700,"name":"锦州","fullname":"锦州市","level":"city","filename":"210000/210700","children":[{"code":210702,"name":"古塔","fullname":"古塔区","level":"district","filename":"210000/210700/210702"},{"code":210703,"name":"凌河","fullname":"凌河区","level":"district","filename":"210000/210700/210703"},{"code":210711,"name":"太和","fullname":"太和区","level":"district","filename":"210000/210700/210711"},{"code":210726,"name":"黑山","fullname":"黑山县","level":"district","filename":"210000/210700/210726"},{"code":210727,"name":"义县","fullname":"义县","level":"district","filename":"210000/210700/210727"},{"code":210781,"name":"凌海","fullname":"凌海市","level":"district","filename":"210000/210700/210781"},{"code":210782,"name":"北镇","fullname":"北镇市","level":"district","filename":"210000/210700/210782"}]},{"code":210800,"name":"营口","fullname":"营口市","level":"city","filename":"210000/210800","children":[{"code":210802,"name":"站前","fullname":"站前区","level":"district","filename":"210000/210800/210802"},{"code":210803,"name":"西市区","fullname":"西市区","level":"district","filename":"210000/210800/210803"},{"code":210804,"name":"鲅鱼圈","fullname":"鲅鱼圈区","level":"district","filename":"210000/210800/210804"},{"code":210811,"name":"老边","fullname":"老边区","level":"district","filename":"210000/210800/210811"},{"code":210881,"name":"盖州","fullname":"盖州市","level":"district","filename":"210000/210800/210881"},{"code":210882,"name":"大石桥","fullname":"大石桥市","level":"district","filename":"210000/210800/210882"}]},{"code":210900,"name":"阜新","fullname":"阜新市","level":"city","filename":"210000/210900","children":[{"code":210902,"name":"海州","fullname":"海州区","level":"district","filename":"210000/210900/210902"},{"code":210903,"name":"新邱","fullname":"新邱区","level":"district","filename":"210000/210900/210903"},{"code":210904,"name":"太平","fullname":"太平区","level":"district","filename":"210000/210900/210904"},{"code":210905,"name":"清河门","fullname":"清河门区","level":"district","filename":"210000/210900/210905"},{"code":210911,"name":"细河","fullname":"细河区","level":"district","filename":"210000/210900/210911"},{"code":210921,"name":"阜新","fullname":"阜新蒙古族自治县","level":"district","filename":"210000/210900/210921"},{"code":210922,"name":"彰武","fullname":"彰武县","level":"district","filename":"210000/210900/210922"}]},{"code":211000,"name":"辽阳","fullname":"辽阳市","level":"city","filename":"210000/211000","children":[{"code":211002,"name":"白塔","fullname":"白塔区","level":"district","filename":"210000/211000/211002"},{"code":211003,"name":"文圣","fullname":"文圣区","level":"district","filename":"210000/211000/211003"},{"code":211004,"name":"宏伟","fullname":"宏伟区","level":"district","filename":"210000/211000/211004"},{"code":211005,"name":"弓长岭","fullname":"弓长岭区","level":"district","filename":"210000/211000/211005"},{"code":211011,"name":"太子河","fullname":"太子河区","level":"district","filename":"210000/211000/211011"},{"code":211021,"name":"辽阳","fullname":"辽阳县","level":"district","filename":"210000/211000/211021"},{"code":211081,"name":"灯塔","fullname":"灯塔市","level":"district","filename":"210000/211000/211081"}]},{"code":211100,"name":"盘锦","fullname":"盘锦市","level":"city","filename":"210000/211100","children":[{"code":211102,"name":"双台子","fullname":"双台子区","level":"district","filename":"210000/211100/211102"},{"code":211103,"name":"兴隆台","fullname":"兴隆台区","level":"district","filename":"210000/211100/211103"},{"code":211104,"name":"大洼","fullname":"大洼区","level":"district","filename":"210000/211100/211104"},{"code":211122,"name":"盘山","fullname":"盘山县","level":"district","filename":"210000/211100/211122"}]},{"code":211200,"name":"铁岭","fullname":"铁岭市","level":"city","filename":"210000/211200","children":[{"code":211202,"name":"银州","fullname":"银州区","level":"district","filename":"210000/211200/211202"},{"code":211204,"name":"清河","fullname":"清河区","level":"district","filename":"210000/211200/211204"},{"code":211221,"name":"铁岭","fullname":"铁岭县","level":"district","filename":"210000/211200/211221"},{"code":211223,"name":"西丰","fullname":"西丰县","level":"district","filename":"210000/211200/211223"},{"code":211224,"name":"昌图","fullname":"昌图县","level":"district","filename":"210000/211200/211224"},{"code":211281,"name":"调兵山","fullname":"调兵山市","level":"district","filename":"210000/211200/211281"},{"code":211282,"name":"开原","fullname":"开原市","level":"district","filename":"210000/211200/211282"}]},{"code":211300,"name":"朝阳","fullname":"朝阳市","level":"city","filename":"210000/211300","children":[{"code":211302,"name":"双塔","fullname":"双塔区","level":"district","filename":"210000/211300/211302"},{"code":211303,"name":"龙城","fullname":"龙城区","level":"district","filename":"210000/211300/211303"},{"code":211321,"name":"朝阳","fullname":"朝阳县","level":"district","filename":"210000/211300/211321"},{"code":211322,"name":"建平","fullname":"建平县","level":"district","filename":"210000/211300/211322"},{"code":211324,"name":"喀喇沁左翼","fullname":"喀喇沁左翼蒙古族自治县","level":"district","filename":"210000/211300/211324"},{"code":211381,"name":"北票","fullname":"北票市","level":"district","filename":"210000/211300/211381"},{"code":211382,"name":"凌源","fullname":"凌源市","level":"district","filename":"210000/211300/211382"}]},{"code":211400,"name":"葫芦岛","fullname":"葫芦岛市","level":"city","filename":"210000/211400","children":[{"code":211402,"name":"连山","fullname":"连山区","level":"district","filename":"210000/211400/211402"},{"code":211403,"name":"龙港","fullname":"龙港区","level":"district","filename":"210000/211400/211403"},{"code":211404,"name":"南票","fullname":"南票区","level":"district","filename":"210000/211400/211404"},{"code":211421,"name":"绥中","fullname":"绥中县","level":"district","filename":"210000/211400/211421"},{"code":211422,"name":"建昌","fullname":"建昌县","level":"district","filename":"210000/211400/211422"},{"code":211481,"name":"兴城","fullname":"兴城市","level":"district","filename":"210000/211400/211481"}]}]},{"code":220000,"name":"吉林","fullname":"吉林省","level":"province","filename":"220000","children":[{"code":220100,"name":"长春","fullname":"长春市","level":"city","filename":"220000/220100","children":[{"code":220102,"name":"南关","fullname":"南关区","level":"district","filename":"220000/220100/220102"},{"code":220103,"name":"宽城","fullname":"宽城区","level":"district","filename":"220000/220100/220103"},{"code":220104,"name":"朝阳","fullname":"朝阳区","level":"district","filename":"220000/220100/220104"},{"code":220105,"name":"二道","fullname":"二道区","level":"district","filename":"220000/220100/220105"},{"code":220106,"name":"绿园","fullname":"绿园区","level":"district","filename":"220000/220100/220106"},{"code":220112,"name":"双阳","fullname":"双阳区","level":"district","filename":"220000/220100/220112"},{"code":220113,"name":"九台","fullname":"九台区","level":"district","filename":"220000/220100/220113"},{"code":220122,"name":"农安","fullname":"农安县","level":"district","filename":"220000/220100/220122"},{"code":220182,"name":"榆树","fullname":"榆树市","level":"district","filename":"220000/220100/220182"},{"code":220183,"name":"德惠","fullname":"德惠市","level":"district","filename":"220000/220100/220183"},{"code":220184,"name":"公主岭","fullname":"公主岭市","level":"district","filename":"220000/220100/220184"}]},{"code":220200,"name":"吉林","fullname":"吉林市","level":"city","filename":"220000/220200","children":[{"code":220202,"name":"昌邑","fullname":"昌邑区","level":"district","filename":"220000/220200/220202"},{"code":220203,"name":"龙潭","fullname":"龙潭区","level":"district","filename":"220000/220200/220203"},{"code":220204,"name":"船营","fullname":"船营区","level":"district","filename":"220000/220200/220204"},{"code":220211,"name":"丰满","fullname":"丰满区","level":"district","filename":"220000/220200/220211"},{"code":220221,"name":"永吉","fullname":"永吉县","level":"district","filename":"220000/220200/220221"},{"code":220281,"name":"蛟河","fullname":"蛟河市","level":"district","filename":"220000/220200/220281"},{"code":220282,"name":"桦甸","fullname":"桦甸市","level":"district","filename":"220000/220200/220282"},{"code":220283,"name":"舒兰","fullname":"舒兰市","level":"district","filename":"220000/220200/220283"},{"code":220284,"name":"磐石","fullname":"磐石市","level":"district","filename":"220000/220200/220284"}]},{"code":220300,"name":"四平","fullname":"四平市","level":"city","filename":"220000/220300","children":[{"code":220302,"name":"铁西","fullname":"铁西区","level":"district","filename":"220000/220300/220302"},{"code":220303,"name":"铁东","fullname":"铁东区","level":"district","filename":"220000/220300/220303"},{"code":220322,"name":"梨树","fullname":"梨树县","level":"district","filename":"220000/220300/220322"},{"code":220323,"name":"伊通","fullname":"伊通满族自治县","level":"district","filename":"220000/220300/220323"},{"code":220382,"name":"双辽","fullname":"双辽市","level":"district","filename":"220000/220300/220382"}]},{"code":220400,"name":"辽源","fullname":"辽源市","level":"city","filename":"220000/220400","children":[{"code":220402,"name":"龙山","fullname":"龙山区","level":"district","filename":"220000/220400/220402"},{"code":220403,"name":"西安","fullname":"西安区","level":"district","filename":"220000/220400/220403"},{"code":220421,"name":"东丰","fullname":"东丰县","level":"district","filename":"220000/220400/220421"},{"code":220422,"name":"东辽","fullname":"东辽县","level":"district","filename":"220000/220400/220422"}]},{"code":220500,"name":"通化","fullname":"通化市","level":"city","filename":"220000/220500","children":[{"code":220502,"name":"东昌","fullname":"东昌区","level":"district","filename":"220000/220500/220502"},{"code":220503,"name":"二道江","fullname":"二道江区","level":"district","filename":"220000/220500/220503"},{"code":220521,"name":"通化","fullname":"通化县","level":"district","filename":"220000/220500/220521"},{"code":220523,"name":"辉南","fullname":"辉南县","level":"district","filename":"220000/220500/220523"},{"code":220524,"name":"柳河","fullname":"柳河县","level":"district","filename":"220000/220500/220524"},{"code":220581,"name":"梅河口","fullname":"梅河口市","level":"district","filename":"220000/220500/220581"},{"code":220582,"name":"集安","fullname":"集安市","level":"district","filename":"220000/220500/220582"}]},{"code":220600,"name":"白山","fullname":"白山市","level":"city","filename":"220000/220600","children":[{"code":220602,"name":"浑江","fullname":"浑江区","level":"district","filename":"220000/220600/220602"},{"code":220605,"name":"江源","fullname":"江源区","level":"district","filename":"220000/220600/220605"},{"code":220621,"name":"抚松","fullname":"抚松县","level":"district","filename":"220000/220600/220621"},{"code":220622,"name":"靖宇","fullname":"靖宇县","level":"district","filename":"220000/220600/220622"},{"code":220623,"name":"长白","fullname":"长白朝鲜族自治县","level":"district","filename":"220000/220600/220623"},{"code":220681,"name":"临江","fullname":"临江市","level":"district","filename":"220000/220600/220681"}]},{"code":220700,"name":"松原","fullname":"松原市","level":"city","filename":"220000/220700","children":[{"code":220702,"name":"宁江","fullname":"宁江区","level":"district","filename":"220000/220700/220702"},{"code":220721,"name":"前郭尔罗斯","fullname":"前郭尔罗斯蒙古族自治县","level":"district","filename":"220000/220700/220721"},{"code":220722,"name":"长岭","fullname":"长岭县","level":"district","filename":"220000/220700/220722"},{"code":220723,"name":"乾安","fullname":"乾安县","level":"district","filename":"220000/220700/220723"},{"code":220781,"name":"扶余","fullname":"扶余市","level":"district","filename":"220000/220700/220781"}]},{"code":220800,"name":"白城","fullname":"白城市","level":"city","filename":"220000/220800","children":[{"code":220802,"name":"洮北","fullname":"洮北区","level":"district","filename":"220000/220800/220802"},{"code":220821,"name":"镇赉","fullname":"镇赉县","level":"district","filename":"220000/220800/220821"},{"code":220822,"name":"通榆","fullname":"通榆县","level":"district","filename":"220000/220800/220822"},{"code":220881,"name":"洮南","fullname":"洮南市","level":"district","filename":"220000/220800/220881"},{"code":220882,"name":"大安","fullname":"大安市","level":"district","filename":"220000/220800/220882"}]},{"code":222400,"name":"延边","fullname":"延边朝鲜族自治州","level":"city","filename":"220000/222400","children":[{"code":222401,"name":"延吉","fullname":"延吉市","level":"district","filename":"220000/222400/222401"},{"code":222402,"name":"图们","fullname":"图们市","level":"district","filename":"220000/222400/222402"},{"code":222403,"name":"敦化","fullname":"敦化市","level":"district","filename":"220000/222400/222403"},{"code":222404,"name":"珲春","fullname":"珲春市","level":"district","filename":"220000/222400/222404"},{"code":222405,"name":"龙井","fullname":"龙井市","level":"district","filename":"220000/222400/222405"},{"code":222406,"name":"和龙","fullname":"和龙市","level":"district","filename":"220000/222400/222406"},{"code":222424,"name":"汪清","fullname":"汪清县","level":"district","filename":"220000/222400/222424"},{"code":222426,"name":"安图","fullname":"安图县","level":"district","filename":"220000/222400/222426"}]}]},{"code":230000,"name":"黑龙江","fullname":"黑龙江省","level":"province","filename":"230000","children":[{"code":230100,"name":"哈尔滨","fullname":"哈尔滨市","level":"city","filename":"230000/230100","children":[{"code":230102,"name":"道里","fullname":"道里区","level":"district","filename":"230000/230100/230102"},{"code":230103,"name":"南岗","fullname":"南岗区","level":"district","filename":"230000/230100/230103"},{"code":230104,"name":"道外","fullname":"道外区","level":"district","filename":"230000/230100/230104"},{"code":230108,"name":"平房","fullname":"平房区","level":"district","filename":"230000/230100/230108"},{"code":230109,"name":"松北","fullname":"松北区","level":"district","filename":"230000/230100/230109"},{"code":230110,"name":"香坊","fullname":"香坊区","level":"district","filename":"230000/230100/230110"},{"code":230111,"name":"呼兰","fullname":"呼兰区","level":"district","filename":"230000/230100/230111"},{"code":230112,"name":"阿城","fullname":"阿城区","level":"district","filename":"230000/230100/230112"},{"code":230113,"name":"双城","fullname":"双城区","level":"district","filename":"230000/230100/230113"},{"code":230123,"name":"依兰","fullname":"依兰县","level":"district","filename":"230000/230100/230123"},{"code":230124,"name":"方正","fullname":"方正县","level":"district","filename":"230000/230100/230124"},{"code":230125,"name":"宾县","fullname":"宾县","level":"district","filename":"230000/230100/230125"},{"code":230126,"name":"巴彦","fullname":"巴彦县","level":"district","filename":"230000/230100/230126"},{"code":230127,"name":"木兰","fullname":"木兰县","level":"district","filename":"230000/230100/230127"},{"code":230128,"name":"通河","fullname":"通河县","level":"district","filename":"230000/230100/230128"},{"code":230129,"name":"延寿","fullname":"延寿县","level":"district","filename":"230000/230100/230129"},{"code":230183,"name":"尚志","fullname":"尚志市","level":"district","filename":"230000/230100/230183"},{"code":230184,"name":"五常","fullname":"五常市","level":"district","filename":"230000/230100/230184"}]},{"code":230200,"name":"齐齐哈尔","fullname":"齐齐哈尔市","level":"city","filename":"230000/230200","children":[{"code":230202,"name":"龙沙","fullname":"龙沙区","level":"district","filename":"230000/230200/230202"},{"code":230203,"name":"建华","fullname":"建华区","level":"district","filename":"230000/230200/230203"},{"code":230204,"name":"铁锋","fullname":"铁锋区","level":"district","filename":"230000/230200/230204"},{"code":230205,"name":"昂昂溪","fullname":"昂昂溪区","level":"district","filename":"230000/230200/230205"},{"code":230206,"name":"富拉尔基","fullname":"富拉尔基区","level":"district","filename":"230000/230200/230206"},{"code":230207,"name":"碾子山","fullname":"碾子山区","level":"district","filename":"230000/230200/230207"},{"code":230208,"name":"梅里斯","fullname":"梅里斯达斡尔族区","level":"district","filename":"230000/230200/230208"},{"code":230221,"name":"龙江","fullname":"龙江县","level":"district","filename":"230000/230200/230221"},{"code":230223,"name":"依安","fullname":"依安县","level":"district","filename":"230000/230200/230223"},{"code":230224,"name":"泰来","fullname":"泰来县","level":"district","filename":"230000/230200/230224"},{"code":230225,"name":"甘南","fullname":"甘南县","level":"district","filename":"230000/230200/230225"},{"code":230227,"name":"富裕","fullname":"富裕县","level":"district","filename":"230000/230200/230227"},{"code":230229,"name":"克山","fullname":"克山县","level":"district","filename":"230000/230200/230229"},{"code":230230,"name":"克东","fullname":"克东县","level":"district","filename":"230000/230200/230230"},{"code":230231,"name":"拜泉","fullname":"拜泉县","level":"district","filename":"230000/230200/230231"},{"code":230281,"name":"讷河","fullname":"讷河市","level":"district","filename":"230000/230200/230281"}]},{"code":230300,"name":"鸡西","fullname":"鸡西市","level":"city","filename":"230000/230300","children":[{"code":230302,"name":"鸡冠","fullname":"鸡冠区","level":"district","filename":"230000/230300/230302"},{"code":230303,"name":"恒山","fullname":"恒山区","level":"district","filename":"230000/230300/230303"},{"code":230304,"name":"滴道","fullname":"滴道区","level":"district","filename":"230000/230300/230304"},{"code":230305,"name":"梨树","fullname":"梨树区","level":"district","filename":"230000/230300/230305"},{"code":230306,"name":"城子河","fullname":"城子河区","level":"district","filename":"230000/230300/230306"},{"code":230307,"name":"麻山","fullname":"麻山区","level":"district","filename":"230000/230300/230307"},{"code":230321,"name":"鸡东","fullname":"鸡东县","level":"district","filename":"230000/230300/230321"},{"code":230381,"name":"虎林","fullname":"虎林市","level":"district","filename":"230000/230300/230381"},{"code":230382,"name":"密山","fullname":"密山市","level":"district","filename":"230000/230300/230382"}]},{"code":230400,"name":"鹤岗","fullname":"鹤岗市","level":"city","filename":"230000/230400","children":[{"code":230402,"name":"向阳","fullname":"向阳区","level":"district","filename":"230000/230400/230402"},{"code":230403,"name":"工农","fullname":"工农区","level":"district","filename":"230000/230400/230403"},{"code":230404,"name":"南山","fullname":"南山区","level":"district","filename":"230000/230400/230404"},{"code":230405,"name":"兴安","fullname":"兴安区","level":"district","filename":"230000/230400/230405"},{"code":230406,"name":"东山","fullname":"东山区","level":"district","filename":"230000/230400/230406"},{"code":230407,"name":"兴山","fullname":"兴山区","level":"district","filename":"230000/230400/230407"},{"code":230421,"name":"萝北","fullname":"萝北县","level":"district","filename":"230000/230400/230421"},{"code":230422,"name":"绥滨","fullname":"绥滨县","level":"district","filename":"230000/230400/230422"}]},{"code":230500,"name":"双鸭山","fullname":"双鸭山市","level":"city","filename":"230000/230500","children":[{"code":230502,"name":"尖山","fullname":"尖山区","level":"district","filename":"230000/230500/230502"},{"code":230503,"name":"岭东","fullname":"岭东区","level":"district","filename":"230000/230500/230503"},{"code":230505,"name":"四方台","fullname":"四方台区","level":"district","filename":"230000/230500/230505"},{"code":230506,"name":"宝山","fullname":"宝山区","level":"district","filename":"230000/230500/230506"},{"code":230521,"name":"集贤","fullname":"集贤县","level":"district","filename":"230000/230500/230521"},{"code":230522,"name":"友谊","fullname":"友谊县","level":"district","filename":"230000/230500/230522"},{"code":230523,"name":"宝清","fullname":"宝清县","level":"district","filename":"230000/230500/230523"},{"code":230524,"name":"饶河","fullname":"饶河县","level":"district","filename":"230000/230500/230524"}]},{"code":230600,"name":"大庆","fullname":"大庆市","level":"city","filename":"230000/230600","children":[{"code":230602,"name":"萨尔图","fullname":"萨尔图区","level":"district","filename":"230000/230600/230602"},{"code":230603,"name":"龙凤","fullname":"龙凤区","level":"district","filename":"230000/230600/230603"},{"code":230604,"name":"让胡路","fullname":"让胡路区","level":"district","filename":"230000/230600/230604"},{"code":230605,"name":"红岗","fullname":"红岗区","level":"district","filename":"230000/230600/230605"},{"code":230606,"name":"大同","fullname":"大同区","level":"district","filename":"230000/230600/230606"},{"code":230621,"name":"肇州","fullname":"肇州县","level":"district","filename":"230000/230600/230621"},{"code":230622,"name":"肇源","fullname":"肇源县","level":"district","filename":"230000/230600/230622"},{"code":230623,"name":"林甸","fullname":"林甸县","level":"district","filename":"230000/230600/230623"},{"code":230624,"name":"杜尔伯特","fullname":"杜尔伯特蒙古族自治县","level":"district","filename":"230000/230600/230624"}]},{"code":230700,"name":"伊春","fullname":"伊春市","level":"city","filename":"230000/230700","children":[{"code":230717,"name":"伊美","fullname":"伊美区","level":"district","filename":"230000/230700/230717"},{"code":230718,"name":"乌翠","fullname":"乌翠区","level":"district","filename":"230000/230700/230718"},{"code":230719,"name":"友好","fullname":"友好区","level":"district","filename":"230000/230700/230719"},{"code":230722,"name":"嘉荫","fullname":"嘉荫县","level":"district","filename":"230000/230700/230722"},{"code":230723,"name":"汤旺","fullname":"汤旺县","level":"district","filename":"230000/230700/230723"},{"code":230724,"name":"丰林","fullname":"丰林县","level":"district","filename":"230000/230700/230724"},{"code":230725,"name":"大箐山","fullname":"大箐山县","level":"district","filename":"230000/230700/230725"},{"code":230726,"name":"南岔","fullname":"南岔县","level":"district","filename":"230000/230700/230726"},{"code":230751,"name":"金林","fullname":"金林区","level":"district","filename":"230000/230700/230751"},{"code":230781,"name":"铁力","fullname":"铁力市","level":"district","filename":"230000/230700/230781"}]},{"code":230800,"name":"佳木斯","fullname":"佳木斯市","level":"city","filename":"230000/230800","children":[{"code":230803,"name":"向阳","fullname":"向阳区","level":"district","filename":"230000/230800/230803"},{"code":230804,"name":"前进","fullname":"前进区","level":"district","filename":"230000/230800/230804"},{"code":230805,"name":"东风","fullname":"东风区","level":"district","filename":"230000/230800/230805"},{"code":230811,"name":"郊区","fullname":"郊区","level":"district","filename":"230000/230800/230811"},{"code":230822,"name":"桦南","fullname":"桦南县","level":"district","filename":"230000/230800/230822"},{"code":230826,"name":"桦川","fullname":"桦川县","level":"district","filename":"230000/230800/230826"},{"code":230828,"name":"汤原","fullname":"汤原县","level":"district","filename":"230000/230800/230828"},{"code":230881,"name":"同江","fullname":"同江市","level":"district","filename":"230000/230800/230881"},{"code":230882,"name":"富锦","fullname":"富锦市","level":"district","filename":"230000/230800/230882"},{"code":230883,"name":"抚远","fullname":"抚远市","level":"district","filename":"230000/230800/230883"}]},{"code":230900,"name":"七台河","fullname":"七台河市","level":"city","filename":"230000/230900","children":[{"code":230902,"name":"新兴","fullname":"新兴区","level":"district","filename":"230000/230900/230902"},{"code":230903,"name":"桃山","fullname":"桃山区","level":"district","filename":"230000/230900/230903"},{"code":230904,"name":"茄子河","fullname":"茄子河区","level":"district","filename":"230000/230900/230904"},{"code":230921,"name":"勃利","fullname":"勃利县","level":"district","filename":"230000/230900/230921"}]},{"code":231000,"name":"牡丹江","fullname":"牡丹江市","level":"city","filename":"230000/231000","children":[{"code":231002,"name":"东安","fullname":"东安区","level":"district","filename":"230000/231000/231002"},{"code":231003,"name":"阳明","fullname":"阳明区","level":"district","filename":"230000/231000/231003"},{"code":231004,"name":"爱民","fullname":"爱民区","level":"district","filename":"230000/231000/231004"},{"code":231005,"name":"西安","fullname":"西安区","level":"district","filename":"230000/231000/231005"},{"code":231025,"name":"林口","fullname":"林口县","level":"district","filename":"230000/231000/231025"},{"code":231081,"name":"绥芬河","fullname":"绥芬河市","level":"district","filename":"230000/231000/231081"},{"code":231083,"name":"海林","fullname":"海林市","level":"district","filename":"230000/231000/231083"},{"code":231084,"name":"宁安","fullname":"宁安市","level":"district","filename":"230000/231000/231084"},{"code":231085,"name":"穆棱","fullname":"穆棱市","level":"district","filename":"230000/231000/231085"},{"code":231086,"name":"东宁","fullname":"东宁市","level":"district","filename":"230000/231000/231086"}]},{"code":231100,"name":"黑河","fullname":"黑河市","level":"city","filename":"230000/231100","children":[{"code":231102,"name":"爱辉","fullname":"爱辉区","level":"district","filename":"230000/231100/231102"},{"code":231123,"name":"逊克","fullname":"逊克县","level":"district","filename":"230000/231100/231123"},{"code":231124,"name":"孙吴","fullname":"孙吴县","level":"district","filename":"230000/231100/231124"},{"code":231181,"name":"北安","fullname":"北安市","level":"district","filename":"230000/231100/231181"},{"code":231182,"name":"五大连池","fullname":"五大连池市","level":"district","filename":"230000/231100/231182"},{"code":231183,"name":"嫩江","fullname":"嫩江市","level":"district","filename":"230000/231100/231183"}]},{"code":231200,"name":"绥化","fullname":"绥化市","level":"city","filename":"230000/231200","children":[{"code":231202,"name":"北林","fullname":"北林区","level":"district","filename":"230000/231200/231202"},{"code":231221,"name":"望奎","fullname":"望奎县","level":"district","filename":"230000/231200/231221"},{"code":231222,"name":"兰西","fullname":"兰西县","level":"district","filename":"230000/231200/231222"},{"code":231223,"name":"青冈","fullname":"青冈县","level":"district","filename":"230000/231200/231223"},{"code":231224,"name":"庆安","fullname":"庆安县","level":"district","filename":"230000/231200/231224"},{"code":231225,"name":"明水","fullname":"明水县","level":"district","filename":"230000/231200/231225"},{"code":231226,"name":"绥棱","fullname":"绥棱县","level":"district","filename":"230000/231200/231226"},{"code":231281,"name":"安达","fullname":"安达市","level":"district","filename":"230000/231200/231281"},{"code":231282,"name":"肇东","fullname":"肇东市","level":"district","filename":"230000/231200/231282"},{"code":231283,"name":"海伦","fullname":"海伦市","level":"district","filename":"230000/231200/231283"}]},{"code":232700,"name":"大兴安岭","fullname":"大兴安岭地区","level":"city","filename":"230000/232700","children":[{"code":232701,"name":"漠河","fullname":"漠河市","level":"district","filename":"230000/232700/232701"},{"code":232718,"name":"加格达奇","fullname":"加格达奇区","level":"district","filename":"230000/232700/232718"},{"code":232721,"name":"呼玛","fullname":"呼玛县","level":"district","filename":"230000/232700/232721"},{"code":232722,"name":"塔河","fullname":"塔河县","level":"district","filename":"230000/232700/232722"}]}]},{"code":310000,"name":"上海","fullname":"上海市","level":"province","filename":"310000","children":[{"code":310101,"name":"黄浦","fullname":"黄浦区","level":"district","filename":"310000/310101"},{"code":310104,"name":"徐汇","fullname":"徐汇区","level":"district","filename":"310000/310104"},{"code":310105,"name":"长宁","fullname":"长宁区","level":"district","filename":"310000/310105"},{"code":310106,"name":"静安","fullname":"静安区","level":"district","filename":"310000/310106"},{"code":310107,"name":"普陀","fullname":"普陀区","level":"district","filename":"310000/310107"},{"code":310109,"name":"虹口","fullname":"虹口区","level":"district","filename":"310000/310109"},{"code":310110,"name":"杨浦","fullname":"杨浦区","level":"district","filename":"310000/310110"},{"code":310112,"name":"闵行","fullname":"闵行区","level":"district","filename":"310000/310112"},{"code":310113,"name":"宝山","fullname":"宝山区","level":"district","filename":"310000/310113"},{"code":310114,"name":"嘉定","fullname":"嘉定区","level":"district","filename":"310000/310114"},{"code":310115,"name":"浦东","fullname":"浦东新区","level":"district","filename":"310000/310115"},{"code":310116,"name":"金山","fullname":"金山区","level":"district","filename":"310000/310116"},{"code":310117,"name":"松江","fullname":"松江区","level":"district","filename":"310000/310117"},{"code":310118,"name":"青浦","fullname":"青浦区","level":"district","filename":"310000/310118"},{"code":310120,"name":"奉贤","fullname":"奉贤区","level":"district","filename":"310000/310120"},{"code":310151,"name":"崇明","fullname":"崇明区","level":"district","filename":"310000/310151"}]},{"code":320000,"name":"江苏","fullname":"江苏省","level":"province","filename":"320000","children":[{"code":320100,"name":"南京","fullname":"南京市","level":"city","filename":"320000/320100","children":[{"code":320102,"name":"玄武","fullname":"玄武区","level":"district","filename":"320000/320100/320102"},{"code":320104,"name":"秦淮","fullname":"秦淮区","level":"district","filename":"320000/320100/320104"},{"code":320105,"name":"建邺","fullname":"建邺区","level":"district","filename":"320000/320100/320105"},{"code":320106,"name":"鼓楼","fullname":"鼓楼区","level":"district","filename":"320000/320100/320106"},{"code":320111,"name":"浦口","fullname":"浦口区","level":"district","filename":"320000/320100/320111"},{"code":320113,"name":"栖霞","fullname":"栖霞区","level":"district","filename":"320000/320100/320113"},{"code":320114,"name":"雨花台","fullname":"雨花台区","level":"district","filename":"320000/320100/320114"},{"code":320115,"name":"江宁","fullname":"江宁区","level":"district","filename":"320000/320100/320115"},{"code":320116,"name":"六合","fullname":"六合区","level":"district","filename":"320000/320100/320116"},{"code":320117,"name":"溧水","fullname":"溧水区","level":"district","filename":"320000/320100/320117"},{"code":320118,"name":"高淳","fullname":"高淳区","level":"district","filename":"320000/320100/320118"}]},{"code":320200,"name":"无锡","fullname":"无锡市","level":"city","filename":"320000/320200","children":[{"code":320205,"name":"锡山","fullname":"锡山区","level":"district","filename":"320000/320200/320205"},{"code":320206,"name":"惠山","fullname":"惠山区","level":"district","filename":"320000/320200/320206"},{"code":320211,"name":"滨湖","fullname":"滨湖区","level":"district","filename":"320000/320200/320211"},{"code":320213,"name":"梁溪","fullname":"梁溪区","level":"district","filename":"320000/320200/320213"},{"code":320214,"name":"新吴","fullname":"新吴区","level":"district","filename":"320000/320200/320214"},{"code":320281,"name":"江阴","fullname":"江阴市","level":"district","filename":"320000/320200/320281"},{"code":320282,"name":"宜兴","fullname":"宜兴市","level":"district","filename":"320000/320200/320282"}]},{"code":320300,"name":"徐州","fullname":"徐州市","level":"city","filename":"320000/320300","children":[{"code":320302,"name":"鼓楼","fullname":"鼓楼区","level":"district","filename":"320000/320300/320302"},{"code":320303,"name":"云龙","fullname":"云龙区","level":"district","filename":"320000/320300/320303"},{"code":320305,"name":"贾汪","fullname":"贾汪区","level":"district","filename":"320000/320300/320305"},{"code":320311,"name":"泉山","fullname":"泉山区","level":"district","filename":"320000/320300/320311"},{"code":320312,"name":"铜山","fullname":"铜山区","level":"district","filename":"320000/320300/320312"},{"code":320321,"name":"丰县","fullname":"丰县","level":"district","filename":"320000/320300/320321"},{"code":320322,"name":"沛县","fullname":"沛县","level":"district","filename":"320000/320300/320322"},{"code":320324,"name":"睢宁","fullname":"睢宁县","level":"district","filename":"320000/320300/320324"},{"code":320381,"name":"新沂","fullname":"新沂市","level":"district","filename":"320000/320300/320381"},{"code":320382,"name":"邳州","fullname":"邳州市","level":"district","filename":"320000/320300/320382"}]},{"code":320400,"name":"常州","fullname":"常州市","level":"city","filename":"320000/320400","children":[{"code":320402,"name":"天宁","fullname":"天宁区","level":"district","filename":"320000/320400/320402"},{"code":320404,"name":"钟楼","fullname":"钟楼区","level":"district","filename":"320000/320400/320404"},{"code":320411,"name":"新北","fullname":"新北区","level":"district","filename":"320000/320400/320411"},{"code":320412,"name":"武进","fullname":"武进区","level":"district","filename":"320000/320400/320412"},{"code":320413,"name":"金坛","fullname":"金坛区","level":"district","filename":"320000/320400/320413"},{"code":320481,"name":"溧阳","fullname":"溧阳市","level":"district","filename":"320000/320400/320481"}]},{"code":320500,"name":"苏州","fullname":"苏州市","level":"city","filename":"320000/320500","children":[{"code":320505,"name":"虎丘","fullname":"虎丘区","level":"district","filename":"320000/320500/320505"},{"code":320506,"name":"吴中","fullname":"吴中区","level":"district","filename":"320000/320500/320506"},{"code":320507,"name":"相城","fullname":"相城区","level":"district","filename":"320000/320500/320507"},{"code":320508,"name":"姑苏","fullname":"姑苏区","level":"district","filename":"320000/320500/320508"},{"code":320509,"name":"吴江","fullname":"吴江区","level":"district","filename":"320000/320500/320509"},{"code":320581,"name":"常熟","fullname":"常熟市","level":"district","filename":"320000/320500/320581"},{"code":320582,"name":"张家港","fullname":"张家港市","level":"district","filename":"320000/320500/320582"},{"code":320583,"name":"昆山","fullname":"昆山市","level":"district","filename":"320000/320500/320583"},{"code":320585,"name":"太仓","fullname":"太仓市","level":"district","filename":"320000/320500/320585"}]},{"code":320600,"name":"南通","fullname":"南通市","level":"city","filename":"320000/320600","children":[{"code":320602,"name":"崇川","fullname":"崇川区","level":"district","filename":"320000/320600/320602"},{"code":320612,"name":"通州","fullname":"通州区","level":"district","filename":"320000/320600/320612"},{"code":320623,"name":"如东","fullname":"如东县","level":"district","filename":"320000/320600/320623"},{"code":320681,"name":"启东","fullname":"启东市","level":"district","filename":"320000/320600/320681"},{"code":320682,"name":"如皋","fullname":"如皋市","level":"district","filename":"320000/320600/320682"},{"code":320684,"name":"海门","fullname":"海门区","level":"district","filename":"320000/320600/320684"},{"code":320685,"name":"海安","fullname":"海安市","level":"district","filename":"320000/320600/320685"}]},{"code":320700,"name":"连云港","fullname":"连云港市","level":"city","filename":"320000/320700","children":[{"code":320703,"name":"连云","fullname":"连云区","level":"district","filename":"320000/320700/320703"},{"code":320706,"name":"海州","fullname":"海州区","level":"district","filename":"320000/320700/320706"},{"code":320707,"name":"赣榆","fullname":"赣榆区","level":"district","filename":"320000/320700/320707"},{"code":320722,"name":"东海","fullname":"东海县","level":"district","filename":"320000/320700/320722"},{"code":320723,"name":"灌云","fullname":"灌云县","level":"district","filename":"320000/320700/320723"},{"code":320724,"name":"灌南","fullname":"灌南县","level":"district","filename":"320000/320700/320724"}]},{"code":320800,"name":"淮安","fullname":"淮安市","level":"city","filename":"320000/320800","children":[{"code":320803,"name":"淮安","fullname":"淮安区","level":"district","filename":"320000/320800/320803"},{"code":320804,"name":"淮阴","fullname":"淮阴区","level":"district","filename":"320000/320800/320804"},{"code":320812,"name":"清江浦","fullname":"清江浦区","level":"district","filename":"320000/320800/320812"},{"code":320813,"name":"洪泽","fullname":"洪泽区","level":"district","filename":"320000/320800/320813"},{"code":320826,"name":"涟水","fullname":"涟水县","level":"district","filename":"320000/320800/320826"},{"code":320830,"name":"盱眙","fullname":"盱眙县","level":"district","filename":"320000/320800/320830"},{"code":320831,"name":"金湖","fullname":"金湖县","level":"district","filename":"320000/320800/320831"}]},{"code":320900,"name":"盐城","fullname":"盐城市","level":"city","filename":"320000/320900","children":[{"code":320902,"name":"亭湖","fullname":"亭湖区","level":"district","filename":"320000/320900/320902"},{"code":320903,"name":"盐都","fullname":"盐都区","level":"district","filename":"320000/320900/320903"},{"code":320904,"name":"大丰","fullname":"大丰区","level":"district","filename":"320000/320900/320904"},{"code":320921,"name":"响水","fullname":"响水县","level":"district","filename":"320000/320900/320921"},{"code":320922,"name":"滨海","fullname":"滨海县","level":"district","filename":"320000/320900/320922"},{"code":320923,"name":"阜宁","fullname":"阜宁县","level":"district","filename":"320000/320900/320923"},{"code":320924,"name":"射阳","fullname":"射阳县","level":"district","filename":"320000/320900/320924"},{"code":320925,"name":"建湖","fullname":"建湖县","level":"district","filename":"320000/320900/320925"},{"code":320981,"name":"东台","fullname":"东台市","level":"district","filename":"320000/320900/320981"}]},{"code":321000,"name":"扬州","fullname":"扬州市","level":"city","filename":"320000/321000","children":[{"code":321002,"name":"广陵","fullname":"广陵区","level":"district","filename":"320000/321000/321002"},{"code":321003,"name":"邗江","fullname":"邗江区","level":"district","filename":"320000/321000/321003"},{"code":321012,"name":"江都","fullname":"江都区","level":"district","filename":"320000/321000/321012"},{"code":321023,"name":"宝应","fullname":"宝应县","level":"district","filename":"320000/321000/321023"},{"code":321081,"name":"仪征","fullname":"仪征市","level":"district","filename":"320000/321000/321081"},{"code":321084,"name":"高邮","fullname":"高邮市","level":"district","filename":"320000/321000/321084"}]},{"code":321100,"name":"镇江","fullname":"镇江市","level":"city","filename":"320000/321100","children":[{"code":321102,"name":"京口","fullname":"京口区","level":"district","filename":"320000/321100/321102"},{"code":321111,"name":"润州","fullname":"润州区","level":"district","filename":"320000/321100/321111"},{"code":321112,"name":"丹徒","fullname":"丹徒区","level":"district","filename":"320000/321100/321112"},{"code":321181,"name":"丹阳","fullname":"丹阳市","level":"district","filename":"320000/321100/321181"},{"code":321182,"name":"扬中","fullname":"扬中市","level":"district","filename":"320000/321100/321182"},{"code":321183,"name":"句容","fullname":"句容市","level":"district","filename":"320000/321100/321183"}]},{"code":321200,"name":"泰州","fullname":"泰州市","level":"city","filename":"320000/321200","children":[{"code":321202,"name":"海陵","fullname":"海陵区","level":"district","filename":"320000/321200/321202"},{"code":321203,"name":"高港","fullname":"高港区","level":"district","filename":"320000/321200/321203"},{"code":321204,"name":"姜堰","fullname":"姜堰区","level":"district","filename":"320000/321200/321204"},{"code":321281,"name":"兴化","fullname":"兴化市","level":"district","filename":"320000/321200/321281"},{"code":321282,"name":"靖江","fullname":"靖江市","level":"district","filename":"320000/321200/321282"},{"code":321283,"name":"泰兴","fullname":"泰兴市","level":"district","filename":"320000/321200/321283"}]},{"code":321300,"name":"宿迁","fullname":"宿迁市","level":"city","filename":"320000/321300","children":[{"code":321302,"name":"宿城","fullname":"宿城区","level":"district","filename":"320000/321300/321302"},{"code":321311,"name":"宿豫","fullname":"宿豫区","level":"district","filename":"320000/321300/321311"},{"code":321322,"name":"沭阳","fullname":"沭阳县","level":"district","filename":"320000/321300/321322"},{"code":321323,"name":"泗阳","fullname":"泗阳县","level":"district","filename":"320000/321300/321323"},{"code":321324,"name":"泗洪","fullname":"泗洪县","level":"district","filename":"320000/321300/321324"}]}]},{"code":330000,"name":"浙江","fullname":"浙江省","level":"province","filename":"330000","children":[{"code":330100,"name":"杭州","fullname":"杭州市","level":"city","filename":"330000/330100","children":[{"code":330102,"name":"上城","fullname":"上城区","level":"district","filename":"330000/330100/330102"},{"code":330105,"name":"拱墅","fullname":"拱墅区","level":"district","filename":"330000/330100/330105"},{"code":330106,"name":"西湖","fullname":"西湖区","level":"district","filename":"330000/330100/330106"},{"code":330108,"name":"滨江","fullname":"滨江区","level":"district","filename":"330000/330100/330108"},{"code":330109,"name":"萧山","fullname":"萧山区","level":"district","filename":"330000/330100/330109"},{"code":330110,"name":"余杭","fullname":"余杭区","level":"district","filename":"330000/330100/330110"},{"code":330111,"name":"富阳","fullname":"富阳区","level":"district","filename":"330000/330100/330111"},{"code":330112,"name":"临安","fullname":"临安区","level":"district","filename":"330000/330100/330112"},{"code":330114,"name":"钱塘","fullname":"钱塘区","level":"district","filename":"330000/330100/330114"},{"code":330113,"name":"临平","fullname":"临平区","level":"district","filename":"330000/330100/330113"},{"code":330122,"name":"桐庐","fullname":"桐庐县","level":"district","filename":"330000/330100/330122"},{"code":330127,"name":"淳安","fullname":"淳安县","level":"district","filename":"330000/330100/330127"},{"code":330182,"name":"建德","fullname":"建德市","level":"district","filename":"330000/330100/330182"}]},{"code":330200,"name":"宁波","fullname":"宁波市","level":"city","filename":"330000/330200","children":[{"code":330203,"name":"海曙","fullname":"海曙区","level":"district","filename":"330000/330200/330203"},{"code":330205,"name":"江北","fullname":"江北区","level":"district","filename":"330000/330200/330205"},{"code":330206,"name":"北仑","fullname":"北仑区","level":"district","filename":"330000/330200/330206"},{"code":330211,"name":"镇海","fullname":"镇海区","level":"district","filename":"330000/330200/330211"},{"code":330212,"name":"鄞州","fullname":"鄞州区","level":"district","filename":"330000/330200/330212"},{"code":330213,"name":"奉化","fullname":"奉化区","level":"district","filename":"330000/330200/330213"},{"code":330225,"name":"象山","fullname":"象山县","level":"district","filename":"330000/330200/330225"},{"code":330226,"name":"宁海","fullname":"宁海县","level":"district","filename":"330000/330200/330226"},{"code":330281,"name":"余姚","fullname":"余姚市","level":"district","filename":"330000/330200/330281"},{"code":330282,"name":"慈溪","fullname":"慈溪市","level":"district","filename":"330000/330200/330282"}]},{"code":330300,"name":"温州","fullname":"温州市","level":"city","filename":"330000/330300","children":[{"code":330302,"name":"鹿城","fullname":"鹿城区","level":"district","filename":"330000/330300/330302"},{"code":330303,"name":"龙湾","fullname":"龙湾区","level":"district","filename":"330000/330300/330303"},{"code":330304,"name":"瓯海","fullname":"瓯海区","level":"district","filename":"330000/330300/330304"},{"code":330305,"name":"洞头","fullname":"洞头区","level":"district","filename":"330000/330300/330305"},{"code":330324,"name":"永嘉","fullname":"永嘉县","level":"district","filename":"330000/330300/330324"},{"code":330326,"name":"平阳","fullname":"平阳县","level":"district","filename":"330000/330300/330326"},{"code":330327,"name":"苍南","fullname":"苍南县","level":"district","filename":"330000/330300/330327"},{"code":330328,"name":"文成","fullname":"文成县","level":"district","filename":"330000/330300/330328"},{"code":330329,"name":"泰顺","fullname":"泰顺县","level":"district","filename":"330000/330300/330329"},{"code":330381,"name":"瑞安","fullname":"瑞安市","level":"district","filename":"330000/330300/330381"},{"code":330382,"name":"乐清","fullname":"乐清市","level":"district","filename":"330000/330300/330382"},{"code":330383,"name":"龙港","fullname":"龙港市","level":"district","filename":"330000/330300/330383"}]},{"code":330400,"name":"嘉兴","fullname":"嘉兴市","level":"city","filename":"330000/330400","children":[{"code":330402,"name":"南湖","fullname":"南湖区","level":"district","filename":"330000/330400/330402"},{"code":330411,"name":"秀洲","fullname":"秀洲区","level":"district","filename":"330000/330400/330411"},{"code":330421,"name":"嘉善","fullname":"嘉善县","level":"district","filename":"330000/330400/330421"},{"code":330424,"name":"海盐","fullname":"海盐县","level":"district","filename":"330000/330400/330424"},{"code":330481,"name":"海宁","fullname":"海宁市","level":"district","filename":"330000/330400/330481"},{"code":330482,"name":"平湖","fullname":"平湖市","level":"district","filename":"330000/330400/330482"},{"code":330483,"name":"桐乡","fullname":"桐乡市","level":"district","filename":"330000/330400/330483"}]},{"code":330500,"name":"湖州","fullname":"湖州市","level":"city","filename":"330000/330500","children":[{"code":330502,"name":"吴兴","fullname":"吴兴区","level":"district","filename":"330000/330500/330502"},{"code":330503,"name":"南浔","fullname":"南浔区","level":"district","filename":"330000/330500/330503"},{"code":330521,"name":"德清","fullname":"德清县","level":"district","filename":"330000/330500/330521"},{"code":330522,"name":"长兴","fullname":"长兴县","level":"district","filename":"330000/330500/330522"},{"code":330523,"name":"安吉","fullname":"安吉县","level":"district","filename":"330000/330500/330523"}]},{"code":330600,"name":"绍兴","fullname":"绍兴市","level":"city","filename":"330000/330600","children":[{"code":330602,"name":"越城","fullname":"越城区","level":"district","filename":"330000/330600/330602"},{"code":330603,"name":"柯桥","fullname":"柯桥区","level":"district","filename":"330000/330600/330603"},{"code":330604,"name":"上虞","fullname":"上虞区","level":"district","filename":"330000/330600/330604"},{"code":330624,"name":"新昌","fullname":"新昌县","level":"district","filename":"330000/330600/330624"},{"code":330681,"name":"诸暨","fullname":"诸暨市","level":"district","filename":"330000/330600/330681"},{"code":330683,"name":"嵊州","fullname":"嵊州市","level":"district","filename":"330000/330600/330683"}]},{"code":330700,"name":"金华","fullname":"金华市","level":"city","filename":"330000/330700","children":[{"code":330702,"name":"婺城","fullname":"婺城区","level":"district","filename":"330000/330700/330702"},{"code":330703,"name":"金东","fullname":"金东区","level":"district","filename":"330000/330700/330703"},{"code":330723,"name":"武义","fullname":"武义县","level":"district","filename":"330000/330700/330723"},{"code":330726,"name":"浦江","fullname":"浦江县","level":"district","filename":"330000/330700/330726"},{"code":330727,"name":"磐安","fullname":"磐安县","level":"district","filename":"330000/330700/330727"},{"code":330781,"name":"兰溪","fullname":"兰溪市","level":"district","filename":"330000/330700/330781"},{"code":330782,"name":"义乌","fullname":"义乌市","level":"district","filename":"330000/330700/330782"},{"code":330783,"name":"东阳","fullname":"东阳市","level":"district","filename":"330000/330700/330783"},{"code":330784,"name":"永康","fullname":"永康市","level":"district","filename":"330000/330700/330784"}]},{"code":330800,"name":"衢州","fullname":"衢州市","level":"city","filename":"330000/330800","children":[{"code":330802,"name":"柯城","fullname":"柯城区","level":"district","filename":"330000/330800/330802"},{"code":330803,"name":"衢江","fullname":"衢江区","level":"district","filename":"330000/330800/330803"},{"code":330822,"name":"常山","fullname":"常山县","level":"district","filename":"330000/330800/330822"},{"code":330824,"name":"开化","fullname":"开化县","level":"district","filename":"330000/330800/330824"},{"code":330825,"name":"龙游","fullname":"龙游县","level":"district","filename":"330000/330800/330825"},{"code":330881,"name":"江山","fullname":"江山市","level":"district","filename":"330000/330800/330881"}]},{"code":330900,"name":"舟山","fullname":"舟山市","level":"city","filename":"330000/330900","children":[{"code":330902,"name":"定海","fullname":"定海区","level":"district","filename":"330000/330900/330902"},{"code":330903,"name":"普陀","fullname":"普陀区","level":"district","filename":"330000/330900/330903"},{"code":330921,"name":"岱山","fullname":"岱山县","level":"district","filename":"330000/330900/330921"},{"code":330922,"name":"嵊泗","fullname":"嵊泗县","level":"district","filename":"330000/330900/330922"}]},{"code":331000,"name":"台州","fullname":"台州市","level":"city","filename":"330000/331000","children":[{"code":331002,"name":"椒江","fullname":"椒江区","level":"district","filename":"330000/331000/331002"},{"code":331003,"name":"黄岩","fullname":"黄岩区","level":"district","filename":"330000/331000/331003"},{"code":331004,"name":"路桥","fullname":"路桥区","level":"district","filename":"330000/331000/331004"},{"code":331022,"name":"三门","fullname":"三门县","level":"district","filename":"330000/331000/331022"},{"code":331023,"name":"天台","fullname":"天台县","level":"district","filename":"330000/331000/331023"},{"code":331024,"name":"仙居","fullname":"仙居县","level":"district","filename":"330000/331000/331024"},{"code":331081,"name":"温岭","fullname":"温岭市","level":"district","filename":"330000/331000/331081"},{"code":331082,"name":"临海","fullname":"临海市","level":"district","filename":"330000/331000/331082"},{"code":331083,"name":"玉环","fullname":"玉环市","level":"district","filename":"330000/331000/331083"}]},{"code":331100,"name":"丽水","fullname":"丽水市","level":"city","filename":"330000/331100","children":[{"code":331102,"name":"莲都","fullname":"莲都区","level":"district","filename":"330000/331100/331102"},{"code":331121,"name":"青田","fullname":"青田县","level":"district","filename":"330000/331100/331121"},{"code":331122,"name":"缙云","fullname":"缙云县","level":"district","filename":"330000/331100/331122"},{"code":331123,"name":"遂昌","fullname":"遂昌县","level":"district","filename":"330000/331100/331123"},{"code":331124,"name":"松阳","fullname":"松阳县","level":"district","filename":"330000/331100/331124"},{"code":331125,"name":"云和","fullname":"云和县","level":"district","filename":"330000/331100/331125"},{"code":331126,"name":"庆元","fullname":"庆元县","level":"district","filename":"330000/331100/331126"},{"code":331127,"name":"景宁","fullname":"景宁畲族自治县","level":"district","filename":"330000/331100/331127"},{"code":331181,"name":"龙泉","fullname":"龙泉市","level":"district","filename":"330000/331100/331181"}]}]},{"code":340000,"name":"安徽","fullname":"安徽省","level":"province","filename":"340000","children":[{"code":340100,"name":"合肥","fullname":"合肥市","level":"city","filename":"340000/340100","children":[{"code":340102,"name":"瑶海","fullname":"瑶海区","level":"district","filename":"340000/340100/340102"},{"code":340103,"name":"庐阳","fullname":"庐阳区","level":"district","filename":"340000/340100/340103"},{"code":340104,"name":"蜀山","fullname":"蜀山区","level":"district","filename":"340000/340100/340104"},{"code":340111,"name":"包河","fullname":"包河区","level":"district","filename":"340000/340100/340111"},{"code":340121,"name":"长丰","fullname":"长丰县","level":"district","filename":"340000/340100/340121"},{"code":340122,"name":"肥东","fullname":"肥东县","level":"district","filename":"340000/340100/340122"},{"code":340123,"name":"肥西","fullname":"肥西县","level":"district","filename":"340000/340100/340123"},{"code":340124,"name":"庐江","fullname":"庐江县","level":"district","filename":"340000/340100/340124"},{"code":340181,"name":"巢湖","fullname":"巢湖市","level":"district","filename":"340000/340100/340181"}]},{"code":340200,"name":"芜湖","fullname":"芜湖市","level":"city","filename":"340000/340200","children":[{"code":340202,"name":"镜湖","fullname":"镜湖区","level":"district","filename":"340000/340200/340202"},{"code":340207,"name":"鸠江","fullname":"鸠江区","level":"district","filename":"340000/340200/340207"},{"code":340209,"name":"弋江","fullname":"弋江区","level":"district","filename":"340000/340200/340209"},{"code":340210,"name":"湾沚","fullname":"湾沚区","level":"district","filename":"340000/340200/340210"},{"code":340211,"name":"繁昌","fullname":"繁昌区","level":"district","filename":"340000/340200/340211"},{"code":340223,"name":"南陵","fullname":"南陵县","level":"district","filename":"340000/340200/340223"},{"code":340281,"name":"无为","fullname":"无为市","level":"district","filename":"340000/340200/340281"}]},{"code":340300,"name":"蚌埠","fullname":"蚌埠市","level":"city","filename":"340000/340300","children":[{"code":340302,"name":"龙子湖","fullname":"龙子湖区","level":"district","filename":"340000/340300/340302"},{"code":340303,"name":"蚌山","fullname":"蚌山区","level":"district","filename":"340000/340300/340303"},{"code":340304,"name":"禹会","fullname":"禹会区","level":"district","filename":"340000/340300/340304"},{"code":340311,"name":"淮上","fullname":"淮上区","level":"district","filename":"340000/340300/340311"},{"code":340321,"name":"怀远","fullname":"怀远县","level":"district","filename":"340000/340300/340321"},{"code":340322,"name":"五河","fullname":"五河县","level":"district","filename":"340000/340300/340322"},{"code":340323,"name":"固镇","fullname":"固镇县","level":"district","filename":"340000/340300/340323"}]},{"code":340400,"name":"淮南","fullname":"淮南市","level":"city","filename":"340000/340400","children":[{"code":340402,"name":"大通","fullname":"大通区","level":"district","filename":"340000/340400/340402"},{"code":340403,"name":"田家庵","fullname":"田家庵区","level":"district","filename":"340000/340400/340403"},{"code":340404,"name":"谢家集","fullname":"谢家集区","level":"district","filename":"340000/340400/340404"},{"code":340405,"name":"八公山","fullname":"八公山区","level":"district","filename":"340000/340400/340405"},{"code":340406,"name":"潘集","fullname":"潘集区","level":"district","filename":"340000/340400/340406"},{"code":340421,"name":"凤台","fullname":"凤台县","level":"district","filename":"340000/340400/340421"},{"code":340422,"name":"寿县","fullname":"寿县","level":"district","filename":"340000/340400/340422"}]},{"code":340500,"name":"马鞍山","fullname":"马鞍山市","level":"city","filename":"340000/340500","children":[{"code":340503,"name":"花山","fullname":"花山区","level":"district","filename":"340000/340500/340503"},{"code":340504,"name":"雨山","fullname":"雨山区","level":"district","filename":"340000/340500/340504"},{"code":340506,"name":"博望","fullname":"博望区","level":"district","filename":"340000/340500/340506"},{"code":340521,"name":"当涂","fullname":"当涂县","level":"district","filename":"340000/340500/340521"},{"code":340522,"name":"含山","fullname":"含山县","level":"district","filename":"340000/340500/340522"},{"code":340523,"name":"和县","fullname":"和县","level":"district","filename":"340000/340500/340523"}]},{"code":340600,"name":"淮北","fullname":"淮北市","level":"city","filename":"340000/340600","children":[{"code":340602,"name":"杜集","fullname":"杜集区","level":"district","filename":"340000/340600/340602"},{"code":340603,"name":"相山","fullname":"相山区","level":"district","filename":"340000/340600/340603"},{"code":340604,"name":"烈山","fullname":"烈山区","level":"district","filename":"340000/340600/340604"},{"code":340621,"name":"濉溪","fullname":"濉溪县","level":"district","filename":"340000/340600/340621"}]},{"code":340700,"name":"铜陵","fullname":"铜陵市","level":"city","filename":"340000/340700","children":[{"code":340705,"name":"铜官","fullname":"铜官区","level":"district","filename":"340000/340700/340705"},{"code":340706,"name":"义安","fullname":"义安区","level":"district","filename":"340000/340700/340706"},{"code":340711,"name":"郊区","fullname":"郊区","level":"district","filename":"340000/340700/340711"},{"code":340722,"name":"枞阳","fullname":"枞阳县","level":"district","filename":"340000/340700/340722"}]},{"code":340800,"name":"安庆","fullname":"安庆市","level":"city","filename":"340000/340800","children":[{"code":340802,"name":"迎江","fullname":"迎江区","level":"district","filename":"340000/340800/340802"},{"code":340803,"name":"大观","fullname":"大观区","level":"district","filename":"340000/340800/340803"},{"code":340811,"name":"宜秀","fullname":"宜秀区","level":"district","filename":"340000/340800/340811"},{"code":340822,"name":"怀宁","fullname":"怀宁县","level":"district","filename":"340000/340800/340822"},{"code":340825,"name":"太湖","fullname":"太湖县","level":"district","filename":"340000/340800/340825"},{"code":340826,"name":"宿松","fullname":"宿松县","level":"district","filename":"340000/340800/340826"},{"code":340827,"name":"望江","fullname":"望江县","level":"district","filename":"340000/340800/340827"},{"code":340828,"name":"岳西","fullname":"岳西县","level":"district","filename":"340000/340800/340828"},{"code":340881,"name":"桐城","fullname":"桐城市","level":"district","filename":"340000/340800/340881"},{"code":340882,"name":"潜山","fullname":"潜山市","level":"district","filename":"340000/340800/340882"}]},{"code":341000,"name":"黄山","fullname":"黄山市","level":"city","filename":"340000/341000","children":[{"code":341002,"name":"屯溪","fullname":"屯溪区","level":"district","filename":"340000/341000/341002"},{"code":341003,"name":"黄山","fullname":"黄山区","level":"district","filename":"340000/341000/341003"},{"code":341004,"name":"徽州","fullname":"徽州区","level":"district","filename":"340000/341000/341004"},{"code":341021,"name":"歙县","fullname":"歙县","level":"district","filename":"340000/341000/341021"},{"code":341022,"name":"休宁","fullname":"休宁县","level":"district","filename":"340000/341000/341022"},{"code":341023,"name":"黟县","fullname":"黟县","level":"district","filename":"340000/341000/341023"},{"code":341024,"name":"祁门","fullname":"祁门县","level":"district","filename":"340000/341000/341024"}]},{"code":341100,"name":"滁州","fullname":"滁州市","level":"city","filename":"340000/341100","children":[{"code":341102,"name":"琅琊","fullname":"琅琊区","level":"district","filename":"340000/341100/341102"},{"code":341103,"name":"南谯","fullname":"南谯区","level":"district","filename":"340000/341100/341103"},{"code":341122,"name":"来安","fullname":"来安县","level":"district","filename":"340000/341100/341122"},{"code":341124,"name":"全椒","fullname":"全椒县","level":"district","filename":"340000/341100/341124"},{"code":341125,"name":"定远","fullname":"定远县","level":"district","filename":"340000/341100/341125"},{"code":341126,"name":"凤阳","fullname":"凤阳县","level":"district","filename":"340000/341100/341126"},{"code":341181,"name":"天长","fullname":"天长市","level":"district","filename":"340000/341100/341181"},{"code":341182,"name":"明光","fullname":"明光市","level":"district","filename":"340000/341100/341182"}]},{"code":341200,"name":"阜阳","fullname":"阜阳市","level":"city","filename":"340000/341200","children":[{"code":341202,"name":"颍州","fullname":"颍州区","level":"district","filename":"340000/341200/341202"},{"code":341203,"name":"颍东","fullname":"颍东区","level":"district","filename":"340000/341200/341203"},{"code":341204,"name":"颍泉","fullname":"颍泉区","level":"district","filename":"340000/341200/341204"},{"code":341221,"name":"临泉","fullname":"临泉县","level":"district","filename":"340000/341200/341221"},{"code":341222,"name":"太和","fullname":"太和县","level":"district","filename":"340000/341200/341222"},{"code":341225,"name":"阜南","fullname":"阜南县","level":"district","filename":"340000/341200/341225"},{"code":341226,"name":"颍上","fullname":"颍上县","level":"district","filename":"340000/341200/341226"},{"code":341282,"name":"界首","fullname":"界首市","level":"district","filename":"340000/341200/341282"}]},{"code":341300,"name":"宿州","fullname":"宿州市","level":"city","filename":"340000/341300","children":[{"code":341302,"name":"埇桥","fullname":"埇桥区","level":"district","filename":"340000/341300/341302"},{"code":341321,"name":"砀山","fullname":"砀山县","level":"district","filename":"340000/341300/341321"},{"code":341322,"name":"萧县","fullname":"萧县","level":"district","filename":"340000/341300/341322"},{"code":341323,"name":"灵璧","fullname":"灵璧县","level":"district","filename":"340000/341300/341323"},{"code":341324,"name":"泗县","fullname":"泗县","level":"district","filename":"340000/341300/341324"}]},{"code":341500,"name":"六安","fullname":"六安市","level":"city","filename":"340000/341500","children":[{"code":341502,"name":"金安","fullname":"金安区","level":"district","filename":"340000/341500/341502"},{"code":341503,"name":"裕安","fullname":"裕安区","level":"district","filename":"340000/341500/341503"},{"code":341504,"name":"叶集","fullname":"叶集区","level":"district","filename":"340000/341500/341504"},{"code":341522,"name":"霍邱","fullname":"霍邱县","level":"district","filename":"340000/341500/341522"},{"code":341523,"name":"舒城","fullname":"舒城县","level":"district","filename":"340000/341500/341523"},{"code":341524,"name":"金寨","fullname":"金寨县","level":"district","filename":"340000/341500/341524"},{"code":341525,"name":"霍山","fullname":"霍山县","level":"district","filename":"340000/341500/341525"}]},{"code":341600,"name":"亳州","fullname":"亳州市","level":"city","filename":"340000/341600","children":[{"code":341602,"name":"谯城","fullname":"谯城区","level":"district","filename":"340000/341600/341602"},{"code":341621,"name":"涡阳","fullname":"涡阳县","level":"district","filename":"340000/341600/341621"},{"code":341622,"name":"蒙城","fullname":"蒙城县","level":"district","filename":"340000/341600/341622"},{"code":341623,"name":"利辛","fullname":"利辛县","level":"district","filename":"340000/341600/341623"}]},{"code":341700,"name":"池州","fullname":"池州市","level":"city","filename":"340000/341700","children":[{"code":341702,"name":"贵池","fullname":"贵池区","level":"district","filename":"340000/341700/341702"},{"code":341721,"name":"东至","fullname":"东至县","level":"district","filename":"340000/341700/341721"},{"code":341722,"name":"石台","fullname":"石台县","level":"district","filename":"340000/341700/341722"},{"code":341723,"name":"青阳","fullname":"青阳县","level":"district","filename":"340000/341700/341723"}]},{"code":341800,"name":"宣城","fullname":"宣城市","level":"city","filename":"340000/341800","children":[{"code":341802,"name":"宣州","fullname":"宣州区","level":"district","filename":"340000/341800/341802"},{"code":341821,"name":"郎溪","fullname":"郎溪县","level":"district","filename":"340000/341800/341821"},{"code":341823,"name":"泾县","fullname":"泾县","level":"district","filename":"340000/341800/341823"},{"code":341824,"name":"绩溪","fullname":"绩溪县","level":"district","filename":"340000/341800/341824"},{"code":341825,"name":"旌德","fullname":"旌德县","level":"district","filename":"340000/341800/341825"},{"code":341881,"name":"宁国","fullname":"宁国市","level":"district","filename":"340000/341800/341881"},{"code":341882,"name":"广德","fullname":"广德市","level":"district","filename":"340000/341800/341882"}]}]},{"code":350000,"name":"福建","fullname":"福建省","level":"province","filename":"350000","children":[{"code":350100,"name":"福州","fullname":"福州市","level":"city","filename":"350000/350100","children":[{"code":350102,"name":"鼓楼","fullname":"鼓楼区","level":"district","filename":"350000/350100/350102"},{"code":350103,"name":"台江","fullname":"台江区","level":"district","filename":"350000/350100/350103"},{"code":350104,"name":"仓山","fullname":"仓山区","level":"district","filename":"350000/350100/350104"},{"code":350105,"name":"马尾","fullname":"马尾区","level":"district","filename":"350000/350100/350105"},{"code":350111,"name":"晋安","fullname":"晋安区","level":"district","filename":"350000/350100/350111"},{"code":350112,"name":"长乐","fullname":"长乐区","level":"district","filename":"350000/350100/350112"},{"code":350121,"name":"闽侯","fullname":"闽侯县","level":"district","filename":"350000/350100/350121"},{"code":350122,"name":"连江","fullname":"连江县","level":"district","filename":"350000/350100/350122"},{"code":350123,"name":"罗源","fullname":"罗源县","level":"district","filename":"350000/350100/350123"},{"code":350124,"name":"闽清","fullname":"闽清县","level":"district","filename":"350000/350100/350124"},{"code":350125,"name":"永泰","fullname":"永泰县","level":"district","filename":"350000/350100/350125"},{"code":350128,"name":"平潭","fullname":"平潭县","level":"district","filename":"350000/350100/350128"},{"code":350181,"name":"福清","fullname":"福清市","level":"district","filename":"350000/350100/350181"}]},{"code":350200,"name":"厦门","fullname":"厦门市","level":"city","filename":"350000/350200","children":[{"code":350203,"name":"思明","fullname":"思明区","level":"district","filename":"350000/350200/350203"},{"code":350205,"name":"海沧","fullname":"海沧区","level":"district","filename":"350000/350200/350205"},{"code":350206,"name":"湖里","fullname":"湖里区","level":"district","filename":"350000/350200/350206"},{"code":350211,"name":"集美","fullname":"集美区","level":"district","filename":"350000/350200/350211"},{"code":350212,"name":"同安","fullname":"同安区","level":"district","filename":"350000/350200/350212"},{"code":350213,"name":"翔安","fullname":"翔安区","level":"district","filename":"350000/350200/350213"}]},{"code":350300,"name":"莆田","fullname":"莆田市","level":"city","filename":"350000/350300","children":[{"code":350302,"name":"城厢","fullname":"城厢区","level":"district","filename":"350000/350300/350302"},{"code":350303,"name":"涵江","fullname":"涵江区","level":"district","filename":"350000/350300/350303"},{"code":350304,"name":"荔城","fullname":"荔城区","level":"district","filename":"350000/350300/350304"},{"code":350305,"name":"秀屿","fullname":"秀屿区","level":"district","filename":"350000/350300/350305"},{"code":350322,"name":"仙游","fullname":"仙游县","level":"district","filename":"350000/350300/350322"}]},{"code":350400,"name":"三明","fullname":"三明市","level":"city","filename":"350000/350400","children":[{"code":350403,"name":"三元","fullname":"三元区","level":"district","filename":"350000/350400/350403"},{"code":350421,"name":"明溪","fullname":"明溪县","level":"district","filename":"350000/350400/350421"},{"code":350423,"name":"清流","fullname":"清流县","level":"district","filename":"350000/350400/350423"},{"code":350424,"name":"宁化","fullname":"宁化县","level":"district","filename":"350000/350400/350424"},{"code":350425,"name":"大田","fullname":"大田县","level":"district","filename":"350000/350400/350425"},{"code":350426,"name":"尤溪","fullname":"尤溪县","level":"district","filename":"350000/350400/350426"},{"code":350427,"name":"沙县区","fullname":"沙县区","level":"district","filename":"350000/350400/350427"},{"code":350428,"name":"将乐","fullname":"将乐县","level":"district","filename":"350000/350400/350428"},{"code":350429,"name":"泰宁","fullname":"泰宁县","level":"district","filename":"350000/350400/350429"},{"code":350430,"name":"建宁","fullname":"建宁县","level":"district","filename":"350000/350400/350430"},{"code":350481,"name":"永安","fullname":"永安市","level":"district","filename":"350000/350400/350481"}]},{"code":350500,"name":"泉州","fullname":"泉州市","level":"city","filename":"350000/350500","children":[{"code":350502,"name":"鲤城","fullname":"鲤城区","level":"district","filename":"350000/350500/350502"},{"code":350503,"name":"丰泽","fullname":"丰泽区","level":"district","filename":"350000/350500/350503"},{"code":350504,"name":"洛江","fullname":"洛江区","level":"district","filename":"350000/350500/350504"},{"code":350505,"name":"泉港","fullname":"泉港区","level":"district","filename":"350000/350500/350505"},{"code":350521,"name":"惠安","fullname":"惠安县","level":"district","filename":"350000/350500/350521"},{"code":350524,"name":"安溪","fullname":"安溪县","level":"district","filename":"350000/350500/350524"},{"code":350525,"name":"永春","fullname":"永春县","level":"district","filename":"350000/350500/350525"},{"code":350526,"name":"德化","fullname":"德化县","level":"district","filename":"350000/350500/350526"},{"code":350527,"name":"金门","fullname":"金门县","level":"district","filename":"350000/350500/350527"},{"code":350581,"name":"石狮","fullname":"石狮市","level":"district","filename":"350000/350500/350581"},{"code":350582,"name":"晋江","fullname":"晋江市","level":"district","filename":"350000/350500/350582"},{"code":350583,"name":"南安","fullname":"南安市","level":"district","filename":"350000/350500/350583"}]},{"code":350600,"name":"漳州","fullname":"漳州市","level":"city","filename":"350000/350600","children":[{"code":350602,"name":"芗城","fullname":"芗城区","level":"district","filename":"350000/350600/350602"},{"code":350603,"name":"龙文","fullname":"龙文区","level":"district","filename":"350000/350600/350603"},{"code":350622,"name":"云霄","fullname":"云霄县","level":"district","filename":"350000/350600/350622"},{"code":350623,"name":"漳浦","fullname":"漳浦县","level":"district","filename":"350000/350600/350623"},{"code":350624,"name":"诏安","fullname":"诏安县","level":"district","filename":"350000/350600/350624"},{"code":350625,"name":"长泰","fullname":"长泰区","level":"district","filename":"350000/350600/350625"},{"code":350626,"name":"东山","fullname":"东山县","level":"district","filename":"350000/350600/350626"},{"code":350627,"name":"南靖","fullname":"南靖县","level":"district","filename":"350000/350600/350627"},{"code":350628,"name":"平和","fullname":"平和县","level":"district","filename":"350000/350600/350628"},{"code":350629,"name":"华安","fullname":"华安县","level":"district","filename":"350000/350600/350629"},{"code":350681,"name":"龙海","fullname":"龙海区","level":"district","filename":"350000/350600/350681"}]},{"code":350700,"name":"南平","fullname":"南平市","level":"city","filename":"350000/350700","children":[{"code":350702,"name":"延平","fullname":"延平区","level":"district","filename":"350000/350700/350702"},{"code":350703,"name":"建阳","fullname":"建阳区","level":"district","filename":"350000/350700/350703"},{"code":350721,"name":"顺昌","fullname":"顺昌县","level":"district","filename":"350000/350700/350721"},{"code":350722,"name":"浦城","fullname":"浦城县","level":"district","filename":"350000/350700/350722"},{"code":350723,"name":"光泽","fullname":"光泽县","level":"district","filename":"350000/350700/350723"},{"code":350724,"name":"松溪","fullname":"松溪县","level":"district","filename":"350000/350700/350724"},{"code":350725,"name":"政和","fullname":"政和县","level":"district","filename":"350000/350700/350725"},{"code":350781,"name":"邵武","fullname":"邵武市","level":"district","filename":"350000/350700/350781"},{"code":350782,"name":"武夷山","fullname":"武夷山市","level":"district","filename":"350000/350700/350782"},{"code":350783,"name":"建瓯","fullname":"建瓯市","level":"district","filename":"350000/350700/350783"}]},{"code":350800,"name":"龙岩","fullname":"龙岩市","level":"city","filename":"350000/350800","children":[{"code":350802,"name":"新罗","fullname":"新罗区","level":"district","filename":"350000/350800/350802"},{"code":350803,"name":"永定","fullname":"永定区","level":"district","filename":"350000/350800/350803"},{"code":350821,"name":"长汀","fullname":"长汀县","level":"district","filename":"350000/350800/350821"},{"code":350823,"name":"上杭","fullname":"上杭县","level":"district","filename":"350000/350800/350823"},{"code":350824,"name":"武平","fullname":"武平县","level":"district","filename":"350000/350800/350824"},{"code":350825,"name":"连城","fullname":"连城县","level":"district","filename":"350000/350800/350825"},{"code":350881,"name":"漳平","fullname":"漳平市","level":"district","filename":"350000/350800/350881"}]},{"code":350900,"name":"宁德","fullname":"宁德市","level":"city","filename":"350000/350900","children":[{"code":350902,"name":"蕉城","fullname":"蕉城区","level":"district","filename":"350000/350900/350902"},{"code":350921,"name":"霞浦","fullname":"霞浦县","level":"district","filename":"350000/350900/350921"},{"code":350922,"name":"古田","fullname":"古田县","level":"district","filename":"350000/350900/350922"},{"code":350923,"name":"屏南","fullname":"屏南县","level":"district","filename":"350000/350900/350923"},{"code":350924,"name":"寿宁","fullname":"寿宁县","level":"district","filename":"350000/350900/350924"},{"code":350925,"name":"周宁","fullname":"周宁县","level":"district","filename":"350000/350900/350925"},{"code":350926,"name":"柘荣","fullname":"柘荣县","level":"district","filename":"350000/350900/350926"},{"code":350981,"name":"福安","fullname":"福安市","level":"district","filename":"350000/350900/350981"},{"code":350982,"name":"福鼎","fullname":"福鼎市","level":"district","filename":"350000/350900/350982"}]}]},{"code":360000,"name":"江西","fullname":"江西省","level":"province","filename":"360000","children":[{"code":360100,"name":"南昌","fullname":"南昌市","level":"city","filename":"360000/360100","children":[{"code":360102,"name":"东湖","fullname":"东湖区","level":"district","filename":"360000/360100/360102"},{"code":360103,"name":"西湖","fullname":"西湖区","level":"district","filename":"360000/360100/360103"},{"code":360104,"name":"青云谱","fullname":"青云谱区","level":"district","filename":"360000/360100/360104"},{"code":360111,"name":"青山湖","fullname":"青山湖区","level":"district","filename":"360000/360100/360111"},{"code":360112,"name":"新建","fullname":"新建区","level":"district","filename":"360000/360100/360112"},{"code":360113,"name":"红谷滩","fullname":"红谷滩区","level":"district","filename":"360000/360100/360113"},{"code":360121,"name":"南昌","fullname":"南昌县","level":"district","filename":"360000/360100/360121"},{"code":360123,"name":"安义","fullname":"安义县","level":"district","filename":"360000/360100/360123"},{"code":360124,"name":"进贤","fullname":"进贤县","level":"district","filename":"360000/360100/360124"}]},{"code":360200,"name":"景德镇","fullname":"景德镇市","level":"city","filename":"360000/360200","children":[{"code":360202,"name":"昌江","fullname":"昌江区","level":"district","filename":"360000/360200/360202"},{"code":360203,"name":"珠山","fullname":"珠山区","level":"district","filename":"360000/360200/360203"},{"code":360222,"name":"浮梁","fullname":"浮梁县","level":"district","filename":"360000/360200/360222"},{"code":360281,"name":"乐平","fullname":"乐平市","level":"district","filename":"360000/360200/360281"}]},{"code":360300,"name":"萍乡","fullname":"萍乡市","level":"city","filename":"360000/360300","children":[{"code":360302,"name":"安源","fullname":"安源区","level":"district","filename":"360000/360300/360302"},{"code":360313,"name":"湘东","fullname":"湘东区","level":"district","filename":"360000/360300/360313"},{"code":360321,"name":"莲花","fullname":"莲花县","level":"district","filename":"360000/360300/360321"},{"code":360322,"name":"上栗","fullname":"上栗县","level":"district","filename":"360000/360300/360322"},{"code":360323,"name":"芦溪","fullname":"芦溪县","level":"district","filename":"360000/360300/360323"}]},{"code":360400,"name":"九江","fullname":"九江市","level":"city","filename":"360000/360400","children":[{"code":360402,"name":"濂溪","fullname":"濂溪区","level":"district","filename":"360000/360400/360402"},{"code":360403,"name":"浔阳","fullname":"浔阳区","level":"district","filename":"360000/360400/360403"},{"code":360404,"name":"柴桑","fullname":"柴桑区","level":"district","filename":"360000/360400/360404"},{"code":360423,"name":"武宁","fullname":"武宁县","level":"district","filename":"360000/360400/360423"},{"code":360424,"name":"修水","fullname":"修水县","level":"district","filename":"360000/360400/360424"},{"code":360425,"name":"永修","fullname":"永修县","level":"district","filename":"360000/360400/360425"},{"code":360426,"name":"德安","fullname":"德安县","level":"district","filename":"360000/360400/360426"},{"code":360428,"name":"都昌","fullname":"都昌县","level":"district","filename":"360000/360400/360428"},{"code":360429,"name":"湖口","fullname":"湖口县","level":"district","filename":"360000/360400/360429"},{"code":360430,"name":"彭泽","fullname":"彭泽县","level":"district","filename":"360000/360400/360430"},{"code":360481,"name":"瑞昌","fullname":"瑞昌市","level":"district","filename":"360000/360400/360481"},{"code":360482,"name":"共青城","fullname":"共青城市","level":"district","filename":"360000/360400/360482"},{"code":360483,"name":"庐山","fullname":"庐山市","level":"district","filename":"360000/360400/360483"}]},{"code":360500,"name":"新余","fullname":"新余市","level":"city","filename":"360000/360500","children":[{"code":360502,"name":"渝水","fullname":"渝水区","level":"district","filename":"360000/360500/360502"},{"code":360521,"name":"分宜","fullname":"分宜县","level":"district","filename":"360000/360500/360521"}]},{"code":360600,"name":"鹰潭","fullname":"鹰潭市","level":"city","filename":"360000/360600","children":[{"code":360602,"name":"月湖","fullname":"月湖区","level":"district","filename":"360000/360600/360602"},{"code":360603,"name":"余江","fullname":"余江区","level":"district","filename":"360000/360600/360603"},{"code":360681,"name":"贵溪","fullname":"贵溪市","level":"district","filename":"360000/360600/360681"}]},{"code":360700,"name":"赣州","fullname":"赣州市","level":"city","filename":"360000/360700","children":[{"code":360702,"name":"章贡","fullname":"章贡区","level":"district","filename":"360000/360700/360702"},{"code":360703,"name":"南康","fullname":"南康区","level":"district","filename":"360000/360700/360703"},{"code":360704,"name":"赣县区","fullname":"赣县区","level":"district","filename":"360000/360700/360704"},{"code":360722,"name":"信丰","fullname":"信丰县","level":"district","filename":"360000/360700/360722"},{"code":360723,"name":"大余","fullname":"大余县","level":"district","filename":"360000/360700/360723"},{"code":360724,"name":"上犹","fullname":"上犹县","level":"district","filename":"360000/360700/360724"},{"code":360725,"name":"崇义","fullname":"崇义县","level":"district","filename":"360000/360700/360725"},{"code":360726,"name":"安远","fullname":"安远县","level":"district","filename":"360000/360700/360726"},{"code":360728,"name":"定南","fullname":"定南县","level":"district","filename":"360000/360700/360728"},{"code":360729,"name":"全南","fullname":"全南县","level":"district","filename":"360000/360700/360729"},{"code":360730,"name":"宁都","fullname":"宁都县","level":"district","filename":"360000/360700/360730"},{"code":360731,"name":"于都","fullname":"于都县","level":"district","filename":"360000/360700/360731"},{"code":360732,"name":"兴国","fullname":"兴国县","level":"district","filename":"360000/360700/360732"},{"code":360733,"name":"会昌","fullname":"会昌县","level":"district","filename":"360000/360700/360733"},{"code":360734,"name":"寻乌","fullname":"寻乌县","level":"district","filename":"360000/360700/360734"},{"code":360735,"name":"石城","fullname":"石城县","level":"district","filename":"360000/360700/360735"},{"code":360781,"name":"瑞金","fullname":"瑞金市","level":"district","filename":"360000/360700/360781"},{"code":360783,"name":"龙南","fullname":"龙南市","level":"district","filename":"360000/360700/360783"}]},{"code":360800,"name":"吉安","fullname":"吉安市","level":"city","filename":"360000/360800","children":[{"code":360802,"name":"吉州","fullname":"吉州区","level":"district","filename":"360000/360800/360802"},{"code":360803,"name":"青原","fullname":"青原区","level":"district","filename":"360000/360800/360803"},{"code":360821,"name":"吉安","fullname":"吉安县","level":"district","filename":"360000/360800/360821"},{"code":360822,"name":"吉水","fullname":"吉水县","level":"district","filename":"360000/360800/360822"},{"code":360823,"name":"峡江","fullname":"峡江县","level":"district","filename":"360000/360800/360823"},{"code":360824,"name":"新干","fullname":"新干县","level":"district","filename":"360000/360800/360824"},{"code":360825,"name":"永丰","fullname":"永丰县","level":"district","filename":"360000/360800/360825"},{"code":360826,"name":"泰和","fullname":"泰和县","level":"district","filename":"360000/360800/360826"},{"code":360827,"name":"遂川","fullname":"遂川县","level":"district","filename":"360000/360800/360827"},{"code":360828,"name":"万安","fullname":"万安县","level":"district","filename":"360000/360800/360828"},{"code":360829,"name":"安福","fullname":"安福县","level":"district","filename":"360000/360800/360829"},{"code":360830,"name":"永新","fullname":"永新县","level":"district","filename":"360000/360800/360830"},{"code":360881,"name":"井冈山","fullname":"井冈山市","level":"district","filename":"360000/360800/360881"}]},{"code":360900,"name":"宜春","fullname":"宜春市","level":"city","filename":"360000/360900","children":[{"code":360902,"name":"袁州","fullname":"袁州区","level":"district","filename":"360000/360900/360902"},{"code":360921,"name":"奉新","fullname":"奉新县","level":"district","filename":"360000/360900/360921"},{"code":360922,"name":"万载","fullname":"万载县","level":"district","filename":"360000/360900/360922"},{"code":360923,"name":"上高","fullname":"上高县","level":"district","filename":"360000/360900/360923"},{"code":360924,"name":"宜丰","fullname":"宜丰县","level":"district","filename":"360000/360900/360924"},{"code":360925,"name":"靖安","fullname":"靖安县","level":"district","filename":"360000/360900/360925"},{"code":360926,"name":"铜鼓","fullname":"铜鼓县","level":"district","filename":"360000/360900/360926"},{"code":360981,"name":"丰城","fullname":"丰城市","level":"district","filename":"360000/360900/360981"},{"code":360982,"name":"樟树","fullname":"樟树市","level":"district","filename":"360000/360900/360982"},{"code":360983,"name":"高安","fullname":"高安市","level":"district","filename":"360000/360900/360983"}]},{"code":361000,"name":"抚州","fullname":"抚州市","level":"city","filename":"360000/361000","children":[{"code":361002,"name":"临川","fullname":"临川区","level":"district","filename":"360000/361000/361002"},{"code":361003,"name":"东乡","fullname":"东乡区","level":"district","filename":"360000/361000/361003"},{"code":361021,"name":"南城","fullname":"南城县","level":"district","filename":"360000/361000/361021"},{"code":361022,"name":"黎川","fullname":"黎川县","level":"district","filename":"360000/361000/361022"},{"code":361023,"name":"南丰","fullname":"南丰县","level":"district","filename":"360000/361000/361023"},{"code":361024,"name":"崇仁","fullname":"崇仁县","level":"district","filename":"360000/361000/361024"},{"code":361025,"name":"乐安","fullname":"乐安县","level":"district","filename":"360000/361000/361025"},{"code":361026,"name":"宜黄","fullname":"宜黄县","level":"district","filename":"360000/361000/361026"},{"code":361027,"name":"金溪","fullname":"金溪县","level":"district","filename":"360000/361000/361027"},{"code":361028,"name":"资溪","fullname":"资溪县","level":"district","filename":"360000/361000/361028"},{"code":361030,"name":"广昌","fullname":"广昌县","level":"district","filename":"360000/361000/361030"}]},{"code":361100,"name":"上饶","fullname":"上饶市","level":"city","filename":"360000/361100","children":[{"code":361102,"name":"信州","fullname":"信州区","level":"district","filename":"360000/361100/361102"},{"code":361103,"name":"广丰","fullname":"广丰区","level":"district","filename":"360000/361100/361103"},{"code":361104,"name":"广信","fullname":"广信区","level":"district","filename":"360000/361100/361104"},{"code":361123,"name":"玉山","fullname":"玉山县","level":"district","filename":"360000/361100/361123"},{"code":361124,"name":"铅山","fullname":"铅山县","level":"district","filename":"360000/361100/361124"},{"code":361125,"name":"横峰","fullname":"横峰县","level":"district","filename":"360000/361100/361125"},{"code":361126,"name":"弋阳","fullname":"弋阳县","level":"district","filename":"360000/361100/361126"},{"code":361127,"name":"余干","fullname":"余干县","level":"district","filename":"360000/361100/361127"},{"code":361128,"name":"鄱阳","fullname":"鄱阳县","level":"district","filename":"360000/361100/361128"},{"code":361129,"name":"万年","fullname":"万年县","level":"district","filename":"360000/361100/361129"},{"code":361130,"name":"婺源","fullname":"婺源县","level":"district","filename":"360000/361100/361130"},{"code":361181,"name":"德兴","fullname":"德兴市","level":"district","filename":"360000/361100/361181"}]}]},{"code":370000,"name":"山东","fullname":"山东省","level":"province","filename":"370000","children":[{"code":370100,"name":"济南","fullname":"济南市","level":"city","filename":"370000/370100","children":[{"code":370102,"name":"历下","fullname":"历下区","level":"district","filename":"370000/370100/370102"},{"code":370103,"name":"市中区","fullname":"市中区","level":"district","filename":"370000/370100/370103"},{"code":370104,"name":"槐荫","fullname":"槐荫区","level":"district","filename":"370000/370100/370104"},{"code":370105,"name":"天桥","fullname":"天桥区","level":"district","filename":"370000/370100/370105"},{"code":370112,"name":"历城","fullname":"历城区","level":"district","filename":"370000/370100/370112"},{"code":370113,"name":"长清","fullname":"长清区","level":"district","filename":"370000/370100/370113"},{"code":370114,"name":"章丘","fullname":"章丘区","level":"district","filename":"370000/370100/370114"},{"code":370115,"name":"济阳","fullname":"济阳区","level":"district","filename":"370000/370100/370115"},{"code":370116,"name":"莱芜","fullname":"莱芜区","level":"district","filename":"370000/370100/370116"},{"code":370117,"name":"钢城","fullname":"钢城区","level":"district","filename":"370000/370100/370117"},{"code":370124,"name":"平阴","fullname":"平阴县","level":"district","filename":"370000/370100/370124"},{"code":370126,"name":"商河","fullname":"商河县","level":"district","filename":"370000/370100/370126"}]},{"code":370200,"name":"青岛","fullname":"青岛市","level":"city","filename":"370000/370200","children":[{"code":370202,"name":"市南区","fullname":"市南区","level":"district","filename":"370000/370200/370202"},{"code":370203,"name":"市北区","fullname":"市北区","level":"district","filename":"370000/370200/370203"},{"code":370211,"name":"黄岛","fullname":"黄岛区","level":"district","filename":"370000/370200/370211"},{"code":370212,"name":"崂山","fullname":"崂山区","level":"district","filename":"370000/370200/370212"},{"code":370213,"name":"李沧","fullname":"李沧区","level":"district","filename":"370000/370200/370213"},{"code":370214,"name":"城阳","fullname":"城阳区","level":"district","filename":"370000/370200/370214"},{"code":370215,"name":"即墨","fullname":"即墨区","level":"district","filename":"370000/370200/370215"},{"code":370281,"name":"胶州","fullname":"胶州市","level":"district","filename":"370000/370200/370281"},{"code":370283,"name":"平度","fullname":"平度市","level":"district","filename":"370000/370200/370283"},{"code":370285,"name":"莱西","fullname":"莱西市","level":"district","filename":"370000/370200/370285"}]},{"code":370300,"name":"淄博","fullname":"淄博市","level":"city","filename":"370000/370300","children":[{"code":370302,"name":"淄川","fullname":"淄川区","level":"district","filename":"370000/370300/370302"},{"code":370303,"name":"张店","fullname":"张店区","level":"district","filename":"370000/370300/370303"},{"code":370304,"name":"博山","fullname":"博山区","level":"district","filename":"370000/370300/370304"},{"code":370305,"name":"临淄","fullname":"临淄区","level":"district","filename":"370000/370300/370305"},{"code":370306,"name":"周村","fullname":"周村区","level":"district","filename":"370000/370300/370306"},{"code":370321,"name":"桓台","fullname":"桓台县","level":"district","filename":"370000/370300/370321"},{"code":370322,"name":"高青","fullname":"高青县","level":"district","filename":"370000/370300/370322"},{"code":370323,"name":"沂源","fullname":"沂源县","level":"district","filename":"370000/370300/370323"}]},{"code":370400,"name":"枣庄","fullname":"枣庄市","level":"city","filename":"370000/370400","children":[{"code":370402,"name":"市中区","fullname":"市中区","level":"district","filename":"370000/370400/370402"},{"code":370403,"name":"薛城","fullname":"薛城区","level":"district","filename":"370000/370400/370403"},{"code":370404,"name":"峄城","fullname":"峄城区","level":"district","filename":"370000/370400/370404"},{"code":370405,"name":"台儿庄","fullname":"台儿庄区","level":"district","filename":"370000/370400/370405"},{"code":370406,"name":"山亭","fullname":"山亭区","level":"district","filename":"370000/370400/370406"},{"code":370481,"name":"滕州","fullname":"滕州市","level":"district","filename":"370000/370400/370481"}]},{"code":370500,"name":"东营","fullname":"东营市","level":"city","filename":"370000/370500","children":[{"code":370502,"name":"东营","fullname":"东营区","level":"district","filename":"370000/370500/370502"},{"code":370503,"name":"河口","fullname":"河口区","level":"district","filename":"370000/370500/370503"},{"code":370505,"name":"垦利","fullname":"垦利区","level":"district","filename":"370000/370500/370505"},{"code":370522,"name":"利津","fullname":"利津县","level":"district","filename":"370000/370500/370522"},{"code":370523,"name":"广饶","fullname":"广饶县","level":"district","filename":"370000/370500/370523"}]},{"code":370600,"name":"烟台","fullname":"烟台市","level":"city","filename":"370000/370600","children":[{"code":370602,"name":"芝罘","fullname":"芝罘区","level":"district","filename":"370000/370600/370602"},{"code":370611,"name":"福山","fullname":"福山区","level":"district","filename":"370000/370600/370611"},{"code":370612,"name":"牟平","fullname":"牟平区","level":"district","filename":"370000/370600/370612"},{"code":370613,"name":"莱山","fullname":"莱山区","level":"district","filename":"370000/370600/370613"},{"code":370614,"name":"蓬莱","fullname":"蓬莱区","level":"district","filename":"370000/370600/370614"},{"code":370681,"name":"龙口","fullname":"龙口市","level":"district","filename":"370000/370600/370681"},{"code":370682,"name":"莱阳","fullname":"莱阳市","level":"district","filename":"370000/370600/370682"},{"code":370683,"name":"莱州","fullname":"莱州市","level":"district","filename":"370000/370600/370683"},{"code":370685,"name":"招远","fullname":"招远市","level":"district","filename":"370000/370600/370685"},{"code":370686,"name":"栖霞","fullname":"栖霞市","level":"district","filename":"370000/370600/370686"},{"code":370687,"name":"海阳","fullname":"海阳市","level":"district","filename":"370000/370600/370687"}]},{"code":370700,"name":"潍坊","fullname":"潍坊市","level":"city","filename":"370000/370700","children":[{"code":370702,"name":"潍城","fullname":"潍城区","level":"district","filename":"370000/370700/370702"},{"code":370703,"name":"寒亭","fullname":"寒亭区","level":"district","filename":"370000/370700/370703"},{"code":370704,"name":"坊子","fullname":"坊子区","level":"district","filename":"370000/370700/370704"},{"code":370705,"name":"奎文","fullname":"奎文区","level":"district","filename":"370000/370700/370705"},{"code":370724,"name":"临朐","fullname":"临朐县","level":"district","filename":"370000/370700/370724"},{"code":370725,"name":"昌乐","fullname":"昌乐县","level":"district","filename":"370000/370700/370725"},{"code":370781,"name":"青州","fullname":"青州市","level":"district","filename":"370000/370700/370781"},{"code":370782,"name":"诸城","fullname":"诸城市","level":"district","filename":"370000/370700/370782"},{"code":370783,"name":"寿光","fullname":"寿光市","level":"district","filename":"370000/370700/370783"},{"code":370784,"name":"安丘","fullname":"安丘市","level":"district","filename":"370000/370700/370784"},{"code":370785,"name":"高密","fullname":"高密市","level":"district","filename":"370000/370700/370785"},{"code":370786,"name":"昌邑","fullname":"昌邑市","level":"district","filename":"370000/370700/370786"}]},{"code":370800,"name":"济宁","fullname":"济宁市","level":"city","filename":"370000/370800","children":[{"code":370811,"name":"任城","fullname":"任城区","level":"district","filename":"370000/370800/370811"},{"code":370812,"name":"兖州","fullname":"兖州区","level":"district","filename":"370000/370800/370812"},{"code":370826,"name":"微山","fullname":"微山县","level":"district","filename":"370000/370800/370826"},{"code":370827,"name":"鱼台","fullname":"鱼台县","level":"district","filename":"370000/370800/370827"},{"code":370828,"name":"金乡","fullname":"金乡县","level":"district","filename":"370000/370800/370828"},{"code":370829,"name":"嘉祥","fullname":"嘉祥县","level":"district","filename":"370000/370800/370829"},{"code":370830,"name":"汶上","fullname":"汶上县","level":"district","filename":"370000/370800/370830"},{"code":370831,"name":"泗水","fullname":"泗水县","level":"district","filename":"370000/370800/370831"},{"code":370832,"name":"梁山","fullname":"梁山县","level":"district","filename":"370000/370800/370832"},{"code":370881,"name":"曲阜","fullname":"曲阜市","level":"district","filename":"370000/370800/370881"},{"code":370883,"name":"邹城","fullname":"邹城市","level":"district","filename":"370000/370800/370883"}]},{"code":370900,"name":"泰安","fullname":"泰安市","level":"city","filename":"370000/370900","children":[{"code":370902,"name":"泰山","fullname":"泰山区","level":"district","filename":"370000/370900/370902"},{"code":370911,"name":"岱岳","fullname":"岱岳区","level":"district","filename":"370000/370900/370911"},{"code":370921,"name":"宁阳","fullname":"宁阳县","level":"district","filename":"370000/370900/370921"},{"code":370923,"name":"东平","fullname":"东平县","level":"district","filename":"370000/370900/370923"},{"code":370982,"name":"新泰","fullname":"新泰市","level":"district","filename":"370000/370900/370982"},{"code":370983,"name":"肥城","fullname":"肥城市","level":"district","filename":"370000/370900/370983"}]},{"code":371000,"name":"威海","fullname":"威海市","level":"city","filename":"370000/371000","children":[{"code":371002,"name":"环翠","fullname":"环翠区","level":"district","filename":"370000/371000/371002"},{"code":371003,"name":"文登","fullname":"文登区","level":"district","filename":"370000/371000/371003"},{"code":371082,"name":"荣成","fullname":"荣成市","level":"district","filename":"370000/371000/371082"},{"code":371083,"name":"乳山","fullname":"乳山市","level":"district","filename":"370000/371000/371083"}]},{"code":371100,"name":"日照","fullname":"日照市","level":"city","filename":"370000/371100","children":[{"code":371102,"name":"东港","fullname":"东港区","level":"district","filename":"370000/371100/371102"},{"code":371103,"name":"岚山","fullname":"岚山区","level":"district","filename":"370000/371100/371103"},{"code":371121,"name":"五莲","fullname":"五莲县","level":"district","filename":"370000/371100/371121"},{"code":371122,"name":"莒县","fullname":"莒县","level":"district","filename":"370000/371100/371122"}]},{"code":371300,"name":"临沂","fullname":"临沂市","level":"city","filename":"370000/371300","children":[{"code":371302,"name":"兰山","fullname":"兰山区","level":"district","filename":"370000/371300/371302"},{"code":371311,"name":"罗庄","fullname":"罗庄区","level":"district","filename":"370000/371300/371311"},{"code":371312,"name":"河东","fullname":"河东区","level":"district","filename":"370000/371300/371312"},{"code":371321,"name":"沂南","fullname":"沂南县","level":"district","filename":"370000/371300/371321"},{"code":371322,"name":"郯城","fullname":"郯城县","level":"district","filename":"370000/371300/371322"},{"code":371323,"name":"沂水","fullname":"沂水县","level":"district","filename":"370000/371300/371323"},{"code":371324,"name":"兰陵","fullname":"兰陵县","level":"district","filename":"370000/371300/371324"},{"code":371325,"name":"费县","fullname":"费县","level":"district","filename":"370000/371300/371325"},{"code":371326,"name":"平邑","fullname":"平邑县","level":"district","filename":"370000/371300/371326"},{"code":371327,"name":"莒南","fullname":"莒南县","level":"district","filename":"370000/371300/371327"},{"code":371328,"name":"蒙阴","fullname":"蒙阴县","level":"district","filename":"370000/371300/371328"},{"code":371329,"name":"临沭","fullname":"临沭县","level":"district","filename":"370000/371300/371329"}]},{"code":371400,"name":"德州","fullname":"德州市","level":"city","filename":"370000/371400","children":[{"code":371402,"name":"德城","fullname":"德城区","level":"district","filename":"370000/371400/371402"},{"code":371403,"name":"陵城","fullname":"陵城区","level":"district","filename":"370000/371400/371403"},{"code":371422,"name":"宁津","fullname":"宁津县","level":"district","filename":"370000/371400/371422"},{"code":371423,"name":"庆云","fullname":"庆云县","level":"district","filename":"370000/371400/371423"},{"code":371424,"name":"临邑","fullname":"临邑县","level":"district","filename":"370000/371400/371424"},{"code":371425,"name":"齐河","fullname":"齐河县","level":"district","filename":"370000/371400/371425"},{"code":371426,"name":"平原","fullname":"平原县","level":"district","filename":"370000/371400/371426"},{"code":371427,"name":"夏津","fullname":"夏津县","level":"district","filename":"370000/371400/371427"},{"code":371428,"name":"武城","fullname":"武城县","level":"district","filename":"370000/371400/371428"},{"code":371481,"name":"乐陵","fullname":"乐陵市","level":"district","filename":"370000/371400/371481"},{"code":371482,"name":"禹城","fullname":"禹城市","level":"district","filename":"370000/371400/371482"}]},{"code":371500,"name":"聊城","fullname":"聊城市","level":"city","filename":"370000/371500","children":[{"code":371502,"name":"东昌府","fullname":"东昌府区","level":"district","filename":"370000/371500/371502"},{"code":371503,"name":"茌平","fullname":"茌平区","level":"district","filename":"370000/371500/371503"},{"code":371521,"name":"阳谷","fullname":"阳谷县","level":"district","filename":"370000/371500/371521"},{"code":371522,"name":"莘县","fullname":"莘县","level":"district","filename":"370000/371500/371522"},{"code":371524,"name":"东阿","fullname":"东阿县","level":"district","filename":"370000/371500/371524"},{"code":371525,"name":"冠县","fullname":"冠县","level":"district","filename":"370000/371500/371525"},{"code":371526,"name":"高唐","fullname":"高唐县","level":"district","filename":"370000/371500/371526"},{"code":371581,"name":"临清","fullname":"临清市","level":"district","filename":"370000/371500/371581"}]},{"code":371600,"name":"滨州","fullname":"滨州市","level":"city","filename":"370000/371600","children":[{"code":371602,"name":"滨城","fullname":"滨城区","level":"district","filename":"370000/371600/371602"},{"code":371603,"name":"沾化","fullname":"沾化区","level":"district","filename":"370000/371600/371603"},{"code":371621,"name":"惠民","fullname":"惠民县","level":"district","filename":"370000/371600/371621"},{"code":371622,"name":"阳信","fullname":"阳信县","level":"district","filename":"370000/371600/371622"},{"code":371623,"name":"无棣","fullname":"无棣县","level":"district","filename":"370000/371600/371623"},{"code":371625,"name":"博兴","fullname":"博兴县","level":"district","filename":"370000/371600/371625"},{"code":371681,"name":"邹平","fullname":"邹平市","level":"district","filename":"370000/371600/371681"}]},{"code":371700,"name":"菏泽","fullname":"菏泽市","level":"city","filename":"370000/371700","children":[{"code":371702,"name":"牡丹","fullname":"牡丹区","level":"district","filename":"370000/371700/371702"},{"code":371703,"name":"定陶","fullname":"定陶区","level":"district","filename":"370000/371700/371703"},{"code":371721,"name":"曹县","fullname":"曹县","level":"district","filename":"370000/371700/371721"},{"code":371722,"name":"单县","fullname":"单县","level":"district","filename":"370000/371700/371722"},{"code":371723,"name":"成武","fullname":"成武县","level":"district","filename":"370000/371700/371723"},{"code":371724,"name":"巨野","fullname":"巨野县","level":"district","filename":"370000/371700/371724"},{"code":371725,"name":"郓城","fullname":"郓城县","level":"district","filename":"370000/371700/371725"},{"code":371726,"name":"鄄城","fullname":"鄄城县","level":"district","filename":"370000/371700/371726"},{"code":371728,"name":"东明","fullname":"东明县","level":"district","filename":"370000/371700/371728"}]}]},{"code":410000,"name":"河南","fullname":"河南省","level":"province","filename":"410000","children":[{"code":410100,"name":"郑州","fullname":"郑州市","level":"city","filename":"410000/410100","children":[{"code":410102,"name":"中原","fullname":"中原区","level":"district","filename":"410000/410100/410102"},{"code":410103,"name":"二七","fullname":"二七区","level":"district","filename":"410000/410100/410103"},{"code":410104,"name":"管城","fullname":"管城回族区","level":"district","filename":"410000/410100/410104"},{"code":410105,"name":"金水","fullname":"金水区","level":"district","filename":"410000/410100/410105"},{"code":410106,"name":"上街","fullname":"上街区","level":"district","filename":"410000/410100/410106"},{"code":410108,"name":"惠济","fullname":"惠济区","level":"district","filename":"410000/410100/410108"},{"code":410122,"name":"中牟","fullname":"中牟县","level":"district","filename":"410000/410100/410122"},{"code":410181,"name":"巩义","fullname":"巩义市","level":"district","filename":"410000/410100/410181"},{"code":410182,"name":"荥阳","fullname":"荥阳市","level":"district","filename":"410000/410100/410182"},{"code":410183,"name":"新密","fullname":"新密市","level":"district","filename":"410000/410100/410183"},{"code":410184,"name":"新郑","fullname":"新郑市","level":"district","filename":"410000/410100/410184"},{"code":410185,"name":"登封","fullname":"登封市","level":"district","filename":"410000/410100/410185"}]},{"code":410200,"name":"开封","fullname":"开封市","level":"city","filename":"410000/410200","children":[{"code":410202,"name":"龙亭","fullname":"龙亭区","level":"district","filename":"410000/410200/410202"},{"code":410203,"name":"顺河","fullname":"顺河回族区","level":"district","filename":"410000/410200/410203"},{"code":410204,"name":"鼓楼","fullname":"鼓楼区","level":"district","filename":"410000/410200/410204"},{"code":410205,"name":"禹王台","fullname":"禹王台区","level":"district","filename":"410000/410200/410205"},{"code":410212,"name":"祥符","fullname":"祥符区","level":"district","filename":"410000/410200/410212"},{"code":410221,"name":"杞县","fullname":"杞县","level":"district","filename":"410000/410200/410221"},{"code":410222,"name":"通许","fullname":"通许县","level":"district","filename":"410000/410200/410222"},{"code":410223,"name":"尉氏","fullname":"尉氏县","level":"district","filename":"410000/410200/410223"},{"code":410225,"name":"兰考","fullname":"兰考县","level":"district","filename":"410000/410200/410225"}]},{"code":410300,"name":"洛阳","fullname":"洛阳市","level":"city","filename":"410000/410300","children":[{"code":410302,"name":"老城","fullname":"老城区","level":"district","filename":"410000/410300/410302"},{"code":410303,"name":"西工","fullname":"西工区","level":"district","filename":"410000/410300/410303"},{"code":410304,"name":"瀍河","fullname":"瀍河回族区","level":"district","filename":"410000/410300/410304"},{"code":410305,"name":"涧西","fullname":"涧西区","level":"district","filename":"410000/410300/410305"},{"code":410307,"name":"偃师","fullname":"偃师区","level":"district","filename":"410000/410300/410307"},{"code":410308,"name":"孟津","fullname":"孟津区","level":"district","filename":"410000/410300/410308"},{"code":410311,"name":"洛龙","fullname":"洛龙区","level":"district","filename":"410000/410300/410311"},{"code":410323,"name":"新安","fullname":"新安县","level":"district","filename":"410000/410300/410323"},{"code":410324,"name":"栾川","fullname":"栾川县","level":"district","filename":"410000/410300/410324"},{"code":410325,"name":"嵩县","fullname":"嵩县","level":"district","filename":"410000/410300/410325"},{"code":410326,"name":"汝阳","fullname":"汝阳县","level":"district","filename":"410000/410300/410326"},{"code":410327,"name":"宜阳","fullname":"宜阳县","level":"district","filename":"410000/410300/410327"},{"code":410328,"name":"洛宁","fullname":"洛宁县","level":"district","filename":"410000/410300/410328"},{"code":410329,"name":"伊川","fullname":"伊川县","level":"district","filename":"410000/410300/410329"}]},{"code":410400,"name":"平顶山","fullname":"平顶山市","level":"city","filename":"410000/410400","children":[{"code":410402,"name":"新华","fullname":"新华区","level":"district","filename":"410000/410400/410402"},{"code":410403,"name":"卫东","fullname":"卫东区","level":"district","filename":"410000/410400/410403"},{"code":410404,"name":"石龙","fullname":"石龙区","level":"district","filename":"410000/410400/410404"},{"code":410411,"name":"湛河","fullname":"湛河区","level":"district","filename":"410000/410400/410411"},{"code":410421,"name":"宝丰","fullname":"宝丰县","level":"district","filename":"410000/410400/410421"},{"code":410422,"name":"叶县","fullname":"叶县","level":"district","filename":"410000/410400/410422"},{"code":410423,"name":"鲁山","fullname":"鲁山县","level":"district","filename":"410000/410400/410423"},{"code":410425,"name":"郏县","fullname":"郏县","level":"district","filename":"410000/410400/410425"},{"code":410481,"name":"舞钢","fullname":"舞钢市","level":"district","filename":"410000/410400/410481"},{"code":410482,"name":"汝州","fullname":"汝州市","level":"district","filename":"410000/410400/410482"}]},{"code":410500,"name":"安阳","fullname":"安阳市","level":"city","filename":"410000/410500","children":[{"code":410502,"name":"文峰","fullname":"文峰区","level":"district","filename":"410000/410500/410502"},{"code":410503,"name":"北关","fullname":"北关区","level":"district","filename":"410000/410500/410503"},{"code":410505,"name":"殷都","fullname":"殷都区","level":"district","filename":"410000/410500/410505"},{"code":410506,"name":"龙安","fullname":"龙安区","level":"district","filename":"410000/410500/410506"},{"code":410522,"name":"安阳","fullname":"安阳县","level":"district","filename":"410000/410500/410522"},{"code":410523,"name":"汤阴","fullname":"汤阴县","level":"district","filename":"410000/410500/410523"},{"code":410526,"name":"滑县","fullname":"滑县","level":"district","filename":"410000/410500/410526"},{"code":410527,"name":"内黄","fullname":"内黄县","level":"district","filename":"410000/410500/410527"},{"code":410581,"name":"林州","fullname":"林州市","level":"district","filename":"410000/410500/410581"}]},{"code":410600,"name":"鹤壁","fullname":"鹤壁市","level":"city","filename":"410000/410600","children":[{"code":410602,"name":"鹤山","fullname":"鹤山区","level":"district","filename":"410000/410600/410602"},{"code":410603,"name":"山城","fullname":"山城区","level":"district","filename":"410000/410600/410603"},{"code":410611,"name":"淇滨","fullname":"淇滨区","level":"district","filename":"410000/410600/410611"},{"code":410621,"name":"浚县","fullname":"浚县","level":"district","filename":"410000/410600/410621"},{"code":410622,"name":"淇县","fullname":"淇县","level":"district","filename":"410000/410600/410622"}]},{"code":410700,"name":"新乡","fullname":"新乡市","level":"city","filename":"410000/410700","children":[{"code":410702,"name":"红旗区","fullname":"红旗区","level":"district","filename":"410000/410700/410702"},{"code":410703,"name":"卫滨","fullname":"卫滨区","level":"district","filename":"410000/410700/410703"},{"code":410704,"name":"凤泉","fullname":"凤泉区","level":"district","filename":"410000/410700/410704"},{"code":410711,"name":"牧野","fullname":"牧野区","level":"district","filename":"410000/410700/410711"},{"code":410721,"name":"新乡","fullname":"新乡县","level":"district","filename":"410000/410700/410721"},{"code":410724,"name":"获嘉","fullname":"获嘉县","level":"district","filename":"410000/410700/410724"},{"code":410725,"name":"原阳","fullname":"原阳县","level":"district","filename":"410000/410700/410725"},{"code":410726,"name":"延津","fullname":"延津县","level":"district","filename":"410000/410700/410726"},{"code":410727,"name":"封丘","fullname":"封丘县","level":"district","filename":"410000/410700/410727"},{"code":410781,"name":"卫辉","fullname":"卫辉市","level":"district","filename":"410000/410700/410781"},{"code":410782,"name":"辉县市","fullname":"辉县市","level":"district","filename":"410000/410700/410782"},{"code":410783,"name":"长垣","fullname":"长垣市","level":"district","filename":"410000/410700/410783"}]},{"code":410800,"name":"焦作","fullname":"焦作市","level":"city","filename":"410000/410800","children":[{"code":410802,"name":"解放","fullname":"解放区","level":"district","filename":"410000/410800/410802"},{"code":410803,"name":"中站","fullname":"中站区","level":"district","filename":"410000/410800/410803"},{"code":410804,"name":"马村","fullname":"马村区","level":"district","filename":"410000/410800/410804"},{"code":410811,"name":"山阳","fullname":"山阳区","level":"district","filename":"410000/410800/410811"},{"code":410821,"name":"修武","fullname":"修武县","level":"district","filename":"410000/410800/410821"},{"code":410822,"name":"博爱","fullname":"博爱县","level":"district","filename":"410000/410800/410822"},{"code":410823,"name":"武陟","fullname":"武陟县","level":"district","filename":"410000/410800/410823"},{"code":410825,"name":"温县","fullname":"温县","level":"district","filename":"410000/410800/410825"},{"code":410882,"name":"沁阳","fullname":"沁阳市","level":"district","filename":"410000/410800/410882"},{"code":410883,"name":"孟州","fullname":"孟州市","level":"district","filename":"410000/410800/410883"}]},{"code":410900,"name":"濮阳","fullname":"濮阳市","level":"city","filename":"410000/410900","children":[{"code":410902,"name":"华龙","fullname":"华龙区","level":"district","filename":"410000/410900/410902"},{"code":410922,"name":"清丰","fullname":"清丰县","level":"district","filename":"410000/410900/410922"},{"code":410923,"name":"南乐","fullname":"南乐县","level":"district","filename":"410000/410900/410923"},{"code":410926,"name":"范县","fullname":"范县","level":"district","filename":"410000/410900/410926"},{"code":410927,"name":"台前","fullname":"台前县","level":"district","filename":"410000/410900/410927"},{"code":410928,"name":"濮阳","fullname":"濮阳县","level":"district","filename":"410000/410900/410928"}]},{"code":411000,"name":"许昌","fullname":"许昌市","level":"city","filename":"410000/411000","children":[{"code":411002,"name":"魏都","fullname":"魏都区","level":"district","filename":"410000/411000/411002"},{"code":411003,"name":"建安","fullname":"建安区","level":"district","filename":"410000/411000/411003"},{"code":411024,"name":"鄢陵","fullname":"鄢陵县","level":"district","filename":"410000/411000/411024"},{"code":411025,"name":"襄城","fullname":"襄城县","level":"district","filename":"410000/411000/411025"},{"code":411081,"name":"禹州","fullname":"禹州市","level":"district","filename":"410000/411000/411081"},{"code":411082,"name":"长葛","fullname":"长葛市","level":"district","filename":"410000/411000/411082"}]},{"code":411100,"name":"漯河","fullname":"漯河市","level":"city","filename":"410000/411100","children":[{"code":411102,"name":"源汇","fullname":"源汇区","level":"district","filename":"410000/411100/411102"},{"code":411103,"name":"郾城","fullname":"郾城区","level":"district","filename":"410000/411100/411103"},{"code":411104,"name":"召陵","fullname":"召陵区","level":"district","filename":"410000/411100/411104"},{"code":411121,"name":"舞阳","fullname":"舞阳县","level":"district","filename":"410000/411100/411121"},{"code":411122,"name":"临颍","fullname":"临颍县","level":"district","filename":"410000/411100/411122"}]},{"code":411200,"name":"三门峡","fullname":"三门峡市","level":"city","filename":"410000/411200","children":[{"code":411202,"name":"湖滨","fullname":"湖滨区","level":"district","filename":"410000/411200/411202"},{"code":411203,"name":"陕州","fullname":"陕州区","level":"district","filename":"410000/411200/411203"},{"code":411221,"name":"渑池","fullname":"渑池县","level":"district","filename":"410000/411200/411221"},{"code":411224,"name":"卢氏","fullname":"卢氏县","level":"district","filename":"410000/411200/411224"},{"code":411281,"name":"义马","fullname":"义马市","level":"district","filename":"410000/411200/411281"},{"code":411282,"name":"灵宝","fullname":"灵宝市","level":"district","filename":"410000/411200/411282"}]},{"code":411300,"name":"南阳","fullname":"南阳市","level":"city","filename":"410000/411300","children":[{"code":411302,"name":"宛城","fullname":"宛城区","level":"district","filename":"410000/411300/411302"},{"code":411303,"name":"卧龙","fullname":"卧龙区","level":"district","filename":"410000/411300/411303"},{"code":411321,"name":"南召","fullname":"南召县","level":"district","filename":"410000/411300/411321"},{"code":411322,"name":"方城","fullname":"方城县","level":"district","filename":"410000/411300/411322"},{"code":411323,"name":"西峡","fullname":"西峡县","level":"district","filename":"410000/411300/411323"},{"code":411324,"name":"镇平","fullname":"镇平县","level":"district","filename":"410000/411300/411324"},{"code":411325,"name":"内乡","fullname":"内乡县","level":"district","filename":"410000/411300/411325"},{"code":411326,"name":"淅川","fullname":"淅川县","level":"district","filename":"410000/411300/411326"},{"code":411327,"name":"社旗县","fullname":"社旗县","level":"district","filename":"410000/411300/411327"},{"code":411328,"name":"唐河","fullname":"唐河县","level":"district","filename":"410000/411300/411328"},{"code":411329,"name":"新野","fullname":"新野县","level":"district","filename":"410000/411300/411329"},{"code":411330,"name":"桐柏","fullname":"桐柏县","level":"district","filename":"410000/411300/411330"},{"code":411381,"name":"邓州","fullname":"邓州市","level":"district","filename":"410000/411300/411381"}]},{"code":411400,"name":"商丘","fullname":"商丘市","level":"city","filename":"410000/411400","children":[{"code":411402,"name":"梁园","fullname":"梁园区","level":"district","filename":"410000/411400/411402"},{"code":411403,"name":"睢阳","fullname":"睢阳区","level":"district","filename":"410000/411400/411403"},{"code":411421,"name":"民权","fullname":"民权县","level":"district","filename":"410000/411400/411421"},{"code":411422,"name":"睢县","fullname":"睢县","level":"district","filename":"410000/411400/411422"},{"code":411423,"name":"宁陵","fullname":"宁陵县","level":"district","filename":"410000/411400/411423"},{"code":411424,"name":"柘城","fullname":"柘城县","level":"district","filename":"410000/411400/411424"},{"code":411425,"name":"虞城","fullname":"虞城县","level":"district","filename":"410000/411400/411425"},{"code":411426,"name":"夏邑","fullname":"夏邑县","level":"district","filename":"410000/411400/411426"},{"code":411481,"name":"永城","fullname":"永城市","level":"district","filename":"410000/411400/411481"}]},{"code":411500,"name":"信阳","fullname":"信阳市","level":"city","filename":"410000/411500","children":[{"code":411502,"name":"浉河","fullname":"浉河区","level":"district","filename":"410000/411500/411502"},{"code":411503,"name":"平桥","fullname":"平桥区","level":"district","filename":"410000/411500/411503"},{"code":411521,"name":"罗山","fullname":"罗山县","level":"district","filename":"410000/411500/411521"},{"code":411522,"name":"光山","fullname":"光山县","level":"district","filename":"410000/411500/411522"},{"code":411523,"name":"新县","fullname":"新县","level":"district","filename":"410000/411500/411523"},{"code":411524,"name":"商城","fullname":"商城县","level":"district","filename":"410000/411500/411524"},{"code":411525,"name":"固始","fullname":"固始县","level":"district","filename":"410000/411500/411525"},{"code":411526,"name":"潢川","fullname":"潢川县","level":"district","filename":"410000/411500/411526"},{"code":411527,"name":"淮滨","fullname":"淮滨县","level":"district","filename":"410000/411500/411527"},{"code":411528,"name":"息县","fullname":"息县","level":"district","filename":"410000/411500/411528"}]},{"code":411600,"name":"周口","fullname":"周口市","level":"city","filename":"410000/411600","children":[{"code":411602,"name":"川汇","fullname":"川汇区","level":"district","filename":"410000/411600/411602"},{"code":411603,"name":"淮阳","fullname":"淮阳区","level":"district","filename":"410000/411600/411603"},{"code":411621,"name":"扶沟","fullname":"扶沟县","level":"district","filename":"410000/411600/411621"},{"code":411622,"name":"西华","fullname":"西华县","level":"district","filename":"410000/411600/411622"},{"code":411623,"name":"商水","fullname":"商水县","level":"district","filename":"410000/411600/411623"},{"code":411624,"name":"沈丘","fullname":"沈丘县","level":"district","filename":"410000/411600/411624"},{"code":411625,"name":"郸城","fullname":"郸城县","level":"district","filename":"410000/411600/411625"},{"code":411627,"name":"太康","fullname":"太康县","level":"district","filename":"410000/411600/411627"},{"code":411628,"name":"鹿邑","fullname":"鹿邑县","level":"district","filename":"410000/411600/411628"},{"code":411681,"name":"项城","fullname":"项城市","level":"district","filename":"410000/411600/411681"}]},{"code":411700,"name":"驻马店","fullname":"驻马店市","level":"city","filename":"410000/411700","children":[{"code":411702,"name":"驿城","fullname":"驿城区","level":"district","filename":"410000/411700/411702"},{"code":411721,"name":"西平","fullname":"西平县","level":"district","filename":"410000/411700/411721"},{"code":411722,"name":"上蔡","fullname":"上蔡县","level":"district","filename":"410000/411700/411722"},{"code":411723,"name":"平舆","fullname":"平舆县","level":"district","filename":"410000/411700/411723"},{"code":411724,"name":"正阳","fullname":"正阳县","level":"district","filename":"410000/411700/411724"},{"code":411725,"name":"确山","fullname":"确山县","level":"district","filename":"410000/411700/411725"},{"code":411726,"name":"泌阳","fullname":"泌阳县","level":"district","filename":"410000/411700/411726"},{"code":411727,"name":"汝南","fullname":"汝南县","level":"district","filename":"410000/411700/411727"},{"code":411728,"name":"遂平","fullname":"遂平县","level":"district","filename":"410000/411700/411728"},{"code":411729,"name":"新蔡","fullname":"新蔡县","level":"district","filename":"410000/411700/411729"}]},{"code":419001,"name":"济源","fullname":"济源市","level":"city","filename":"410000/419001"}]},{"code":420000,"name":"湖北","fullname":"湖北省","level":"province","filename":"420000","children":[{"code":420100,"name":"武汉","fullname":"武汉市","level":"city","filename":"420000/420100","children":[{"code":420102,"name":"江岸","fullname":"江岸区","level":"district","filename":"420000/420100/420102"},{"code":420103,"name":"江汉","fullname":"江汉区","level":"district","filename":"420000/420100/420103"},{"code":420104,"name":"硚口","fullname":"硚口区","level":"district","filename":"420000/420100/420104"},{"code":420105,"name":"汉阳","fullname":"汉阳区","level":"district","filename":"420000/420100/420105"},{"code":420106,"name":"武昌","fullname":"武昌区","level":"district","filename":"420000/420100/420106"},{"code":420107,"name":"青山","fullname":"青山区","level":"district","filename":"420000/420100/420107"},{"code":420111,"name":"洪山","fullname":"洪山区","level":"district","filename":"420000/420100/420111"},{"code":420112,"name":"东西湖","fullname":"东西湖区","level":"district","filename":"420000/420100/420112"},{"code":420113,"name":"汉南","fullname":"汉南区","level":"district","filename":"420000/420100/420113"},{"code":420114,"name":"蔡甸","fullname":"蔡甸区","level":"district","filename":"420000/420100/420114"},{"code":420115,"name":"江夏","fullname":"江夏区","level":"district","filename":"420000/420100/420115"},{"code":420116,"name":"黄陂","fullname":"黄陂区","level":"district","filename":"420000/420100/420116"},{"code":420117,"name":"新洲","fullname":"新洲区","level":"district","filename":"420000/420100/420117"}]},{"code":420200,"name":"黄石","fullname":"黄石市","level":"city","filename":"420000/420200","children":[{"code":420202,"name":"黄石港","fullname":"黄石港区","level":"district","filename":"420000/420200/420202"},{"code":420203,"name":"西塞山","fullname":"西塞山区","level":"district","filename":"420000/420200/420203"},{"code":420204,"name":"下陆","fullname":"下陆区","level":"district","filename":"420000/420200/420204"},{"code":420205,"name":"铁山","fullname":"铁山区","level":"district","filename":"420000/420200/420205"},{"code":420222,"name":"阳新","fullname":"阳新县","level":"district","filename":"420000/420200/420222"},{"code":420281,"name":"大冶","fullname":"大冶市","level":"district","filename":"420000/420200/420281"}]},{"code":420300,"name":"十堰","fullname":"十堰市","level":"city","filename":"420000/420300","children":[{"code":420302,"name":"茅箭","fullname":"茅箭区","level":"district","filename":"420000/420300/420302"},{"code":420303,"name":"张湾","fullname":"张湾区","level":"district","filename":"420000/420300/420303"},{"code":420304,"name":"郧阳","fullname":"郧阳区","level":"district","filename":"420000/420300/420304"},{"code":420322,"name":"郧西","fullname":"郧西县","level":"district","filename":"420000/420300/420322"},{"code":420323,"name":"竹山","fullname":"竹山县","level":"district","filename":"420000/420300/420323"},{"code":420324,"name":"竹溪","fullname":"竹溪县","level":"district","filename":"420000/420300/420324"},{"code":420325,"name":"房县","fullname":"房县","level":"district","filename":"420000/420300/420325"},{"code":420381,"name":"丹江口","fullname":"丹江口市","level":"district","filename":"420000/420300/420381"}]},{"code":420500,"name":"宜昌","fullname":"宜昌市","level":"city","filename":"420000/420500","children":[{"code":420502,"name":"西陵","fullname":"西陵区","level":"district","filename":"420000/420500/420502"},{"code":420503,"name":"伍家岗","fullname":"伍家岗区","level":"district","filename":"420000/420500/420503"},{"code":420504,"name":"点军","fullname":"点军区","level":"district","filename":"420000/420500/420504"},{"code":420505,"name":"猇亭","fullname":"猇亭区","level":"district","filename":"420000/420500/420505"},{"code":420506,"name":"夷陵","fullname":"夷陵区","level":"district","filename":"420000/420500/420506"},{"code":420525,"name":"远安","fullname":"远安县","level":"district","filename":"420000/420500/420525"},{"code":420526,"name":"兴山","fullname":"兴山县","level":"district","filename":"420000/420500/420526"},{"code":420527,"name":"秭归","fullname":"秭归县","level":"district","filename":"420000/420500/420527"},{"code":420528,"name":"长阳","fullname":"长阳土家族自治县","level":"district","filename":"420000/420500/420528"},{"code":420529,"name":"五峰","fullname":"五峰土家族自治县","level":"district","filename":"420000/420500/420529"},{"code":420581,"name":"宜都","fullname":"宜都市","level":"district","filename":"420000/420500/420581"},{"code":420582,"name":"当阳","fullname":"当阳市","level":"district","filename":"420000/420500/420582"},{"code":420583,"name":"枝江","fullname":"枝江市","level":"district","filename":"420000/420500/420583"}]},{"code":420600,"name":"襄阳","fullname":"襄阳市","level":"city","filename":"420000/420600","children":[{"code":420602,"name":"襄城","fullname":"襄城区","level":"district","filename":"420000/420600/420602"},{"code":420606,"name":"樊城","fullname":"樊城区","level":"district","filename":"420000/420600/420606"},{"code":420607,"name":"襄州","fullname":"襄州区","level":"district","filename":"420000/420600/420607"},{"code":420624,"name":"南漳","fullname":"南漳县","level":"district","filename":"420000/420600/420624"},{"code":420625,"name":"谷城","fullname":"谷城县","level":"district","filename":"420000/420600/420625"},{"code":420626,"name":"保康","fullname":"保康县","level":"district","filename":"420000/420600/420626"},{"code":420682,"name":"老河口","fullname":"老河口市","level":"district","filename":"420000/420600/420682"},{"code":420683,"name":"枣阳","fullname":"枣阳市","level":"district","filename":"420000/420600/420683"},{"code":420684,"name":"宜城","fullname":"宜城市","level":"district","filename":"420000/420600/420684"}]},{"code":420700,"name":"鄂州","fullname":"鄂州市","level":"city","filename":"420000/420700","children":[{"code":420702,"name":"梁子湖","fullname":"梁子湖区","level":"district","filename":"420000/420700/420702"},{"code":420703,"name":"华容","fullname":"华容区","level":"district","filename":"420000/420700/420703"},{"code":420704,"name":"鄂城","fullname":"鄂城区","level":"district","filename":"420000/420700/420704"}]},{"code":420800,"name":"荆门","fullname":"荆门市","level":"city","filename":"420000/420800","children":[{"code":420802,"name":"东宝","fullname":"东宝区","level":"district","filename":"420000/420800/420802"},{"code":420804,"name":"掇刀","fullname":"掇刀区","level":"district","filename":"420000/420800/420804"},{"code":420822,"name":"沙洋","fullname":"沙洋县","level":"district","filename":"420000/420800/420822"},{"code":420881,"name":"钟祥","fullname":"钟祥市","level":"district","filename":"420000/420800/420881"},{"code":420882,"name":"京山","fullname":"京山市","level":"district","filename":"420000/420800/420882"}]},{"code":420900,"name":"孝感","fullname":"孝感市","level":"city","filename":"420000/420900","children":[{"code":420902,"name":"孝南","fullname":"孝南区","level":"district","filename":"420000/420900/420902"},{"code":420921,"name":"孝昌","fullname":"孝昌县","level":"district","filename":"420000/420900/420921"},{"code":420922,"name":"大悟","fullname":"大悟县","level":"district","filename":"420000/420900/420922"},{"code":420923,"name":"云梦","fullname":"云梦县","level":"district","filename":"420000/420900/420923"},{"code":420981,"name":"应城","fullname":"应城市","level":"district","filename":"420000/420900/420981"},{"code":420982,"name":"安陆","fullname":"安陆市","level":"district","filename":"420000/420900/420982"},{"code":420984,"name":"汉川","fullname":"汉川市","level":"district","filename":"420000/420900/420984"}]},{"code":421000,"name":"荆州","fullname":"荆州市","level":"city","filename":"420000/421000","children":[{"code":421002,"name":"沙市区","fullname":"沙市区","level":"district","filename":"420000/421000/421002"},{"code":421003,"name":"荆州","fullname":"荆州区","level":"district","filename":"420000/421000/421003"},{"code":421022,"name":"公安","fullname":"公安县","level":"district","filename":"420000/421000/421022"},{"code":421023,"name":"监利","fullname":"监利市","level":"district","filename":"420000/421000/421023"},{"code":421024,"name":"江陵","fullname":"江陵县","level":"district","filename":"420000/421000/421024"},{"code":421081,"name":"石首","fullname":"石首市","level":"district","filename":"420000/421000/421081"},{"code":421083,"name":"洪湖","fullname":"洪湖市","level":"district","filename":"420000/421000/421083"},{"code":421087,"name":"松滋","fullname":"松滋市","level":"district","filename":"420000/421000/421087"}]},{"code":421100,"name":"黄冈","fullname":"黄冈市","level":"city","filename":"420000/421100","children":[{"code":421102,"name":"黄州","fullname":"黄州区","level":"district","filename":"420000/421100/421102"},{"code":421121,"name":"团风","fullname":"团风县","level":"district","filename":"420000/421100/421121"},{"code":421122,"name":"红安","fullname":"红安县","level":"district","filename":"420000/421100/421122"},{"code":421123,"name":"罗田","fullname":"罗田县","level":"district","filename":"420000/421100/421123"},{"code":421124,"name":"英山","fullname":"英山县","level":"district","filename":"420000/421100/421124"},{"code":421125,"name":"浠水","fullname":"浠水县","level":"district","filename":"420000/421100/421125"},{"code":421126,"name":"蕲春","fullname":"蕲春县","level":"district","filename":"420000/421100/421126"},{"code":421127,"name":"黄梅","fullname":"黄梅县","level":"district","filename":"420000/421100/421127"},{"code":421181,"name":"麻城","fullname":"麻城市","level":"district","filename":"420000/421100/421181"},{"code":421182,"name":"武穴","fullname":"武穴市","level":"district","filename":"420000/421100/421182"}]},{"code":421200,"name":"咸宁","fullname":"咸宁市","level":"city","filename":"420000/421200","children":[{"code":421202,"name":"咸安","fullname":"咸安区","level":"district","filename":"420000/421200/421202"},{"code":421221,"name":"嘉鱼","fullname":"嘉鱼县","level":"district","filename":"420000/421200/421221"},{"code":421222,"name":"通城","fullname":"通城县","level":"district","filename":"420000/421200/421222"},{"code":421223,"name":"崇阳","fullname":"崇阳县","level":"district","filename":"420000/421200/421223"},{"code":421224,"name":"通山","fullname":"通山县","level":"district","filename":"420000/421200/421224"},{"code":421281,"name":"赤壁","fullname":"赤壁市","level":"district","filename":"420000/421200/421281"}]},{"code":421300,"name":"随州","fullname":"随州市","level":"city","filename":"420000/421300","children":[{"code":421303,"name":"曾都","fullname":"曾都区","level":"district","filename":"420000/421300/421303"},{"code":421321,"name":"随县","fullname":"随县","level":"district","filename":"420000/421300/421321"},{"code":421381,"name":"广水","fullname":"广水市","level":"district","filename":"420000/421300/421381"}]},{"code":422800,"name":"恩施","fullname":"恩施土家族苗族自治州","level":"city","filename":"420000/422800","children":[{"code":422801,"name":"恩施","fullname":"恩施市","level":"district","filename":"420000/422800/422801"},{"code":422802,"name":"利川","fullname":"利川市","level":"district","filename":"420000/422800/422802"},{"code":422822,"name":"建始","fullname":"建始县","level":"district","filename":"420000/422800/422822"},{"code":422823,"name":"巴东","fullname":"巴东县","level":"district","filename":"420000/422800/422823"},{"code":422825,"name":"宣恩","fullname":"宣恩县","level":"district","filename":"420000/422800/422825"},{"code":422826,"name":"咸丰","fullname":"咸丰县","level":"district","filename":"420000/422800/422826"},{"code":422827,"name":"来凤","fullname":"来凤县","level":"district","filename":"420000/422800/422827"},{"code":422828,"name":"鹤峰","fullname":"鹤峰县","level":"district","filename":"420000/422800/422828"}]},{"code":429004,"name":"仙桃","fullname":"仙桃市","level":"city","filename":"420000/429004"},{"code":429005,"name":"潜江","fullname":"潜江市","level":"city","filename":"420000/429005"},{"code":429006,"name":"天门","fullname":"天门市","level":"city","filename":"420000/429006"},{"code":429021,"name":"神农架","fullname":"神农架林区","level":"city","filename":"420000/429021"}]},{"code":430000,"name":"湖南","fullname":"湖南省","level":"province","filename":"430000","children":[{"code":430100,"name":"长沙","fullname":"长沙市","level":"city","filename":"430000/430100","children":[{"code":430102,"name":"芙蓉","fullname":"芙蓉区","level":"district","filename":"430000/430100/430102"},{"code":430103,"name":"天心","fullname":"天心区","level":"district","filename":"430000/430100/430103"},{"code":430104,"name":"岳麓","fullname":"岳麓区","level":"district","filename":"430000/430100/430104"},{"code":430105,"name":"开福","fullname":"开福区","level":"district","filename":"430000/430100/430105"},{"code":430111,"name":"雨花","fullname":"雨花区","level":"district","filename":"430000/430100/430111"},{"code":430112,"name":"望城","fullname":"望城区","level":"district","filename":"430000/430100/430112"},{"code":430121,"name":"长沙","fullname":"长沙县","level":"district","filename":"430000/430100/430121"},{"code":430181,"name":"浏阳","fullname":"浏阳市","level":"district","filename":"430000/430100/430181"},{"code":430182,"name":"宁乡","fullname":"宁乡市","level":"district","filename":"430000/430100/430182"}]},{"code":430200,"name":"株洲","fullname":"株洲市","level":"city","filename":"430000/430200","children":[{"code":430202,"name":"荷塘","fullname":"荷塘区","level":"district","filename":"430000/430200/430202"},{"code":430203,"name":"芦淞","fullname":"芦淞区","level":"district","filename":"430000/430200/430203"},{"code":430204,"name":"石峰","fullname":"石峰区","level":"district","filename":"430000/430200/430204"},{"code":430211,"name":"天元","fullname":"天元区","level":"district","filename":"430000/430200/430211"},{"code":430212,"name":"渌口","fullname":"渌口区","level":"district","filename":"430000/430200/430212"},{"code":430223,"name":"攸县","fullname":"攸县","level":"district","filename":"430000/430200/430223"},{"code":430224,"name":"茶陵","fullname":"茶陵县","level":"district","filename":"430000/430200/430224"},{"code":430225,"name":"炎陵","fullname":"炎陵县","level":"district","filename":"430000/430200/430225"},{"code":430281,"name":"醴陵","fullname":"醴陵市","level":"district","filename":"430000/430200/430281"}]},{"code":430300,"name":"湘潭","fullname":"湘潭市","level":"city","filename":"430000/430300","children":[{"code":430302,"name":"雨湖","fullname":"雨湖区","level":"district","filename":"430000/430300/430302"},{"code":430304,"name":"岳塘","fullname":"岳塘区","level":"district","filename":"430000/430300/430304"},{"code":430321,"name":"湘潭","fullname":"湘潭县","level":"district","filename":"430000/430300/430321"},{"code":430381,"name":"湘乡","fullname":"湘乡市","level":"district","filename":"430000/430300/430381"},{"code":430382,"name":"韶山","fullname":"韶山市","level":"district","filename":"430000/430300/430382"}]},{"code":430400,"name":"衡阳","fullname":"衡阳市","level":"city","filename":"430000/430400","children":[{"code":430405,"name":"珠晖","fullname":"珠晖区","level":"district","filename":"430000/430400/430405"},{"code":430406,"name":"雁峰","fullname":"雁峰区","level":"district","filename":"430000/430400/430406"},{"code":430407,"name":"石鼓","fullname":"石鼓区","level":"district","filename":"430000/430400/430407"},{"code":430408,"name":"蒸湘","fullname":"蒸湘区","level":"district","filename":"430000/430400/430408"},{"code":430412,"name":"南岳","fullname":"南岳区","level":"district","filename":"430000/430400/430412"},{"code":430421,"name":"衡阳","fullname":"衡阳县","level":"district","filename":"430000/430400/430421"},{"code":430422,"name":"衡南","fullname":"衡南县","level":"district","filename":"430000/430400/430422"},{"code":430423,"name":"衡山","fullname":"衡山县","level":"district","filename":"430000/430400/430423"},{"code":430424,"name":"衡东","fullname":"衡东县","level":"district","filename":"430000/430400/430424"},{"code":430426,"name":"祁东","fullname":"祁东县","level":"district","filename":"430000/430400/430426"},{"code":430481,"name":"耒阳","fullname":"耒阳市","level":"district","filename":"430000/430400/430481"},{"code":430482,"name":"常宁","fullname":"常宁市","level":"district","filename":"430000/430400/430482"}]},{"code":430500,"name":"邵阳","fullname":"邵阳市","level":"city","filename":"430000/430500","children":[{"code":430502,"name":"双清","fullname":"双清区","level":"district","filename":"430000/430500/430502"},{"code":430503,"name":"大祥","fullname":"大祥区","level":"district","filename":"430000/430500/430503"},{"code":430511,"name":"北塔","fullname":"北塔区","level":"district","filename":"430000/430500/430511"},{"code":430522,"name":"新邵","fullname":"新邵县","level":"district","filename":"430000/430500/430522"},{"code":430523,"name":"邵阳","fullname":"邵阳县","level":"district","filename":"430000/430500/430523"},{"code":430524,"name":"隆回","fullname":"隆回县","level":"district","filename":"430000/430500/430524"},{"code":430525,"name":"洞口","fullname":"洞口县","level":"district","filename":"430000/430500/430525"},{"code":430527,"name":"绥宁","fullname":"绥宁县","level":"district","filename":"430000/430500/430527"},{"code":430528,"name":"新宁","fullname":"新宁县","level":"district","filename":"430000/430500/430528"},{"code":430529,"name":"城步","fullname":"城步苗族自治县","level":"district","filename":"430000/430500/430529"},{"code":430581,"name":"武冈","fullname":"武冈市","level":"district","filename":"430000/430500/430581"},{"code":430582,"name":"邵东","fullname":"邵东市","level":"district","filename":"430000/430500/430582"}]},{"code":430600,"name":"岳阳","fullname":"岳阳市","level":"city","filename":"430000/430600","children":[{"code":430602,"name":"岳阳楼","fullname":"岳阳楼区","level":"district","filename":"430000/430600/430602"},{"code":430603,"name":"云溪","fullname":"云溪区","level":"district","filename":"430000/430600/430603"},{"code":430611,"name":"君山","fullname":"君山区","level":"district","filename":"430000/430600/430611"},{"code":430621,"name":"岳阳","fullname":"岳阳县","level":"district","filename":"430000/430600/430621"},{"code":430623,"name":"华容","fullname":"华容县","level":"district","filename":"430000/430600/430623"},{"code":430624,"name":"湘阴","fullname":"湘阴县","level":"district","filename":"430000/430600/430624"},{"code":430626,"name":"平江","fullname":"平江县","level":"district","filename":"430000/430600/430626"},{"code":430681,"name":"汨罗","fullname":"汨罗市","level":"district","filename":"430000/430600/430681"},{"code":430682,"name":"临湘","fullname":"临湘市","level":"district","filename":"430000/430600/430682"}]},{"code":430700,"name":"常德","fullname":"常德市","level":"city","filename":"430000/430700","children":[{"code":430702,"name":"武陵","fullname":"武陵区","level":"district","filename":"430000/430700/430702"},{"code":430703,"name":"鼎城","fullname":"鼎城区","level":"district","filename":"430000/430700/430703"},{"code":430721,"name":"安乡","fullname":"安乡县","level":"district","filename":"430000/430700/430721"},{"code":430722,"name":"汉寿","fullname":"汉寿县","level":"district","filename":"430000/430700/430722"},{"code":430723,"name":"澧县","fullname":"澧县","level":"district","filename":"430000/430700/430723"},{"code":430724,"name":"临澧","fullname":"临澧县","level":"district","filename":"430000/430700/430724"},{"code":430725,"name":"桃源","fullname":"桃源县","level":"district","filename":"430000/430700/430725"},{"code":430726,"name":"石门","fullname":"石门县","level":"district","filename":"430000/430700/430726"},{"code":430781,"name":"津市市","fullname":"津市市","level":"district","filename":"430000/430700/430781"}]},{"code":430800,"name":"张家界","fullname":"张家界市","level":"city","filename":"430000/430800","children":[{"code":430802,"name":"永定","fullname":"永定区","level":"district","filename":"430000/430800/430802"},{"code":430811,"name":"武陵源","fullname":"武陵源区","level":"district","filename":"430000/430800/430811"},{"code":430821,"name":"慈利","fullname":"慈利县","level":"district","filename":"430000/430800/430821"},{"code":430822,"name":"桑植","fullname":"桑植县","level":"district","filename":"430000/430800/430822"}]},{"code":430900,"name":"益阳","fullname":"益阳市","level":"city","filename":"430000/430900","children":[{"code":430902,"name":"资阳","fullname":"资阳区","level":"district","filename":"430000/430900/430902"},{"code":430903,"name":"赫山","fullname":"赫山区","level":"district","filename":"430000/430900/430903"},{"code":430921,"name":"南县","fullname":"南县","level":"district","filename":"430000/430900/430921"},{"code":430922,"name":"桃江","fullname":"桃江县","level":"district","filename":"430000/430900/430922"},{"code":430923,"name":"安化","fullname":"安化县","level":"district","filename":"430000/430900/430923"},{"code":430981,"name":"沅江","fullname":"沅江市","level":"district","filename":"430000/430900/430981"}]},{"code":431000,"name":"郴州","fullname":"郴州市","level":"city","filename":"430000/431000","children":[{"code":431002,"name":"北湖","fullname":"北湖区","level":"district","filename":"430000/431000/431002"},{"code":431003,"name":"苏仙","fullname":"苏仙区","level":"district","filename":"430000/431000/431003"},{"code":431021,"name":"桂阳","fullname":"桂阳县","level":"district","filename":"430000/431000/431021"},{"code":431022,"name":"宜章","fullname":"宜章县","level":"district","filename":"430000/431000/431022"},{"code":431023,"name":"永兴","fullname":"永兴县","level":"district","filename":"430000/431000/431023"},{"code":431024,"name":"嘉禾","fullname":"嘉禾县","level":"district","filename":"430000/431000/431024"},{"code":431025,"name":"临武","fullname":"临武县","level":"district","filename":"430000/431000/431025"},{"code":431026,"name":"汝城","fullname":"汝城县","level":"district","filename":"430000/431000/431026"},{"code":431027,"name":"桂东","fullname":"桂东县","level":"district","filename":"430000/431000/431027"},{"code":431028,"name":"安仁","fullname":"安仁县","level":"district","filename":"430000/431000/431028"},{"code":431081,"name":"资兴","fullname":"资兴市","level":"district","filename":"430000/431000/431081"}]},{"code":431100,"name":"永州","fullname":"永州市","level":"city","filename":"430000/431100","children":[{"code":431102,"name":"零陵","fullname":"零陵区","level":"district","filename":"430000/431100/431102"},{"code":431103,"name":"冷水滩","fullname":"冷水滩区","level":"district","filename":"430000/431100/431103"},{"code":431121,"name":"祁阳","fullname":"祁阳市","level":"district","filename":"430000/431100/431121"},{"code":431122,"name":"东安","fullname":"东安县","level":"district","filename":"430000/431100/431122"},{"code":431123,"name":"双牌","fullname":"双牌县","level":"district","filename":"430000/431100/431123"},{"code":431124,"name":"道县","fullname":"道县","level":"district","filename":"430000/431100/431124"},{"code":431125,"name":"江永","fullname":"江永县","level":"district","filename":"430000/431100/431125"},{"code":431126,"name":"宁远","fullname":"宁远县","level":"district","filename":"430000/431100/431126"},{"code":431127,"name":"蓝山","fullname":"蓝山县","level":"district","filename":"430000/431100/431127"},{"code":431128,"name":"新田","fullname":"新田县","level":"district","filename":"430000/431100/431128"},{"code":431129,"name":"江华","fullname":"江华瑶族自治县","level":"district","filename":"430000/431100/431129"}]},{"code":431200,"name":"怀化","fullname":"怀化市","level":"city","filename":"430000/431200","children":[{"code":431202,"name":"鹤城","fullname":"鹤城区","level":"district","filename":"430000/431200/431202"},{"code":431221,"name":"中方","fullname":"中方县","level":"district","filename":"430000/431200/431221"},{"code":431222,"name":"沅陵","fullname":"沅陵县","level":"district","filename":"430000/431200/431222"},{"code":431223,"name":"辰溪","fullname":"辰溪县","level":"district","filename":"430000/431200/431223"},{"code":431224,"name":"溆浦","fullname":"溆浦县","level":"district","filename":"430000/431200/431224"},{"code":431225,"name":"会同","fullname":"会同县","level":"district","filename":"430000/431200/431225"},{"code":431226,"name":"麻阳","fullname":"麻阳苗族自治县","level":"district","filename":"430000/431200/431226"},{"code":431227,"name":"新晃","fullname":"新晃侗族自治县","level":"district","filename":"430000/431200/431227"},{"code":431228,"name":"芷江","fullname":"芷江侗族自治县","level":"district","filename":"430000/431200/431228"},{"code":431229,"name":"靖州","fullname":"靖州苗族侗族自治县","level":"district","filename":"430000/431200/431229"},{"code":431230,"name":"通道","fullname":"通道侗族自治县","level":"district","filename":"430000/431200/431230"},{"code":431281,"name":"洪江","fullname":"洪江市","level":"district","filename":"430000/431200/431281"}]},{"code":431300,"name":"娄底","fullname":"娄底市","level":"city","filename":"430000/431300","children":[{"code":431302,"name":"娄星","fullname":"娄星区","level":"district","filename":"430000/431300/431302"},{"code":431321,"name":"双峰","fullname":"双峰县","level":"district","filename":"430000/431300/431321"},{"code":431322,"name":"新化","fullname":"新化县","level":"district","filename":"430000/431300/431322"},{"code":431381,"name":"冷水江","fullname":"冷水江市","level":"district","filename":"430000/431300/431381"},{"code":431382,"name":"涟源","fullname":"涟源市","level":"district","filename":"430000/431300/431382"}]},{"code":433100,"name":"湘西","fullname":"湘西土家族苗族自治州","level":"city","filename":"430000/433100","children":[{"code":433101,"name":"吉首","fullname":"吉首市","level":"district","filename":"430000/433100/433101"},{"code":433122,"name":"泸溪","fullname":"泸溪县","level":"district","filename":"430000/433100/433122"},{"code":433123,"name":"凤凰","fullname":"凤凰县","level":"district","filename":"430000/433100/433123"},{"code":433124,"name":"花垣","fullname":"花垣县","level":"district","filename":"430000/433100/433124"},{"code":433125,"name":"保靖","fullname":"保靖县","level":"district","filename":"430000/433100/433125"},{"code":433126,"name":"古丈","fullname":"古丈县","level":"district","filename":"430000/433100/433126"},{"code":433127,"name":"永顺","fullname":"永顺县","level":"district","filename":"430000/433100/433127"},{"code":433130,"name":"龙山","fullname":"龙山县","level":"district","filename":"430000/433100/433130"}]}]},{"code":440000,"name":"广东","fullname":"广东省","level":"province","filename":"440000","children":[{"code":440100,"name":"广州","fullname":"广州市","level":"city","filename":"440000/440100","children":[{"code":440103,"name":"荔湾","fullname":"荔湾区","level":"district","filename":"440000/440100/440103"},{"code":440104,"name":"越秀","fullname":"越秀区","level":"district","filename":"440000/440100/440104"},{"code":440105,"name":"海珠","fullname":"海珠区","level":"district","filename":"440000/440100/440105"},{"code":440106,"name":"天河","fullname":"天河区","level":"district","filename":"440000/440100/440106"},{"code":440111,"name":"白云","fullname":"白云区","level":"district","filename":"440000/440100/440111"},{"code":440112,"name":"黄埔","fullname":"黄埔区","level":"district","filename":"440000/440100/440112"},{"code":440113,"name":"番禺","fullname":"番禺区","level":"district","filename":"440000/440100/440113"},{"code":440114,"name":"花都","fullname":"花都区","level":"district","filename":"440000/440100/440114"},{"code":440115,"name":"南沙","fullname":"南沙区","level":"district","filename":"440000/440100/440115"},{"code":440117,"name":"从化","fullname":"从化区","level":"district","filename":"440000/440100/440117"},{"code":440118,"name":"增城","fullname":"增城区","level":"district","filename":"440000/440100/440118"}]},{"code":440200,"name":"韶关","fullname":"韶关市","level":"city","filename":"440000/440200","children":[{"code":440203,"name":"武江","fullname":"武江区","level":"district","filename":"440000/440200/440203"},{"code":440204,"name":"浈江","fullname":"浈江区","level":"district","filename":"440000/440200/440204"},{"code":440205,"name":"曲江","fullname":"曲江区","level":"district","filename":"440000/440200/440205"},{"code":440222,"name":"始兴","fullname":"始兴县","level":"district","filename":"440000/440200/440222"},{"code":440224,"name":"仁化","fullname":"仁化县","level":"district","filename":"440000/440200/440224"},{"code":440229,"name":"翁源","fullname":"翁源县","level":"district","filename":"440000/440200/440229"},{"code":440232,"name":"乳源","fullname":"乳源瑶族自治县","level":"district","filename":"440000/440200/440232"},{"code":440233,"name":"新丰","fullname":"新丰县","level":"district","filename":"440000/440200/440233"},{"code":440281,"name":"乐昌","fullname":"乐昌市","level":"district","filename":"440000/440200/440281"},{"code":440282,"name":"南雄","fullname":"南雄市","level":"district","filename":"440000/440200/440282"}]},{"code":440300,"name":"深圳","fullname":"深圳市","level":"city","filename":"440000/440300","children":[{"code":440303,"name":"罗湖","fullname":"罗湖区","level":"district","filename":"440000/440300/440303"},{"code":440304,"name":"福田","fullname":"福田区","level":"district","filename":"440000/440300/440304"},{"code":440305,"name":"南山","fullname":"南山区","level":"district","filename":"440000/440300/440305"},{"code":440306,"name":"宝安","fullname":"宝安区","level":"district","filename":"440000/440300/440306"},{"code":440307,"name":"龙岗","fullname":"龙岗区","level":"district","filename":"440000/440300/440307"},{"code":440308,"name":"盐田","fullname":"盐田区","level":"district","filename":"440000/440300/440308"},{"code":440309,"name":"龙华","fullname":"龙华区","level":"district","filename":"440000/440300/440309"},{"code":440310,"name":"坪山","fullname":"坪山区","level":"district","filename":"440000/440300/440310"},{"code":440311,"name":"光明","fullname":"光明区","level":"district","filename":"440000/440300/440311"}]},{"code":440400,"name":"珠海","fullname":"珠海市","level":"city","filename":"440000/440400","children":[{"code":440402,"name":"香洲","fullname":"香洲区","level":"district","filename":"440000/440400/440402"},{"code":440403,"name":"斗门","fullname":"斗门区","level":"district","filename":"440000/440400/440403"},{"code":440404,"name":"金湾","fullname":"金湾区","level":"district","filename":"440000/440400/440404"}]},{"code":440500,"name":"汕头","fullname":"汕头市","level":"city","filename":"440000/440500","children":[{"code":440507,"name":"龙湖","fullname":"龙湖区","level":"district","filename":"440000/440500/440507"},{"code":440511,"name":"金平","fullname":"金平区","level":"district","filename":"440000/440500/440511"},{"code":440512,"name":"濠江","fullname":"濠江区","level":"district","filename":"440000/440500/440512"},{"code":440513,"name":"潮阳","fullname":"潮阳区","level":"district","filename":"440000/440500/440513"},{"code":440514,"name":"潮南","fullname":"潮南区","level":"district","filename":"440000/440500/440514"},{"code":440515,"name":"澄海","fullname":"澄海区","level":"district","filename":"440000/440500/440515"},{"code":440523,"name":"南澳","fullname":"南澳县","level":"district","filename":"440000/440500/440523"}]},{"code":440600,"name":"佛山","fullname":"佛山市","level":"city","filename":"440000/440600","children":[{"code":440604,"name":"禅城","fullname":"禅城区","level":"district","filename":"440000/440600/440604"},{"code":440605,"name":"南海","fullname":"南海区","level":"district","filename":"440000/440600/440605"},{"code":440606,"name":"顺德","fullname":"顺德区","level":"district","filename":"440000/440600/440606"},{"code":440607,"name":"三水","fullname":"三水区","level":"district","filename":"440000/440600/440607"},{"code":440608,"name":"高明","fullname":"高明区","level":"district","filename":"440000/440600/440608"}]},{"code":440700,"name":"江门","fullname":"江门市","level":"city","filename":"440000/440700","children":[{"code":440703,"name":"蓬江","fullname":"蓬江区","level":"district","filename":"440000/440700/440703"},{"code":440704,"name":"江海","fullname":"江海区","level":"district","filename":"440000/440700/440704"},{"code":440705,"name":"新会","fullname":"新会区","level":"district","filename":"440000/440700/440705"},{"code":440781,"name":"台山","fullname":"台山市","level":"district","filename":"440000/440700/440781"},{"code":440783,"name":"开平","fullname":"开平市","level":"district","filename":"440000/440700/440783"},{"code":440784,"name":"鹤山","fullname":"鹤山市","level":"district","filename":"440000/440700/440784"},{"code":440785,"name":"恩平","fullname":"恩平市","level":"district","filename":"440000/440700/440785"}]},{"code":440800,"name":"湛江","fullname":"湛江市","level":"city","filename":"440000/440800","children":[{"code":440802,"name":"赤坎","fullname":"赤坎区","level":"district","filename":"440000/440800/440802"},{"code":440803,"name":"霞山","fullname":"霞山区","level":"district","filename":"440000/440800/440803"},{"code":440804,"name":"坡头","fullname":"坡头区","level":"district","filename":"440000/440800/440804"},{"code":440811,"name":"麻章","fullname":"麻章区","level":"district","filename":"440000/440800/440811"},{"code":440823,"name":"遂溪","fullname":"遂溪县","level":"district","filename":"440000/440800/440823"},{"code":440825,"name":"徐闻","fullname":"徐闻县","level":"district","filename":"440000/440800/440825"},{"code":440881,"name":"廉江","fullname":"廉江市","level":"district","filename":"440000/440800/440881"},{"code":440882,"name":"雷州","fullname":"雷州市","level":"district","filename":"440000/440800/440882"},{"code":440883,"name":"吴川","fullname":"吴川市","level":"district","filename":"440000/440800/440883"}]},{"code":440900,"name":"茂名","fullname":"茂名市","level":"city","filename":"440000/440900","children":[{"code":440902,"name":"茂南","fullname":"茂南区","level":"district","filename":"440000/440900/440902"},{"code":440904,"name":"电白","fullname":"电白区","level":"district","filename":"440000/440900/440904"},{"code":440981,"name":"高州","fullname":"高州市","level":"district","filename":"440000/440900/440981"},{"code":440982,"name":"化州","fullname":"化州市","level":"district","filename":"440000/440900/440982"},{"code":440983,"name":"信宜","fullname":"信宜市","level":"district","filename":"440000/440900/440983"}]},{"code":441200,"name":"肇庆","fullname":"肇庆市","level":"city","filename":"440000/441200","children":[{"code":441202,"name":"端州","fullname":"端州区","level":"district","filename":"440000/441200/441202"},{"code":441203,"name":"鼎湖","fullname":"鼎湖区","level":"district","filename":"440000/441200/441203"},{"code":441204,"name":"高要","fullname":"高要区","level":"district","filename":"440000/441200/441204"},{"code":441223,"name":"广宁","fullname":"广宁县","level":"district","filename":"440000/441200/441223"},{"code":441224,"name":"怀集","fullname":"怀集县","level":"district","filename":"440000/441200/441224"},{"code":441225,"name":"封开","fullname":"封开县","level":"district","filename":"440000/441200/441225"},{"code":441226,"name":"德庆","fullname":"德庆县","level":"district","filename":"440000/441200/441226"},{"code":441284,"name":"四会","fullname":"四会市","level":"district","filename":"440000/441200/441284"}]},{"code":441300,"name":"惠州","fullname":"惠州市","level":"city","filename":"440000/441300","children":[{"code":441302,"name":"惠城","fullname":"惠城区","level":"district","filename":"440000/441300/441302"},{"code":441303,"name":"惠阳","fullname":"惠阳区","level":"district","filename":"440000/441300/441303"},{"code":441322,"name":"博罗","fullname":"博罗县","level":"district","filename":"440000/441300/441322"},{"code":441323,"name":"惠东","fullname":"惠东县","level":"district","filename":"440000/441300/441323"},{"code":441324,"name":"龙门","fullname":"龙门县","level":"district","filename":"440000/441300/441324"}]},{"code":441400,"name":"梅州","fullname":"梅州市","level":"city","filename":"440000/441400","children":[{"code":441402,"name":"梅江","fullname":"梅江区","level":"district","filename":"440000/441400/441402"},{"code":441403,"name":"梅县区","fullname":"梅县区","level":"district","filename":"440000/441400/441403"},{"code":441422,"name":"大埔","fullname":"大埔县","level":"district","filename":"440000/441400/441422"},{"code":441423,"name":"丰顺","fullname":"丰顺县","level":"district","filename":"440000/441400/441423"},{"code":441424,"name":"五华","fullname":"五华县","level":"district","filename":"440000/441400/441424"},{"code":441426,"name":"平远","fullname":"平远县","level":"district","filename":"440000/441400/441426"},{"code":441427,"name":"蕉岭","fullname":"蕉岭县","level":"district","filename":"440000/441400/441427"},{"code":441481,"name":"兴宁","fullname":"兴宁市","level":"district","filename":"440000/441400/441481"}]},{"code":441500,"name":"汕尾","fullname":"汕尾市","level":"city","filename":"440000/441500","children":[{"code":441502,"name":"城区","fullname":"城区","level":"district","filename":"440000/441500/441502"},{"code":441521,"name":"海丰","fullname":"海丰县","level":"district","filename":"440000/441500/441521"},{"code":441523,"name":"陆河","fullname":"陆河县","level":"district","filename":"440000/441500/441523"},{"code":441581,"name":"陆丰","fullname":"陆丰市","level":"district","filename":"440000/441500/441581"}]},{"code":441600,"name":"河源","fullname":"河源市","level":"city","filename":"440000/441600","children":[{"code":441602,"name":"源城","fullname":"源城区","level":"district","filename":"440000/441600/441602"},{"code":441621,"name":"紫金","fullname":"紫金县","level":"district","filename":"440000/441600/441621"},{"code":441622,"name":"龙川","fullname":"龙川县","level":"district","filename":"440000/441600/441622"},{"code":441623,"name":"连平","fullname":"连平县","level":"district","filename":"440000/441600/441623"},{"code":441624,"name":"和平","fullname":"和平县","level":"district","filename":"440000/441600/441624"},{"code":441625,"name":"东源","fullname":"东源县","level":"district","filename":"440000/441600/441625"}]},{"code":441700,"name":"阳江","fullname":"阳江市","level":"city","filename":"440000/441700","children":[{"code":441702,"name":"江城","fullname":"江城区","level":"district","filename":"440000/441700/441702"},{"code":441704,"name":"阳东","fullname":"阳东区","level":"district","filename":"440000/441700/441704"},{"code":441721,"name":"阳西","fullname":"阳西县","level":"district","filename":"440000/441700/441721"},{"code":441781,"name":"阳春","fullname":"阳春市","level":"district","filename":"440000/441700/441781"}]},{"code":441800,"name":"清远","fullname":"清远市","level":"city","filename":"440000/441800","children":[{"code":441802,"name":"清城","fullname":"清城区","level":"district","filename":"440000/441800/441802"},{"code":441803,"name":"清新区","fullname":"清新区","level":"district","filename":"440000/441800/441803"},{"code":441821,"name":"佛冈","fullname":"佛冈县","level":"district","filename":"440000/441800/441821"},{"code":441823,"name":"阳山","fullname":"阳山县","level":"district","filename":"440000/441800/441823"},{"code":441825,"name":"连山","fullname":"连山壮族瑶族自治县","level":"district","filename":"440000/441800/441825"},{"code":441826,"name":"连南","fullname":"连南瑶族自治县","level":"district","filename":"440000/441800/441826"},{"code":441881,"name":"英德","fullname":"英德市","level":"district","filename":"440000/441800/441881"},{"code":441882,"name":"连州","fullname":"连州市","level":"district","filename":"440000/441800/441882"}]},{"code":441900,"name":"东莞","fullname":"东莞市","level":"city","filename":"440000/441900"},{"code":442000,"name":"中山","fullname":"中山市","level":"city","filename":"440000/442000"},{"code":445100,"name":"潮州","fullname":"潮州市","level":"city","filename":"440000/445100","children":[{"code":445102,"name":"湘桥","fullname":"湘桥区","level":"district","filename":"440000/445100/445102"},{"code":445103,"name":"潮安","fullname":"潮安区","level":"district","filename":"440000/445100/445103"},{"code":445122,"name":"饶平","fullname":"饶平县","level":"district","filename":"440000/445100/445122"}]},{"code":445200,"name":"揭阳","fullname":"揭阳市","level":"city","filename":"440000/445200","children":[{"code":445202,"name":"榕城","fullname":"榕城区","level":"district","filename":"440000/445200/445202"},{"code":445203,"name":"揭东","fullname":"揭东区","level":"district","filename":"440000/445200/445203"},{"code":445222,"name":"揭西","fullname":"揭西县","level":"district","filename":"440000/445200/445222"},{"code":445224,"name":"惠来","fullname":"惠来县","level":"district","filename":"440000/445200/445224"},{"code":445281,"name":"普宁","fullname":"普宁市","level":"district","filename":"440000/445200/445281"}]},{"code":445300,"name":"云浮","fullname":"云浮市","level":"city","filename":"440000/445300","children":[{"code":445302,"name":"云城","fullname":"云城区","level":"district","filename":"440000/445300/445302"},{"code":445303,"name":"云安","fullname":"云安区","level":"district","filename":"440000/445300/445303"},{"code":445321,"name":"新兴","fullname":"新兴县","level":"district","filename":"440000/445300/445321"},{"code":445322,"name":"郁南","fullname":"郁南县","level":"district","filename":"440000/445300/445322"},{"code":445381,"name":"罗定","fullname":"罗定市","level":"district","filename":"440000/445300/445381"}]}]},{"code":450000,"name":"广西","fullname":"广西壮族自治区","level":"province","filename":"450000","children":[{"code":450100,"name":"南宁","fullname":"南宁市","level":"city","filename":"450000/450100","children":[{"code":450102,"name":"兴宁","fullname":"兴宁区","level":"district","filename":"450000/450100/450102"},{"code":450103,"name":"青秀","fullname":"青秀区","level":"district","filename":"450000/450100/450103"},{"code":450105,"name":"江南","fullname":"江南区","level":"district","filename":"450000/450100/450105"},{"code":450107,"name":"西乡塘","fullname":"西乡塘区","level":"district","filename":"450000/450100/450107"},{"code":450108,"name":"良庆","fullname":"良庆区","level":"district","filename":"450000/450100/450108"},{"code":450109,"name":"邕宁","fullname":"邕宁区","level":"district","filename":"450000/450100/450109"},{"code":450110,"name":"武鸣","fullname":"武鸣区","level":"district","filename":"450000/450100/450110"},{"code":450123,"name":"隆安","fullname":"隆安县","level":"district","filename":"450000/450100/450123"},{"code":450124,"name":"马山","fullname":"马山县","level":"district","filename":"450000/450100/450124"},{"code":450125,"name":"上林","fullname":"上林县","level":"district","filename":"450000/450100/450125"},{"code":450126,"name":"宾阳","fullname":"宾阳县","level":"district","filename":"450000/450100/450126"},{"code":450127,"name":"横州","fullname":"横州市","level":"district","filename":"450000/450100/450127"}]},{"code":450200,"name":"柳州","fullname":"柳州市","level":"city","filename":"450000/450200","children":[{"code":450202,"name":"城中","fullname":"城中区","level":"district","filename":"450000/450200/450202"},{"code":450203,"name":"鱼峰","fullname":"鱼峰区","level":"district","filename":"450000/450200/450203"},{"code":450204,"name":"柳南","fullname":"柳南区","level":"district","filename":"450000/450200/450204"},{"code":450205,"name":"柳北","fullname":"柳北区","level":"district","filename":"450000/450200/450205"},{"code":450206,"name":"柳江","fullname":"柳江区","level":"district","filename":"450000/450200/450206"},{"code":450222,"name":"柳城","fullname":"柳城县","level":"district","filename":"450000/450200/450222"},{"code":450223,"name":"鹿寨","fullname":"鹿寨县","level":"district","filename":"450000/450200/450223"},{"code":450224,"name":"融安","fullname":"融安县","level":"district","filename":"450000/450200/450224"},{"code":450225,"name":"融水","fullname":"融水苗族自治县","level":"district","filename":"450000/450200/450225"},{"code":450226,"name":"三江","fullname":"三江侗族自治县","level":"district","filename":"450000/450200/450226"}]},{"code":450300,"name":"桂林","fullname":"桂林市","level":"city","filename":"450000/450300","children":[{"code":450302,"name":"秀峰","fullname":"秀峰区","level":"district","filename":"450000/450300/450302"},{"code":450303,"name":"叠彩","fullname":"叠彩区","level":"district","filename":"450000/450300/450303"},{"code":450304,"name":"象山","fullname":"象山区","level":"district","filename":"450000/450300/450304"},{"code":450305,"name":"七星","fullname":"七星区","level":"district","filename":"450000/450300/450305"},{"code":450311,"name":"雁山","fullname":"雁山区","level":"district","filename":"450000/450300/450311"},{"code":450312,"name":"临桂","fullname":"临桂区","level":"district","filename":"450000/450300/450312"},{"code":450321,"name":"阳朔","fullname":"阳朔县","level":"district","filename":"450000/450300/450321"},{"code":450323,"name":"灵川","fullname":"灵川县","level":"district","filename":"450000/450300/450323"},{"code":450324,"name":"全州","fullname":"全州县","level":"district","filename":"450000/450300/450324"},{"code":450325,"name":"兴安","fullname":"兴安县","level":"district","filename":"450000/450300/450325"},{"code":450326,"name":"永福","fullname":"永福县","level":"district","filename":"450000/450300/450326"},{"code":450327,"name":"灌阳","fullname":"灌阳县","level":"district","filename":"450000/450300/450327"},{"code":450328,"name":"龙胜","fullname":"龙胜各族自治县","level":"district","filename":"450000/450300/450328"},{"code":450329,"name":"资源","fullname":"资源县","level":"district","filename":"450000/450300/450329"},{"code":450330,"name":"平乐","fullname":"平乐县","level":"district","filename":"450000/450300/450330"},{"code":450332,"name":"恭城","fullname":"恭城瑶族自治县","level":"district","filename":"450000/450300/450332"},{"code":450381,"name":"荔浦","fullname":"荔浦市","level":"district","filename":"450000/450300/450381"}]},{"code":450400,"name":"梧州","fullname":"梧州市","level":"city","filename":"450000/450400","children":[{"code":450403,"name":"万秀","fullname":"万秀区","level":"district","filename":"450000/450400/450403"},{"code":450405,"name":"长洲","fullname":"长洲区","level":"district","filename":"450000/450400/450405"},{"code":450406,"name":"龙圩","fullname":"龙圩区","level":"district","filename":"450000/450400/450406"},{"code":450421,"name":"苍梧","fullname":"苍梧县","level":"district","filename":"450000/450400/450421"},{"code":450422,"name":"藤县","fullname":"藤县","level":"district","filename":"450000/450400/450422"},{"code":450423,"name":"蒙山","fullname":"蒙山县","level":"district","filename":"450000/450400/450423"},{"code":450481,"name":"岑溪","fullname":"岑溪市","level":"district","filename":"450000/450400/450481"}]},{"code":450500,"name":"北海","fullname":"北海市","level":"city","filename":"450000/450500","children":[{"code":450502,"name":"海城","fullname":"海城区","level":"district","filename":"450000/450500/450502"},{"code":450503,"name":"银海","fullname":"银海区","level":"district","filename":"450000/450500/450503"},{"code":450512,"name":"铁山港","fullname":"铁山港区","level":"district","filename":"450000/450500/450512"},{"code":450521,"name":"合浦","fullname":"合浦县","level":"district","filename":"450000/450500/450521"}]},{"code":450600,"name":"防城港","fullname":"防城港市","level":"city","filename":"450000/450600","children":[{"code":450602,"name":"港口","fullname":"港口区","level":"district","filename":"450000/450600/450602"},{"code":450603,"name":"防城","fullname":"防城区","level":"district","filename":"450000/450600/450603"},{"code":450621,"name":"上思","fullname":"上思县","level":"district","filename":"450000/450600/450621"},{"code":450681,"name":"东兴","fullname":"东兴市","level":"district","filename":"450000/450600/450681"}]},{"code":450700,"name":"钦州","fullname":"钦州市","level":"city","filename":"450000/450700","children":[{"code":450702,"name":"钦南","fullname":"钦南区","level":"district","filename":"450000/450700/450702"},{"code":450703,"name":"钦北","fullname":"钦北区","level":"district","filename":"450000/450700/450703"},{"code":450721,"name":"灵山","fullname":"灵山县","level":"district","filename":"450000/450700/450721"},{"code":450722,"name":"浦北","fullname":"浦北县","level":"district","filename":"450000/450700/450722"}]},{"code":450800,"name":"贵港","fullname":"贵港市","level":"city","filename":"450000/450800","children":[{"code":450802,"name":"港北","fullname":"港北区","level":"district","filename":"450000/450800/450802"},{"code":450803,"name":"港南","fullname":"港南区","level":"district","filename":"450000/450800/450803"},{"code":450804,"name":"覃塘","fullname":"覃塘区","level":"district","filename":"450000/450800/450804"},{"code":450821,"name":"平南","fullname":"平南县","level":"district","filename":"450000/450800/450821"},{"code":450881,"name":"桂平","fullname":"桂平市","level":"district","filename":"450000/450800/450881"}]},{"code":450900,"name":"玉林","fullname":"玉林市","level":"city","filename":"450000/450900","children":[{"code":450902,"name":"玉州","fullname":"玉州区","level":"district","filename":"450000/450900/450902"},{"code":450903,"name":"福绵","fullname":"福绵区","level":"district","filename":"450000/450900/450903"},{"code":450921,"name":"容县","fullname":"容县","level":"district","filename":"450000/450900/450921"},{"code":450922,"name":"陆川","fullname":"陆川县","level":"district","filename":"450000/450900/450922"},{"code":450923,"name":"博白","fullname":"博白县","level":"district","filename":"450000/450900/450923"},{"code":450924,"name":"兴业","fullname":"兴业县","level":"district","filename":"450000/450900/450924"},{"code":450981,"name":"北流","fullname":"北流市","level":"district","filename":"450000/450900/450981"}]},{"code":451000,"name":"百色","fullname":"百色市","level":"city","filename":"450000/451000","children":[{"code":451002,"name":"右江","fullname":"右江区","level":"district","filename":"450000/451000/451002"},{"code":451003,"name":"田阳","fullname":"田阳区","level":"district","filename":"450000/451000/451003"},{"code":451022,"name":"田东","fullname":"田东县","level":"district","filename":"450000/451000/451022"},{"code":451024,"name":"德保","fullname":"德保县","level":"district","filename":"450000/451000/451024"},{"code":451026,"name":"那坡","fullname":"那坡县","level":"district","filename":"450000/451000/451026"},{"code":451027,"name":"凌云","fullname":"凌云县","level":"district","filename":"450000/451000/451027"},{"code":451028,"name":"乐业","fullname":"乐业县","level":"district","filename":"450000/451000/451028"},{"code":451029,"name":"田林","fullname":"田林县","level":"district","filename":"450000/451000/451029"},{"code":451030,"name":"西林","fullname":"西林县","level":"district","filename":"450000/451000/451030"},{"code":451031,"name":"隆林","fullname":"隆林各族自治县","level":"district","filename":"450000/451000/451031"},{"code":451081,"name":"靖西","fullname":"靖西市","level":"district","filename":"450000/451000/451081"},{"code":451082,"name":"平果","fullname":"平果市","level":"district","filename":"450000/451000/451082"}]},{"code":451100,"name":"贺州","fullname":"贺州市","level":"city","filename":"450000/451100","children":[{"code":451102,"name":"八步","fullname":"八步区","level":"district","filename":"450000/451100/451102"},{"code":451103,"name":"平桂","fullname":"平桂区","level":"district","filename":"450000/451100/451103"},{"code":451121,"name":"昭平","fullname":"昭平县","level":"district","filename":"450000/451100/451121"},{"code":451122,"name":"钟山","fullname":"钟山县","level":"district","filename":"450000/451100/451122"},{"code":451123,"name":"富川","fullname":"富川瑶族自治县","level":"district","filename":"450000/451100/451123"}]},{"code":451200,"name":"河池","fullname":"河池市","level":"city","filename":"450000/451200","children":[{"code":451202,"name":"金城江","fullname":"金城江区","level":"district","filename":"450000/451200/451202"},{"code":451203,"name":"宜州","fullname":"宜州区","level":"district","filename":"450000/451200/451203"},{"code":451221,"name":"南丹","fullname":"南丹县","level":"district","filename":"450000/451200/451221"},{"code":451222,"name":"天峨","fullname":"天峨县","level":"district","filename":"450000/451200/451222"},{"code":451223,"name":"凤山","fullname":"凤山县","level":"district","filename":"450000/451200/451223"},{"code":451224,"name":"东兰","fullname":"东兰县","level":"district","filename":"450000/451200/451224"},{"code":451225,"name":"罗城","fullname":"罗城仫佬族自治县","level":"district","filename":"450000/451200/451225"},{"code":451226,"name":"环江","fullname":"环江毛南族自治县","level":"district","filename":"450000/451200/451226"},{"code":451227,"name":"巴马","fullname":"巴马瑶族自治县","level":"district","filename":"450000/451200/451227"},{"code":451228,"name":"都安","fullname":"都安瑶族自治县","level":"district","filename":"450000/451200/451228"},{"code":451229,"name":"大化","fullname":"大化瑶族自治县","level":"district","filename":"450000/451200/451229"}]},{"code":451300,"name":"来宾","fullname":"来宾市","level":"city","filename":"450000/451300","children":[{"code":451302,"name":"兴宾","fullname":"兴宾区","level":"district","filename":"450000/451300/451302"},{"code":451321,"name":"忻城","fullname":"忻城县","level":"district","filename":"450000/451300/451321"},{"code":451322,"name":"象州","fullname":"象州县","level":"district","filename":"450000/451300/451322"},{"code":451323,"name":"武宣","fullname":"武宣县","level":"district","filename":"450000/451300/451323"},{"code":451324,"name":"金秀","fullname":"金秀瑶族自治县","level":"district","filename":"450000/451300/451324"},{"code":451381,"name":"合山","fullname":"合山市","level":"district","filename":"450000/451300/451381"}]},{"code":451400,"name":"崇左","fullname":"崇左市","level":"city","filename":"450000/451400","children":[{"code":451402,"name":"江州","fullname":"江州区","level":"district","filename":"450000/451400/451402"},{"code":451421,"name":"扶绥","fullname":"扶绥县","level":"district","filename":"450000/451400/451421"},{"code":451422,"name":"宁明","fullname":"宁明县","level":"district","filename":"450000/451400/451422"},{"code":451423,"name":"龙州","fullname":"龙州县","level":"district","filename":"450000/451400/451423"},{"code":451424,"name":"大新","fullname":"大新县","level":"district","filename":"450000/451400/451424"},{"code":451425,"name":"天等","fullname":"天等县","level":"district","filename":"450000/451400/451425"},{"code":451481,"name":"凭祥","fullname":"凭祥市","level":"district","filename":"450000/451400/451481"}]}]},{"code":460000,"name":"海南","fullname":"海南省","level":"province","filename":"460000","children":[{"code":460100,"name":"海口","fullname":"海口市","level":"city","filename":"460000/460100","children":[{"code":460105,"name":"秀英","fullname":"秀英区","level":"district","filename":"460000/460100/460105"},{"code":460106,"name":"龙华","fullname":"龙华区","level":"district","filename":"460000/460100/460106"},{"code":460107,"name":"琼山","fullname":"琼山区","level":"district","filename":"460000/460100/460107"},{"code":460108,"name":"美兰","fullname":"美兰区","level":"district","filename":"460000/460100/460108"}]},{"code":460200,"name":"三亚","fullname":"三亚市","level":"city","filename":"460000/460200","children":[{"code":460202,"name":"海棠","fullname":"海棠区","level":"district","filename":"460000/460200/460202"},{"code":460203,"name":"吉阳","fullname":"吉阳区","level":"district","filename":"460000/460200/460203"},{"code":460204,"name":"天涯","fullname":"天涯区","level":"district","filename":"460000/460200/460204"},{"code":460205,"name":"崖州","fullname":"崖州区","level":"district","filename":"460000/460200/460205"}]},{"code":460300,"name":"三沙","fullname":"三沙市","level":"city","filename":"460000/460300","children":[{"code":460301,"name":"西沙","fullname":"西沙区","level":"district","filename":"460000/460300/460301"},{"code":460302,"name":"南沙","fullname":"南沙区","level":"district","filename":"460000/460300/460302"}]},{"code":460400,"name":"儋州","fullname":"儋州市","level":"city","filename":"460000/460400"},{"code":469001,"name":"五指山","fullname":"五指山市","level":"city","filename":"460000/469001"},{"code":469002,"name":"琼海","fullname":"琼海市","level":"city","filename":"460000/469002"},{"code":469005,"name":"文昌","fullname":"文昌市","level":"city","filename":"460000/469005"},{"code":469006,"name":"万宁","fullname":"万宁市","level":"city","filename":"460000/469006"},{"code":469007,"name":"东方","fullname":"东方市","level":"city","filename":"460000/469007"},{"code":469021,"name":"定安县","fullname":"定安县","level":"city","filename":"460000/469021"},{"code":469022,"name":"屯昌县","fullname":"屯昌县","level":"city","filename":"460000/469022"},{"code":469023,"name":"澄迈县","fullname":"澄迈县","level":"city","filename":"460000/469023"},{"code":469024,"name":"临高县","fullname":"临高县","level":"city","filename":"460000/469024"},{"code":469025,"name":"白沙","fullname":"白沙黎族自治县","level":"city","filename":"460000/469025"},{"code":469026,"name":"昌江","fullname":"昌江黎族自治县","level":"city","filename":"460000/469026"},{"code":469027,"name":"乐东","fullname":"乐东黎族自治县","level":"city","filename":"460000/469027"},{"code":469028,"name":"陵水","fullname":"陵水黎族自治县","level":"city","filename":"460000/469028"},{"code":469029,"name":"保亭","fullname":"保亭黎族苗族自治县","level":"city","filename":"460000/469029"},{"code":469030,"name":"琼中","fullname":"琼中黎族苗族自治县","level":"city","filename":"460000/469030"}]},{"code":500000,"name":"重庆","fullname":"重庆市","level":"province","filename":"500000","children":[{"code":500101,"name":"万州","fullname":"万州区","level":"district","filename":"500000/500101"},{"code":500102,"name":"涪陵","fullname":"涪陵区","level":"district","filename":"500000/500102"},{"code":500103,"name":"渝中","fullname":"渝中区","level":"district","filename":"500000/500103"},{"code":500104,"name":"大渡口","fullname":"大渡口区","level":"district","filename":"500000/500104"},{"code":500105,"name":"江北","fullname":"江北区","level":"district","filename":"500000/500105"},{"code":500106,"name":"沙坪坝","fullname":"沙坪坝区","level":"district","filename":"500000/500106"},{"code":500107,"name":"九龙坡","fullname":"九龙坡区","level":"district","filename":"500000/500107"},{"code":500108,"name":"南岸","fullname":"南岸区","level":"district","filename":"500000/500108"},{"code":500109,"name":"北碚","fullname":"北碚区","level":"district","filename":"500000/500109"},{"code":500110,"name":"綦江","fullname":"綦江区","level":"district","filename":"500000/500110"},{"code":500111,"name":"大足","fullname":"大足区","level":"district","filename":"500000/500111"},{"code":500112,"name":"渝北","fullname":"渝北区","level":"district","filename":"500000/500112"},{"code":500113,"name":"巴南","fullname":"巴南区","level":"district","filename":"500000/500113"},{"code":500114,"name":"黔江","fullname":"黔江区","level":"district","filename":"500000/500114"},{"code":500115,"name":"长寿","fullname":"长寿区","level":"district","filename":"500000/500115"},{"code":500116,"name":"江津","fullname":"江津区","level":"district","filename":"500000/500116"},{"code":500117,"name":"合川","fullname":"合川区","level":"district","filename":"500000/500117"},{"code":500118,"name":"永川","fullname":"永川区","level":"district","filename":"500000/500118"},{"code":500119,"name":"南川","fullname":"南川区","level":"district","filename":"500000/500119"},{"code":500120,"name":"璧山","fullname":"璧山区","level":"district","filename":"500000/500120"},{"code":500151,"name":"铜梁","fullname":"铜梁区","level":"district","filename":"500000/500151"},{"code":500152,"name":"潼南","fullname":"潼南区","level":"district","filename":"500000/500152"},{"code":500153,"name":"荣昌","fullname":"荣昌区","level":"district","filename":"500000/500153"},{"code":500154,"name":"开州","fullname":"开州区","level":"district","filename":"500000/500154"},{"code":500155,"name":"梁平","fullname":"梁平区","level":"district","filename":"500000/500155"},{"code":500156,"name":"武隆","fullname":"武隆区","level":"district","filename":"500000/500156"},{"code":500229,"name":"城口","fullname":"城口县","level":"district","filename":"500000/500229"},{"code":500230,"name":"丰都","fullname":"丰都县","level":"district","filename":"500000/500230"},{"code":500231,"name":"垫江","fullname":"垫江县","level":"district","filename":"500000/500231"},{"code":500233,"name":"忠县","fullname":"忠县","level":"district","filename":"500000/500233"},{"code":500235,"name":"云阳","fullname":"云阳县","level":"district","filename":"500000/500235"},{"code":500236,"name":"奉节","fullname":"奉节县","level":"district","filename":"500000/500236"},{"code":500237,"name":"巫山","fullname":"巫山县","level":"district","filename":"500000/500237"},{"code":500238,"name":"巫溪","fullname":"巫溪县","level":"district","filename":"500000/500238"},{"code":500240,"name":"石柱","fullname":"石柱土家族自治县","level":"district","filename":"500000/500240"},{"code":500241,"name":"秀山","fullname":"秀山土家族苗族自治县","level":"district","filename":"500000/500241"},{"code":500242,"name":"酉阳","fullname":"酉阳土家族苗族自治县","level":"district","filename":"500000/500242"},{"code":500243,"name":"彭水","fullname":"彭水苗族土家族自治县","level":"district","filename":"500000/500243"}]},{"code":510000,"name":"四川","fullname":"四川省","level":"province","filename":"510000","children":[{"code":510100,"name":"成都","fullname":"成都市","level":"city","filename":"510000/510100","children":[{"code":510104,"name":"锦江","fullname":"锦江区","level":"district","filename":"510000/510100/510104"},{"code":510105,"name":"青羊","fullname":"青羊区","level":"district","filename":"510000/510100/510105"},{"code":510106,"name":"金牛","fullname":"金牛区","level":"district","filename":"510000/510100/510106"},{"code":510107,"name":"武侯","fullname":"武侯区","level":"district","filename":"510000/510100/510107"},{"code":510108,"name":"成华","fullname":"成华区","level":"district","filename":"510000/510100/510108"},{"code":510112,"name":"龙泉驿","fullname":"龙泉驿区","level":"district","filename":"510000/510100/510112"},{"code":510113,"name":"青白江","fullname":"青白江区","level":"district","filename":"510000/510100/510113"},{"code":510114,"name":"新都","fullname":"新都区","level":"district","filename":"510000/510100/510114"},{"code":510115,"name":"温江","fullname":"温江区","level":"district","filename":"510000/510100/510115"},{"code":510116,"name":"双流","fullname":"双流区","level":"district","filename":"510000/510100/510116"},{"code":510117,"name":"郫都","fullname":"郫都区","level":"district","filename":"510000/510100/510117"},{"code":510118,"name":"新津","fullname":"新津区","level":"district","filename":"510000/510100/510118"},{"code":510121,"name":"金堂","fullname":"金堂县","level":"district","filename":"510000/510100/510121"},{"code":510129,"name":"大邑","fullname":"大邑县","level":"district","filename":"510000/510100/510129"},{"code":510131,"name":"蒲江","fullname":"蒲江县","level":"district","filename":"510000/510100/510131"},{"code":510181,"name":"都江堰","fullname":"都江堰市","level":"district","filename":"510000/510100/510181"},{"code":510182,"name":"彭州","fullname":"彭州市","level":"district","filename":"510000/510100/510182"},{"code":510183,"name":"邛崃","fullname":"邛崃市","level":"district","filename":"510000/510100/510183"},{"code":510184,"name":"崇州","fullname":"崇州市","level":"district","filename":"510000/510100/510184"},{"code":510185,"name":"简阳","fullname":"简阳市","level":"district","filename":"510000/510100/510185"}]},{"code":510300,"name":"自贡","fullname":"自贡市","level":"city","filename":"510000/510300","children":[{"code":510302,"name":"自流井","fullname":"自流井区","level":"district","filename":"510000/510300/510302"},{"code":510303,"name":"贡井","fullname":"贡井区","level":"district","filename":"510000/510300/510303"},{"code":510304,"name":"大安","fullname":"大安区","level":"district","filename":"510000/510300/510304"},{"code":510311,"name":"沿滩","fullname":"沿滩区","level":"district","filename":"510000/510300/510311"},{"code":510321,"name":"荣县","fullname":"荣县","level":"district","filename":"510000/510300/510321"},{"code":510322,"name":"富顺","fullname":"富顺县","level":"district","filename":"510000/510300/510322"}]},{"code":510400,"name":"攀枝花","fullname":"攀枝花市","level":"city","filename":"510000/510400","children":[{"code":510402,"name":"东区","fullname":"东区","level":"district","filename":"510000/510400/510402"},{"code":510403,"name":"西区","fullname":"西区","level":"district","filename":"510000/510400/510403"},{"code":510411,"name":"仁和","fullname":"仁和区","level":"district","filename":"510000/510400/510411"},{"code":510421,"name":"米易","fullname":"米易县","level":"district","filename":"510000/510400/510421"},{"code":510422,"name":"盐边","fullname":"盐边县","level":"district","filename":"510000/510400/510422"}]},{"code":510500,"name":"泸州","fullname":"泸州市","level":"city","filename":"510000/510500","children":[{"code":510502,"name":"江阳","fullname":"江阳区","level":"district","filename":"510000/510500/510502"},{"code":510503,"name":"纳溪","fullname":"纳溪区","level":"district","filename":"510000/510500/510503"},{"code":510504,"name":"龙马潭","fullname":"龙马潭区","level":"district","filename":"510000/510500/510504"},{"code":510521,"name":"泸县","fullname":"泸县","level":"district","filename":"510000/510500/510521"},{"code":510522,"name":"合江","fullname":"合江县","level":"district","filename":"510000/510500/510522"},{"code":510524,"name":"叙永","fullname":"叙永县","level":"district","filename":"510000/510500/510524"},{"code":510525,"name":"古蔺","fullname":"古蔺县","level":"district","filename":"510000/510500/510525"}]},{"code":510600,"name":"德阳","fullname":"德阳市","level":"city","filename":"510000/510600","children":[{"code":510603,"name":"旌阳","fullname":"旌阳区","level":"district","filename":"510000/510600/510603"},{"code":510604,"name":"罗江","fullname":"罗江区","level":"district","filename":"510000/510600/510604"},{"code":510623,"name":"中江","fullname":"中江县","level":"district","filename":"510000/510600/510623"},{"code":510681,"name":"广汉","fullname":"广汉市","level":"district","filename":"510000/510600/510681"},{"code":510682,"name":"什邡","fullname":"什邡市","level":"district","filename":"510000/510600/510682"},{"code":510683,"name":"绵竹","fullname":"绵竹市","level":"district","filename":"510000/510600/510683"}]},{"code":510700,"name":"绵阳","fullname":"绵阳市","level":"city","filename":"510000/510700","children":[{"code":510703,"name":"涪城","fullname":"涪城区","level":"district","filename":"510000/510700/510703"},{"code":510704,"name":"游仙","fullname":"游仙区","level":"district","filename":"510000/510700/510704"},{"code":510705,"name":"安州","fullname":"安州区","level":"district","filename":"510000/510700/510705"},{"code":510722,"name":"三台","fullname":"三台县","level":"district","filename":"510000/510700/510722"},{"code":510723,"name":"盐亭","fullname":"盐亭县","level":"district","filename":"510000/510700/510723"},{"code":510725,"name":"梓潼","fullname":"梓潼县","level":"district","filename":"510000/510700/510725"},{"code":510726,"name":"北川","fullname":"北川羌族自治县","level":"district","filename":"510000/510700/510726"},{"code":510727,"name":"平武","fullname":"平武县","level":"district","filename":"510000/510700/510727"},{"code":510781,"name":"江油","fullname":"江油市","level":"district","filename":"510000/510700/510781"}]},{"code":510800,"name":"广元","fullname":"广元市","level":"city","filename":"510000/510800","children":[{"code":510802,"name":"利州","fullname":"利州区","level":"district","filename":"510000/510800/510802"},{"code":510811,"name":"昭化","fullname":"昭化区","level":"district","filename":"510000/510800/510811"},{"code":510812,"name":"朝天","fullname":"朝天区","level":"district","filename":"510000/510800/510812"},{"code":510821,"name":"旺苍","fullname":"旺苍县","level":"district","filename":"510000/510800/510821"},{"code":510822,"name":"青川","fullname":"青川县","level":"district","filename":"510000/510800/510822"},{"code":510823,"name":"剑阁","fullname":"剑阁县","level":"district","filename":"510000/510800/510823"},{"code":510824,"name":"苍溪","fullname":"苍溪县","level":"district","filename":"510000/510800/510824"}]},{"code":510900,"name":"遂宁","fullname":"遂宁市","level":"city","filename":"510000/510900","children":[{"code":510903,"name":"船山","fullname":"船山区","level":"district","filename":"510000/510900/510903"},{"code":510904,"name":"安居","fullname":"安居区","level":"district","filename":"510000/510900/510904"},{"code":510921,"name":"蓬溪","fullname":"蓬溪县","level":"district","filename":"510000/510900/510921"},{"code":510923,"name":"大英","fullname":"大英县","level":"district","filename":"510000/510900/510923"},{"code":510981,"name":"射洪","fullname":"射洪市","level":"district","filename":"510000/510900/510981"}]},{"code":511000,"name":"内江","fullname":"内江市","level":"city","filename":"510000/511000","children":[{"code":511002,"name":"市中区","fullname":"市中区","level":"district","filename":"510000/511000/511002"},{"code":511011,"name":"东兴","fullname":"东兴区","level":"district","filename":"510000/511000/511011"},{"code":511024,"name":"威远","fullname":"威远县","level":"district","filename":"510000/511000/511024"},{"code":511025,"name":"资中","fullname":"资中县","level":"district","filename":"510000/511000/511025"},{"code":511083,"name":"隆昌","fullname":"隆昌市","level":"district","filename":"510000/511000/511083"}]},{"code":511100,"name":"乐山","fullname":"乐山市","level":"city","filename":"510000/511100","children":[{"code":511102,"name":"市中区","fullname":"市中区","level":"district","filename":"510000/511100/511102"},{"code":511111,"name":"沙湾","fullname":"沙湾区","level":"district","filename":"510000/511100/511111"},{"code":511112,"name":"五通桥","fullname":"五通桥区","level":"district","filename":"510000/511100/511112"},{"code":511113,"name":"金口河","fullname":"金口河区","level":"district","filename":"510000/511100/511113"},{"code":511123,"name":"犍为","fullname":"犍为县","level":"district","filename":"510000/511100/511123"},{"code":511124,"name":"井研","fullname":"井研县","level":"district","filename":"510000/511100/511124"},{"code":511126,"name":"夹江","fullname":"夹江县","level":"district","filename":"510000/511100/511126"},{"code":511129,"name":"沐川","fullname":"沐川县","level":"district","filename":"510000/511100/511129"},{"code":511132,"name":"峨边","fullname":"峨边彝族自治县","level":"district","filename":"510000/511100/511132"},{"code":511133,"name":"马边","fullname":"马边彝族自治县","level":"district","filename":"510000/511100/511133"},{"code":511181,"name":"峨眉山","fullname":"峨眉山市","level":"district","filename":"510000/511100/511181"}]},{"code":511300,"name":"南充","fullname":"南充市","level":"city","filename":"510000/511300","children":[{"code":511302,"name":"顺庆","fullname":"顺庆区","level":"district","filename":"510000/511300/511302"},{"code":511303,"name":"高坪","fullname":"高坪区","level":"district","filename":"510000/511300/511303"},{"code":511304,"name":"嘉陵","fullname":"嘉陵区","level":"district","filename":"510000/511300/511304"},{"code":511321,"name":"南部","fullname":"南部县","level":"district","filename":"510000/511300/511321"},{"code":511322,"name":"营山","fullname":"营山县","level":"district","filename":"510000/511300/511322"},{"code":511323,"name":"蓬安","fullname":"蓬安县","level":"district","filename":"510000/511300/511323"},{"code":511324,"name":"仪陇","fullname":"仪陇县","level":"district","filename":"510000/511300/511324"},{"code":511325,"name":"西充","fullname":"西充县","level":"district","filename":"510000/511300/511325"},{"code":511381,"name":"阆中","fullname":"阆中市","level":"district","filename":"510000/511300/511381"}]},{"code":511400,"name":"眉山","fullname":"眉山市","level":"city","filename":"510000/511400","children":[{"code":511402,"name":"东坡","fullname":"东坡区","level":"district","filename":"510000/511400/511402"},{"code":511403,"name":"彭山","fullname":"彭山区","level":"district","filename":"510000/511400/511403"},{"code":511421,"name":"仁寿","fullname":"仁寿县","level":"district","filename":"510000/511400/511421"},{"code":511423,"name":"洪雅","fullname":"洪雅县","level":"district","filename":"510000/511400/511423"},{"code":511424,"name":"丹棱","fullname":"丹棱县","level":"district","filename":"510000/511400/511424"},{"code":511425,"name":"青神","fullname":"青神县","level":"district","filename":"510000/511400/511425"}]},{"code":511500,"name":"宜宾","fullname":"宜宾市","level":"city","filename":"510000/511500","children":[{"code":511502,"name":"翠屏","fullname":"翠屏区","level":"district","filename":"510000/511500/511502"},{"code":511503,"name":"南溪","fullname":"南溪区","level":"district","filename":"510000/511500/511503"},{"code":511504,"name":"叙州","fullname":"叙州区","level":"district","filename":"510000/511500/511504"},{"code":511523,"name":"江安","fullname":"江安县","level":"district","filename":"510000/511500/511523"},{"code":511524,"name":"长宁","fullname":"长宁县","level":"district","filename":"510000/511500/511524"},{"code":511525,"name":"高县","fullname":"高县","level":"district","filename":"510000/511500/511525"},{"code":511526,"name":"珙县","fullname":"珙县","level":"district","filename":"510000/511500/511526"},{"code":511527,"name":"筠连","fullname":"筠连县","level":"district","filename":"510000/511500/511527"},{"code":511528,"name":"兴文","fullname":"兴文县","level":"district","filename":"510000/511500/511528"},{"code":511529,"name":"屏山","fullname":"屏山县","level":"district","filename":"510000/511500/511529"}]},{"code":511600,"name":"广安","fullname":"广安市","level":"city","filename":"510000/511600","children":[{"code":511602,"name":"广安","fullname":"广安区","level":"district","filename":"510000/511600/511602"},{"code":511603,"name":"前锋","fullname":"前锋区","level":"district","filename":"510000/511600/511603"},{"code":511621,"name":"岳池","fullname":"岳池县","level":"district","filename":"510000/511600/511621"},{"code":511622,"name":"武胜","fullname":"武胜县","level":"district","filename":"510000/511600/511622"},{"code":511623,"name":"邻水","fullname":"邻水县","level":"district","filename":"510000/511600/511623"},{"code":511681,"name":"华蓥","fullname":"华蓥市","level":"district","filename":"510000/511600/511681"}]},{"code":511700,"name":"达州","fullname":"达州市","level":"city","filename":"510000/511700","children":[{"code":511702,"name":"通川","fullname":"通川区","level":"district","filename":"510000/511700/511702"},{"code":511703,"name":"达川","fullname":"达川区","level":"district","filename":"510000/511700/511703"},{"code":511722,"name":"宣汉","fullname":"宣汉县","level":"district","filename":"510000/511700/511722"},{"code":511723,"name":"开江","fullname":"开江县","level":"district","filename":"510000/511700/511723"},{"code":511724,"name":"大竹","fullname":"大竹县","level":"district","filename":"510000/511700/511724"},{"code":511725,"name":"渠县","fullname":"渠县","level":"district","filename":"510000/511700/511725"},{"code":511781,"name":"万源","fullname":"万源市","level":"district","filename":"510000/511700/511781"}]},{"code":511800,"name":"雅安","fullname":"雅安市","level":"city","filename":"510000/511800","children":[{"code":511802,"name":"雨城","fullname":"雨城区","level":"district","filename":"510000/511800/511802"},{"code":511803,"name":"名山","fullname":"名山区","level":"district","filename":"510000/511800/511803"},{"code":511822,"name":"荥经","fullname":"荥经县","level":"district","filename":"510000/511800/511822"},{"code":511823,"name":"汉源","fullname":"汉源县","level":"district","filename":"510000/511800/511823"},{"code":511824,"name":"石棉","fullname":"石棉县","level":"district","filename":"510000/511800/511824"},{"code":511825,"name":"天全","fullname":"天全县","level":"district","filename":"510000/511800/511825"},{"code":511826,"name":"芦山","fullname":"芦山县","level":"district","filename":"510000/511800/511826"},{"code":511827,"name":"宝兴","fullname":"宝兴县","level":"district","filename":"510000/511800/511827"}]},{"code":511900,"name":"巴中","fullname":"巴中市","level":"city","filename":"510000/511900","children":[{"code":511902,"name":"巴州","fullname":"巴州区","level":"district","filename":"510000/511900/511902"},{"code":511903,"name":"恩阳","fullname":"恩阳区","level":"district","filename":"510000/511900/511903"},{"code":511921,"name":"通江","fullname":"通江县","level":"district","filename":"510000/511900/511921"},{"code":511922,"name":"南江","fullname":"南江县","level":"district","filename":"510000/511900/511922"},{"code":511923,"name":"平昌","fullname":"平昌县","level":"district","filename":"510000/511900/511923"}]},{"code":512000,"name":"资阳","fullname":"资阳市","level":"city","filename":"510000/512000","children":[{"code":512002,"name":"雁江","fullname":"雁江区","level":"district","filename":"510000/512000/512002"},{"code":512021,"name":"安岳","fullname":"安岳县","level":"district","filename":"510000/512000/512021"},{"code":512022,"name":"乐至","fullname":"乐至县","level":"district","filename":"510000/512000/512022"}]},{"code":513200,"name":"阿坝","fullname":"阿坝藏族羌族自治州","level":"city","filename":"510000/513200","children":[{"code":513201,"name":"马尔康","fullname":"马尔康市","level":"district","filename":"510000/513200/513201"},{"code":513221,"name":"汶川","fullname":"汶川县","level":"district","filename":"510000/513200/513221"},{"code":513222,"name":"理县","fullname":"理县","level":"district","filename":"510000/513200/513222"},{"code":513223,"name":"茂县","fullname":"茂县","level":"district","filename":"510000/513200/513223"},{"code":513224,"name":"松潘","fullname":"松潘县","level":"district","filename":"510000/513200/513224"},{"code":513225,"name":"九寨沟","fullname":"九寨沟县","level":"district","filename":"510000/513200/513225"},{"code":513226,"name":"金川","fullname":"金川县","level":"district","filename":"510000/513200/513226"},{"code":513227,"name":"小金","fullname":"小金县","level":"district","filename":"510000/513200/513227"},{"code":513228,"name":"黑水","fullname":"黑水县","level":"district","filename":"510000/513200/513228"},{"code":513230,"name":"壤塘","fullname":"壤塘县","level":"district","filename":"510000/513200/513230"},{"code":513231,"name":"阿坝","fullname":"阿坝县","level":"district","filename":"510000/513200/513231"},{"code":513232,"name":"若尔盖","fullname":"若尔盖县","level":"district","filename":"510000/513200/513232"},{"code":513233,"name":"红原","fullname":"红原县","level":"district","filename":"510000/513200/513233"}]},{"code":513300,"name":"甘孜","fullname":"甘孜藏族自治州","level":"city","filename":"510000/513300","children":[{"code":513301,"name":"康定","fullname":"康定市","level":"district","filename":"510000/513300/513301"},{"code":513322,"name":"泸定","fullname":"泸定县","level":"district","filename":"510000/513300/513322"},{"code":513323,"name":"丹巴","fullname":"丹巴县","level":"district","filename":"510000/513300/513323"},{"code":513324,"name":"九龙","fullname":"九龙县","level":"district","filename":"510000/513300/513324"},{"code":513325,"name":"雅江","fullname":"雅江县","level":"district","filename":"510000/513300/513325"},{"code":513326,"name":"道孚","fullname":"道孚县","level":"district","filename":"510000/513300/513326"},{"code":513327,"name":"炉霍","fullname":"炉霍县","level":"district","filename":"510000/513300/513327"},{"code":513328,"name":"甘孜","fullname":"甘孜县","level":"district","filename":"510000/513300/513328"},{"code":513329,"name":"新龙","fullname":"新龙县","level":"district","filename":"510000/513300/513329"},{"code":513330,"name":"德格","fullname":"德格县","level":"district","filename":"510000/513300/513330"},{"code":513331,"name":"白玉","fullname":"白玉县","level":"district","filename":"510000/513300/513331"},{"code":513332,"name":"石渠","fullname":"石渠县","level":"district","filename":"510000/513300/513332"},{"code":513333,"name":"色达","fullname":"色达县","level":"district","filename":"510000/513300/513333"},{"code":513334,"name":"理塘","fullname":"理塘县","level":"district","filename":"510000/513300/513334"},{"code":513335,"name":"巴塘","fullname":"巴塘县","level":"district","filename":"510000/513300/513335"},{"code":513336,"name":"乡城","fullname":"乡城县","level":"district","filename":"510000/513300/513336"},{"code":513337,"name":"稻城","fullname":"稻城县","level":"district","filename":"510000/513300/513337"},{"code":513338,"name":"得荣","fullname":"得荣县","level":"district","filename":"510000/513300/513338"}]},{"code":513400,"name":"凉山","fullname":"凉山彝族自治州","level":"city","filename":"510000/513400","children":[{"code":513401,"name":"西昌","fullname":"西昌市","level":"district","filename":"510000/513400/513401"},{"code":513422,"name":"木里","fullname":"木里藏族自治县","level":"district","filename":"510000/513400/513422"},{"code":513423,"name":"盐源","fullname":"盐源县","level":"district","filename":"510000/513400/513423"},{"code":513424,"name":"德昌","fullname":"德昌县","level":"district","filename":"510000/513400/513424"},{"code":513425,"name":"会理","fullname":"会理市","level":"district","filename":"510000/513400/513425"},{"code":513426,"name":"会东","fullname":"会东县","level":"district","filename":"510000/513400/513426"},{"code":513427,"name":"宁南","fullname":"宁南县","level":"district","filename":"510000/513400/513427"},{"code":513428,"name":"普格","fullname":"普格县","level":"district","filename":"510000/513400/513428"},{"code":513429,"name":"布拖","fullname":"布拖县","level":"district","filename":"510000/513400/513429"},{"code":513430,"name":"金阳","fullname":"金阳县","level":"district","filename":"510000/513400/513430"},{"code":513431,"name":"昭觉","fullname":"昭觉县","level":"district","filename":"510000/513400/513431"},{"code":513432,"name":"喜德","fullname":"喜德县","level":"district","filename":"510000/513400/513432"},{"code":513433,"name":"冕宁","fullname":"冕宁县","level":"district","filename":"510000/513400/513433"},{"code":513434,"name":"越西","fullname":"越西县","level":"district","filename":"510000/513400/513434"},{"code":513435,"name":"甘洛","fullname":"甘洛县","level":"district","filename":"510000/513400/513435"},{"code":513436,"name":"美姑","fullname":"美姑县","level":"district","filename":"510000/513400/513436"},{"code":513437,"name":"雷波","fullname":"雷波县","level":"district","filename":"510000/513400/513437"}]}]},{"code":520000,"name":"贵州","fullname":"贵州省","level":"province","filename":"520000","children":[{"code":520100,"name":"贵阳","fullname":"贵阳市","level":"city","filename":"520000/520100","children":[{"code":520102,"name":"南明","fullname":"南明区","level":"district","filename":"520000/520100/520102"},{"code":520103,"name":"云岩","fullname":"云岩区","level":"district","filename":"520000/520100/520103"},{"code":520111,"name":"花溪","fullname":"花溪区","level":"district","filename":"520000/520100/520111"},{"code":520112,"name":"乌当","fullname":"乌当区","level":"district","filename":"520000/520100/520112"},{"code":520113,"name":"白云","fullname":"白云区","level":"district","filename":"520000/520100/520113"},{"code":520115,"name":"观山湖","fullname":"观山湖区","level":"district","filename":"520000/520100/520115"},{"code":520121,"name":"开阳","fullname":"开阳县","level":"district","filename":"520000/520100/520121"},{"code":520122,"name":"息烽","fullname":"息烽县","level":"district","filename":"520000/520100/520122"},{"code":520123,"name":"修文","fullname":"修文县","level":"district","filename":"520000/520100/520123"},{"code":520181,"name":"清镇","fullname":"清镇市","level":"district","filename":"520000/520100/520181"}]},{"code":520200,"name":"六盘水","fullname":"六盘水市","level":"city","filename":"520000/520200","children":[{"code":520201,"name":"钟山","fullname":"钟山区","level":"district","filename":"520000/520200/520201"},{"code":520203,"name":"六枝特","fullname":"六枝特区","level":"district","filename":"520000/520200/520203"},{"code":520221,"name":"水城","fullname":"水城区","level":"district","filename":"520000/520200/520221"},{"code":520281,"name":"盘州","fullname":"盘州市","level":"district","filename":"520000/520200/520281"}]},{"code":520300,"name":"遵义","fullname":"遵义市","level":"city","filename":"520000/520300","children":[{"code":520302,"name":"红花岗","fullname":"红花岗区","level":"district","filename":"520000/520300/520302"},{"code":520303,"name":"汇川","fullname":"汇川区","level":"district","filename":"520000/520300/520303"},{"code":520304,"name":"播州","fullname":"播州区","level":"district","filename":"520000/520300/520304"},{"code":520322,"name":"桐梓","fullname":"桐梓县","level":"district","filename":"520000/520300/520322"},{"code":520323,"name":"绥阳","fullname":"绥阳县","level":"district","filename":"520000/520300/520323"},{"code":520324,"name":"正安","fullname":"正安县","level":"district","filename":"520000/520300/520324"},{"code":520325,"name":"道真","fullname":"道真仡佬族苗族自治县","level":"district","filename":"520000/520300/520325"},{"code":520326,"name":"务川","fullname":"务川仡佬族苗族自治县","level":"district","filename":"520000/520300/520326"},{"code":520327,"name":"凤冈","fullname":"凤冈县","level":"district","filename":"520000/520300/520327"},{"code":520328,"name":"湄潭","fullname":"湄潭县","level":"district","filename":"520000/520300/520328"},{"code":520329,"name":"余庆","fullname":"余庆县","level":"district","filename":"520000/520300/520329"},{"code":520330,"name":"习水","fullname":"习水县","level":"district","filename":"520000/520300/520330"},{"code":520381,"name":"赤水","fullname":"赤水市","level":"district","filename":"520000/520300/520381"},{"code":520382,"name":"仁怀","fullname":"仁怀市","level":"district","filename":"520000/520300/520382"}]},{"code":520400,"name":"安顺","fullname":"安顺市","level":"city","filename":"520000/520400","children":[{"code":520402,"name":"西秀","fullname":"西秀区","level":"district","filename":"520000/520400/520402"},{"code":520403,"name":"平坝","fullname":"平坝区","level":"district","filename":"520000/520400/520403"},{"code":520422,"name":"普定","fullname":"普定县","level":"district","filename":"520000/520400/520422"},{"code":520423,"name":"镇宁","fullname":"镇宁布依族苗族自治县","level":"district","filename":"520000/520400/520423"},{"code":520424,"name":"关岭","fullname":"关岭布依族苗族自治县","level":"district","filename":"520000/520400/520424"},{"code":520425,"name":"紫云","fullname":"紫云苗族布依族自治县","level":"district","filename":"520000/520400/520425"}]},{"code":520500,"name":"毕节","fullname":"毕节市","level":"city","filename":"520000/520500","children":[{"code":520502,"name":"七星关","fullname":"七星关区","level":"district","filename":"520000/520500/520502"},{"code":520521,"name":"大方","fullname":"大方县","level":"district","filename":"520000/520500/520521"},{"code":520522,"name":"黔西","fullname":"黔西市","level":"district","filename":"520000/520500/520522"},{"code":520523,"name":"金沙","fullname":"金沙县","level":"district","filename":"520000/520500/520523"},{"code":520524,"name":"织金","fullname":"织金县","level":"district","filename":"520000/520500/520524"},{"code":520525,"name":"纳雍","fullname":"纳雍县","level":"district","filename":"520000/520500/520525"},{"code":520526,"name":"威宁","fullname":"威宁彝族回族苗族自治县","level":"district","filename":"520000/520500/520526"},{"code":520527,"name":"赫章","fullname":"赫章县","level":"district","filename":"520000/520500/520527"}]},{"code":520600,"name":"铜仁","fullname":"铜仁市","level":"city","filename":"520000/520600","children":[{"code":520602,"name":"碧江","fullname":"碧江区","level":"district","filename":"520000/520600/520602"},{"code":520603,"name":"万山","fullname":"万山区","level":"district","filename":"520000/520600/520603"},{"code":520621,"name":"江口","fullname":"江口县","level":"district","filename":"520000/520600/520621"},{"code":520622,"name":"玉屏","fullname":"玉屏侗族自治县","level":"district","filename":"520000/520600/520622"},{"code":520623,"name":"石阡","fullname":"石阡县","level":"district","filename":"520000/520600/520623"},{"code":520624,"name":"思南","fullname":"思南县","level":"district","filename":"520000/520600/520624"},{"code":520625,"name":"印江","fullname":"印江土家族苗族自治县","level":"district","filename":"520000/520600/520625"},{"code":520626,"name":"德江","fullname":"德江县","level":"district","filename":"520000/520600/520626"},{"code":520627,"name":"沿河","fullname":"沿河土家族自治县","level":"district","filename":"520000/520600/520627"},{"code":520628,"name":"松桃","fullname":"松桃苗族自治县","level":"district","filename":"520000/520600/520628"}]},{"code":522300,"name":"黔西南","fullname":"黔西南布依族苗族自治州","level":"city","filename":"520000/522300","children":[{"code":522301,"name":"兴义","fullname":"兴义市","level":"district","filename":"520000/522300/522301"},{"code":522302,"name":"兴仁","fullname":"兴仁市","level":"district","filename":"520000/522300/522302"},{"code":522323,"name":"普安","fullname":"普安县","level":"district","filename":"520000/522300/522323"},{"code":522324,"name":"晴隆","fullname":"晴隆县","level":"district","filename":"520000/522300/522324"},{"code":522325,"name":"贞丰","fullname":"贞丰县","level":"district","filename":"520000/522300/522325"},{"code":522326,"name":"望谟","fullname":"望谟县","level":"district","filename":"520000/522300/522326"},{"code":522327,"name":"册亨","fullname":"册亨县","level":"district","filename":"520000/522300/522327"},{"code":522328,"name":"安龙","fullname":"安龙县","level":"district","filename":"520000/522300/522328"}]},{"code":522600,"name":"黔东南","fullname":"黔东南苗族侗族自治州","level":"city","filename":"520000/522600","children":[{"code":522601,"name":"凯里","fullname":"凯里市","level":"district","filename":"520000/522600/522601"},{"code":522622,"name":"黄平","fullname":"黄平县","level":"district","filename":"520000/522600/522622"},{"code":522623,"name":"施秉","fullname":"施秉县","level":"district","filename":"520000/522600/522623"},{"code":522624,"name":"三穗","fullname":"三穗县","level":"district","filename":"520000/522600/522624"},{"code":522625,"name":"镇远","fullname":"镇远县","level":"district","filename":"520000/522600/522625"},{"code":522626,"name":"岑巩","fullname":"岑巩县","level":"district","filename":"520000/522600/522626"},{"code":522627,"name":"天柱","fullname":"天柱县","level":"district","filename":"520000/522600/522627"},{"code":522628,"name":"锦屏","fullname":"锦屏县","level":"district","filename":"520000/522600/522628"},{"code":522629,"name":"剑河","fullname":"剑河县","level":"district","filename":"520000/522600/522629"},{"code":522630,"name":"台江","fullname":"台江县","level":"district","filename":"520000/522600/522630"},{"code":522631,"name":"黎平","fullname":"黎平县","level":"district","filename":"520000/522600/522631"},{"code":522632,"name":"榕江","fullname":"榕江县","level":"district","filename":"520000/522600/522632"},{"code":522633,"name":"从江","fullname":"从江县","level":"district","filename":"520000/522600/522633"},{"code":522634,"name":"雷山","fullname":"雷山县","level":"district","filename":"520000/522600/522634"},{"code":522635,"name":"麻江","fullname":"麻江县","level":"district","filename":"520000/522600/522635"},{"code":522636,"name":"丹寨","fullname":"丹寨县","level":"district","filename":"520000/522600/522636"}]},{"code":522700,"name":"黔南","fullname":"黔南布依族苗族自治州","level":"city","filename":"520000/522700","children":[{"code":522701,"name":"都匀","fullname":"都匀市","level":"district","filename":"520000/522700/522701"},{"code":522702,"name":"福泉","fullname":"福泉市","level":"district","filename":"520000/522700/522702"},{"code":522722,"name":"荔波","fullname":"荔波县","level":"district","filename":"520000/522700/522722"},{"code":522723,"name":"贵定","fullname":"贵定县","level":"district","filename":"520000/522700/522723"},{"code":522725,"name":"瓮安","fullname":"瓮安县","level":"district","filename":"520000/522700/522725"},{"code":522726,"name":"独山","fullname":"独山县","level":"district","filename":"520000/522700/522726"},{"code":522727,"name":"平塘","fullname":"平塘县","level":"district","filename":"520000/522700/522727"},{"code":522728,"name":"罗甸","fullname":"罗甸县","level":"district","filename":"520000/522700/522728"},{"code":522729,"name":"长顺","fullname":"长顺县","level":"district","filename":"520000/522700/522729"},{"code":522730,"name":"龙里","fullname":"龙里县","level":"district","filename":"520000/522700/522730"},{"code":522731,"name":"惠水","fullname":"惠水县","level":"district","filename":"520000/522700/522731"},{"code":522732,"name":"三都","fullname":"三都水族自治县","level":"district","filename":"520000/522700/522732"}]}]},{"code":530000,"name":"云南","fullname":"云南省","level":"province","filename":"530000","children":[{"code":530100,"name":"昆明","fullname":"昆明市","level":"city","filename":"530000/530100","children":[{"code":530102,"name":"五华","fullname":"五华区","level":"district","filename":"530000/530100/530102"},{"code":530103,"name":"盘龙","fullname":"盘龙区","level":"district","filename":"530000/530100/530103"},{"code":530111,"name":"官渡","fullname":"官渡区","level":"district","filename":"530000/530100/530111"},{"code":530112,"name":"西山","fullname":"西山区","level":"district","filename":"530000/530100/530112"},{"code":530113,"name":"东川","fullname":"东川区","level":"district","filename":"530000/530100/530113"},{"code":530114,"name":"呈贡","fullname":"呈贡区","level":"district","filename":"530000/530100/530114"},{"code":530115,"name":"晋宁","fullname":"晋宁区","level":"district","filename":"530000/530100/530115"},{"code":530124,"name":"富民","fullname":"富民县","level":"district","filename":"530000/530100/530124"},{"code":530125,"name":"宜良","fullname":"宜良县","level":"district","filename":"530000/530100/530125"},{"code":530126,"name":"石林","fullname":"石林彝族自治县","level":"district","filename":"530000/530100/530126"},{"code":530127,"name":"嵩明","fullname":"嵩明县","level":"district","filename":"530000/530100/530127"},{"code":530128,"name":"禄劝","fullname":"禄劝彝族苗族自治县","level":"district","filename":"530000/530100/530128"},{"code":530129,"name":"寻甸","fullname":"寻甸回族彝族自治县","level":"district","filename":"530000/530100/530129"},{"code":530181,"name":"安宁","fullname":"安宁市","level":"district","filename":"530000/530100/530181"}]},{"code":530300,"name":"曲靖","fullname":"曲靖市","level":"city","filename":"530000/530300","children":[{"code":530302,"name":"麒麟","fullname":"麒麟区","level":"district","filename":"530000/530300/530302"},{"code":530303,"name":"沾益","fullname":"沾益区","level":"district","filename":"530000/530300/530303"},{"code":530304,"name":"马龙","fullname":"马龙区","level":"district","filename":"530000/530300/530304"},{"code":530322,"name":"陆良","fullname":"陆良县","level":"district","filename":"530000/530300/530322"},{"code":530323,"name":"师宗","fullname":"师宗县","level":"district","filename":"530000/530300/530323"},{"code":530324,"name":"罗平","fullname":"罗平县","level":"district","filename":"530000/530300/530324"},{"code":530325,"name":"富源","fullname":"富源县","level":"district","filename":"530000/530300/530325"},{"code":530326,"name":"会泽","fullname":"会泽县","level":"district","filename":"530000/530300/530326"},{"code":530381,"name":"宣威","fullname":"宣威市","level":"district","filename":"530000/530300/530381"}]},{"code":530400,"name":"玉溪","fullname":"玉溪市","level":"city","filename":"530000/530400","children":[{"code":530402,"name":"红塔","fullname":"红塔区","level":"district","filename":"530000/530400/530402"},{"code":530403,"name":"江川","fullname":"江川区","level":"district","filename":"530000/530400/530403"},{"code":530423,"name":"通海","fullname":"通海县","level":"district","filename":"530000/530400/530423"},{"code":530424,"name":"华宁","fullname":"华宁县","level":"district","filename":"530000/530400/530424"},{"code":530425,"name":"易门","fullname":"易门县","level":"district","filename":"530000/530400/530425"},{"code":530426,"name":"峨山","fullname":"峨山彝族自治县","level":"district","filename":"530000/530400/530426"},{"code":530427,"name":"新平","fullname":"新平彝族傣族自治县","level":"district","filename":"530000/530400/530427"},{"code":530428,"name":"元江","fullname":"元江哈尼族彝族傣族自治县","level":"district","filename":"530000/530400/530428"},{"code":530481,"name":"澄江","fullname":"澄江市","level":"district","filename":"530000/530400/530481"}]},{"code":530500,"name":"保山","fullname":"保山市","level":"city","filename":"530000/530500","children":[{"code":530502,"name":"隆阳","fullname":"隆阳区","level":"district","filename":"530000/530500/530502"},{"code":530521,"name":"施甸","fullname":"施甸县","level":"district","filename":"530000/530500/530521"},{"code":530523,"name":"龙陵","fullname":"龙陵县","level":"district","filename":"530000/530500/530523"},{"code":530524,"name":"昌宁","fullname":"昌宁县","level":"district","filename":"530000/530500/530524"},{"code":530581,"name":"腾冲","fullname":"腾冲市","level":"district","filename":"530000/530500/530581"}]},{"code":530600,"name":"昭通","fullname":"昭通市","level":"city","filename":"530000/530600","children":[{"code":530602,"name":"昭阳","fullname":"昭阳区","level":"district","filename":"530000/530600/530602"},{"code":530621,"name":"鲁甸","fullname":"鲁甸县","level":"district","filename":"530000/530600/530621"},{"code":530622,"name":"巧家","fullname":"巧家县","level":"district","filename":"530000/530600/530622"},{"code":530623,"name":"盐津","fullname":"盐津县","level":"district","filename":"530000/530600/530623"},{"code":530624,"name":"大关","fullname":"大关县","level":"district","filename":"530000/530600/530624"},{"code":530625,"name":"永善","fullname":"永善县","level":"district","filename":"530000/530600/530625"},{"code":530626,"name":"绥江","fullname":"绥江县","level":"district","filename":"530000/530600/530626"},{"code":530627,"name":"镇雄","fullname":"镇雄县","level":"district","filename":"530000/530600/530627"},{"code":530628,"name":"彝良","fullname":"彝良县","level":"district","filename":"530000/530600/530628"},{"code":530629,"name":"威信","fullname":"威信县","level":"district","filename":"530000/530600/530629"},{"code":530681,"name":"水富","fullname":"水富市","level":"district","filename":"530000/530600/530681"}]},{"code":530700,"name":"丽江","fullname":"丽江市","level":"city","filename":"530000/530700","children":[{"code":530702,"name":"古城","fullname":"古城区","level":"district","filename":"530000/530700/530702"},{"code":530721,"name":"玉龙","fullname":"玉龙纳西族自治县","level":"district","filename":"530000/530700/530721"},{"code":530722,"name":"永胜","fullname":"永胜县","level":"district","filename":"530000/530700/530722"},{"code":530723,"name":"华坪","fullname":"华坪县","level":"district","filename":"530000/530700/530723"},{"code":530724,"name":"宁蒗","fullname":"宁蒗彝族自治县","level":"district","filename":"530000/530700/530724"}]},{"code":530800,"name":"普洱","fullname":"普洱市","level":"city","filename":"530000/530800","children":[{"code":530802,"name":"思茅","fullname":"思茅区","level":"district","filename":"530000/530800/530802"},{"code":530821,"name":"宁洱","fullname":"宁洱哈尼族彝族自治县","level":"district","filename":"530000/530800/530821"},{"code":530822,"name":"墨江","fullname":"墨江哈尼族自治县","level":"district","filename":"530000/530800/530822"},{"code":530823,"name":"景东","fullname":"景东彝族自治县","level":"district","filename":"530000/530800/530823"},{"code":530824,"name":"景谷","fullname":"景谷傣族彝族自治县","level":"district","filename":"530000/530800/530824"},{"code":530825,"name":"镇沅","fullname":"镇沅彝族哈尼族拉祜族自治县","level":"district","filename":"530000/530800/530825"},{"code":530826,"name":"江城","fullname":"江城哈尼族彝族自治县","level":"district","filename":"530000/530800/530826"},{"code":530827,"name":"孟连","fullname":"孟连傣族拉祜族佤族自治县","level":"district","filename":"530000/530800/530827"},{"code":530828,"name":"澜沧","fullname":"澜沧拉祜族自治县","level":"district","filename":"530000/530800/530828"},{"code":530829,"name":"西盟","fullname":"西盟佤族自治县","level":"district","filename":"530000/530800/530829"}]},{"code":530900,"name":"临沧","fullname":"临沧市","level":"city","filename":"530000/530900","children":[{"code":530902,"name":"临翔","fullname":"临翔区","level":"district","filename":"530000/530900/530902"},{"code":530921,"name":"凤庆","fullname":"凤庆县","level":"district","filename":"530000/530900/530921"},{"code":530922,"name":"云县","fullname":"云县","level":"district","filename":"530000/530900/530922"},{"code":530923,"name":"永德","fullname":"永德县","level":"district","filename":"530000/530900/530923"},{"code":530924,"name":"镇康","fullname":"镇康县","level":"district","filename":"530000/530900/530924"},{"code":530925,"name":"双江","fullname":"双江拉祜族佤族布朗族傣族自治县","level":"district","filename":"530000/530900/530925"},{"code":530926,"name":"耿马","fullname":"耿马傣族佤族自治县","level":"district","filename":"530000/530900/530926"},{"code":530927,"name":"沧源","fullname":"沧源佤族自治县","level":"district","filename":"530000/530900/530927"}]},{"code":532300,"name":"楚雄","fullname":"楚雄彝族自治州","level":"city","filename":"530000/532300","children":[{"code":532301,"name":"楚雄","fullname":"楚雄市","level":"district","filename":"530000/532300/532301"},{"code":532322,"name":"双柏","fullname":"双柏县","level":"district","filename":"530000/532300/532322"},{"code":532323,"name":"牟定","fullname":"牟定县","level":"district","filename":"530000/532300/532323"},{"code":532324,"name":"南华","fullname":"南华县","level":"district","filename":"530000/532300/532324"},{"code":532325,"name":"姚安","fullname":"姚安县","level":"district","filename":"530000/532300/532325"},{"code":532326,"name":"大姚","fullname":"大姚县","level":"district","filename":"530000/532300/532326"},{"code":532327,"name":"永仁","fullname":"永仁县","level":"district","filename":"530000/532300/532327"},{"code":532328,"name":"元谋","fullname":"元谋县","level":"district","filename":"530000/532300/532328"},{"code":532329,"name":"武定","fullname":"武定县","level":"district","filename":"530000/532300/532329"},{"code":532331,"name":"禄丰","fullname":"禄丰市","level":"district","filename":"530000/532300/532331"}]},{"code":532500,"name":"红河","fullname":"红河哈尼族彝族自治州","level":"city","filename":"530000/532500","children":[{"code":532501,"name":"个旧","fullname":"个旧市","level":"district","filename":"530000/532500/532501"},{"code":532502,"name":"开远","fullname":"开远市","level":"district","filename":"530000/532500/532502"},{"code":532503,"name":"蒙自","fullname":"蒙自市","level":"district","filename":"530000/532500/532503"},{"code":532504,"name":"弥勒","fullname":"弥勒市","level":"district","filename":"530000/532500/532504"},{"code":532523,"name":"屏边","fullname":"屏边苗族自治县","level":"district","filename":"530000/532500/532523"},{"code":532524,"name":"建水","fullname":"建水县","level":"district","filename":"530000/532500/532524"},{"code":532525,"name":"石屏","fullname":"石屏县","level":"district","filename":"530000/532500/532525"},{"code":532527,"name":"泸西","fullname":"泸西县","level":"district","filename":"530000/532500/532527"},{"code":532528,"name":"元阳","fullname":"元阳县","level":"district","filename":"530000/532500/532528"},{"code":532529,"name":"红河","fullname":"红河县","level":"district","filename":"530000/532500/532529"},{"code":532530,"name":"金平","fullname":"金平苗族瑶族傣族自治县","level":"district","filename":"530000/532500/532530"},{"code":532531,"name":"绿春","fullname":"绿春县","level":"district","filename":"530000/532500/532531"},{"code":532532,"name":"河口","fullname":"河口瑶族自治县","level":"district","filename":"530000/532500/532532"}]},{"code":532600,"name":"文山","fullname":"文山壮族苗族自治州","level":"city","filename":"530000/532600","children":[{"code":532601,"name":"文山","fullname":"文山市","level":"district","filename":"530000/532600/532601"},{"code":532622,"name":"砚山","fullname":"砚山县","level":"district","filename":"530000/532600/532622"},{"code":532623,"name":"西畴","fullname":"西畴县","level":"district","filename":"530000/532600/532623"},{"code":532624,"name":"麻栗坡","fullname":"麻栗坡县","level":"district","filename":"530000/532600/532624"},{"code":532625,"name":"马关","fullname":"马关县","level":"district","filename":"530000/532600/532625"},{"code":532626,"name":"丘北","fullname":"丘北县","level":"district","filename":"530000/532600/532626"},{"code":532627,"name":"广南","fullname":"广南县","level":"district","filename":"530000/532600/532627"},{"code":532628,"name":"富宁","fullname":"富宁县","level":"district","filename":"530000/532600/532628"}]},{"code":532800,"name":"西双版纳","fullname":"西双版纳傣族自治州","level":"city","filename":"530000/532800","children":[{"code":532801,"name":"景洪","fullname":"景洪市","level":"district","filename":"530000/532800/532801"},{"code":532822,"name":"勐海","fullname":"勐海县","level":"district","filename":"530000/532800/532822"},{"code":532823,"name":"勐腊","fullname":"勐腊县","level":"district","filename":"530000/532800/532823"}]},{"code":532900,"name":"大理","fullname":"大理白族自治州","level":"city","filename":"530000/532900","children":[{"code":532901,"name":"大理","fullname":"大理市","level":"district","filename":"530000/532900/532901"},{"code":532922,"name":"漾濞","fullname":"漾濞彝族自治县","level":"district","filename":"530000/532900/532922"},{"code":532923,"name":"祥云","fullname":"祥云县","level":"district","filename":"530000/532900/532923"},{"code":532924,"name":"宾川","fullname":"宾川县","level":"district","filename":"530000/532900/532924"},{"code":532925,"name":"弥渡","fullname":"弥渡县","level":"district","filename":"530000/532900/532925"},{"code":532926,"name":"南涧","fullname":"南涧彝族自治县","level":"district","filename":"530000/532900/532926"},{"code":532927,"name":"巍山","fullname":"巍山彝族回族自治县","level":"district","filename":"530000/532900/532927"},{"code":532928,"name":"永平","fullname":"永平县","level":"district","filename":"530000/532900/532928"},{"code":532929,"name":"云龙","fullname":"云龙县","level":"district","filename":"530000/532900/532929"},{"code":532930,"name":"洱源","fullname":"洱源县","level":"district","filename":"530000/532900/532930"},{"code":532931,"name":"剑川","fullname":"剑川县","level":"district","filename":"530000/532900/532931"},{"code":532932,"name":"鹤庆","fullname":"鹤庆县","level":"district","filename":"530000/532900/532932"}]},{"code":533100,"name":"德宏","fullname":"德宏傣族景颇族自治州","level":"city","filename":"530000/533100","children":[{"code":533102,"name":"瑞丽","fullname":"瑞丽市","level":"district","filename":"530000/533100/533102"},{"code":533103,"name":"芒市","fullname":"芒市","level":"district","filename":"530000/533100/533103"},{"code":533122,"name":"梁河","fullname":"梁河县","level":"district","filename":"530000/533100/533122"},{"code":533123,"name":"盈江","fullname":"盈江县","level":"district","filename":"530000/533100/533123"},{"code":533124,"name":"陇川","fullname":"陇川县","level":"district","filename":"530000/533100/533124"}]},{"code":533300,"name":"怒江","fullname":"怒江傈僳族自治州","level":"city","filename":"530000/533300","children":[{"code":533301,"name":"泸水","fullname":"泸水市","level":"district","filename":"530000/533300/533301"},{"code":533323,"name":"福贡","fullname":"福贡县","level":"district","filename":"530000/533300/533323"},{"code":533324,"name":"贡山","fullname":"贡山独龙族怒族自治县","level":"district","filename":"530000/533300/533324"},{"code":533325,"name":"兰坪","fullname":"兰坪白族普米族自治县","level":"district","filename":"530000/533300/533325"}]},{"code":533400,"name":"迪庆","fullname":"迪庆藏族自治州","level":"city","filename":"530000/533400","children":[{"code":533401,"name":"香格里拉","fullname":"香格里拉市","level":"district","filename":"530000/533400/533401"},{"code":533422,"name":"德钦","fullname":"德钦县","level":"district","filename":"530000/533400/533422"},{"code":533423,"name":"维西","fullname":"维西傈僳族自治县","level":"district","filename":"530000/533400/533423"}]}]},{"code":540000,"name":"西藏","fullname":"西藏自治区","level":"province","filename":"540000","children":[{"code":540100,"name":"拉萨","fullname":"拉萨市","level":"city","filename":"540000/540100","children":[{"code":540102,"name":"城关","fullname":"城关区","level":"district","filename":"540000/540100/540102"},{"code":540103,"name":"堆龙德庆","fullname":"堆龙德庆区","level":"district","filename":"540000/540100/540103"},{"code":540104,"name":"达孜","fullname":"达孜区","level":"district","filename":"540000/540100/540104"},{"code":540121,"name":"林周","fullname":"林周县","level":"district","filename":"540000/540100/540121"},{"code":540122,"name":"当雄","fullname":"当雄县","level":"district","filename":"540000/540100/540122"},{"code":540123,"name":"尼木","fullname":"尼木县","level":"district","filename":"540000/540100/540123"},{"code":540124,"name":"曲水","fullname":"曲水县","level":"district","filename":"540000/540100/540124"},{"code":540127,"name":"墨竹工卡","fullname":"墨竹工卡县","level":"district","filename":"540000/540100/540127"}]},{"code":540200,"name":"日喀则","fullname":"日喀则市","level":"city","filename":"540000/540200","children":[{"code":540202,"name":"桑珠孜","fullname":"桑珠孜区","level":"district","filename":"540000/540200/540202"},{"code":540221,"name":"南木林","fullname":"南木林县","level":"district","filename":"540000/540200/540221"},{"code":540222,"name":"江孜","fullname":"江孜县","level":"district","filename":"540000/540200/540222"},{"code":540223,"name":"定日","fullname":"定日县","level":"district","filename":"540000/540200/540223"},{"code":540224,"name":"萨迦","fullname":"萨迦县","level":"district","filename":"540000/540200/540224"},{"code":540225,"name":"拉孜","fullname":"拉孜县","level":"district","filename":"540000/540200/540225"},{"code":540226,"name":"昂仁","fullname":"昂仁县","level":"district","filename":"540000/540200/540226"},{"code":540227,"name":"谢通门","fullname":"谢通门县","level":"district","filename":"540000/540200/540227"},{"code":540228,"name":"白朗","fullname":"白朗县","level":"district","filename":"540000/540200/540228"},{"code":540229,"name":"仁布","fullname":"仁布县","level":"district","filename":"540000/540200/540229"},{"code":540230,"name":"康马","fullname":"康马县","level":"district","filename":"540000/540200/540230"},{"code":540231,"name":"定结","fullname":"定结县","level":"district","filename":"540000/540200/540231"},{"code":540232,"name":"仲巴","fullname":"仲巴县","level":"district","filename":"540000/540200/540232"},{"code":540233,"name":"亚东","fullname":"亚东县","level":"district","filename":"540000/540200/540233"},{"code":540234,"name":"吉隆","fullname":"吉隆县","level":"district","filename":"540000/540200/540234"},{"code":540235,"name":"聂拉木","fullname":"聂拉木县","level":"district","filename":"540000/540200/540235"},{"code":540236,"name":"萨嘎","fullname":"萨嘎县","level":"district","filename":"540000/540200/540236"},{"code":540237,"name":"岗巴","fullname":"岗巴县","level":"district","filename":"540000/540200/540237"}]},{"code":540300,"name":"昌都","fullname":"昌都市","level":"city","filename":"540000/540300","children":[{"code":540302,"name":"卡若","fullname":"卡若区","level":"district","filename":"540000/540300/540302"},{"code":540321,"name":"江达","fullname":"江达县","level":"district","filename":"540000/540300/540321"},{"code":540322,"name":"贡觉","fullname":"贡觉县","level":"district","filename":"540000/540300/540322"},{"code":540323,"name":"类乌齐","fullname":"类乌齐县","level":"district","filename":"540000/540300/540323"},{"code":540324,"name":"丁青","fullname":"丁青县","level":"district","filename":"540000/540300/540324"},{"code":540325,"name":"察雅","fullname":"察雅县","level":"district","filename":"540000/540300/540325"},{"code":540326,"name":"八宿","fullname":"八宿县","level":"district","filename":"540000/540300/540326"},{"code":540327,"name":"左贡","fullname":"左贡县","level":"district","filename":"540000/540300/540327"},{"code":540328,"name":"芒康","fullname":"芒康县","level":"district","filename":"540000/540300/540328"},{"code":540329,"name":"洛隆","fullname":"洛隆县","level":"district","filename":"540000/540300/540329"},{"code":540330,"name":"边坝","fullname":"边坝县","level":"district","filename":"540000/540300/540330"}]},{"code":540400,"name":"林芝","fullname":"林芝市","level":"city","filename":"540000/540400","children":[{"code":540402,"name":"巴宜","fullname":"巴宜区","level":"district","filename":"540000/540400/540402"},{"code":540421,"name":"工布江达","fullname":"工布江达县","level":"district","filename":"540000/540400/540421"},{"code":540422,"name":"米林","fullname":"米林县","level":"district","filename":"540000/540400/540422"},{"code":540423,"name":"墨脱","fullname":"墨脱县","level":"district","filename":"540000/540400/540423"},{"code":540424,"name":"波密","fullname":"波密县","level":"district","filename":"540000/540400/540424"},{"code":540425,"name":"察隅","fullname":"察隅县","level":"district","filename":"540000/540400/540425"},{"code":540426,"name":"朗县","fullname":"朗县","level":"district","filename":"540000/540400/540426"}]},{"code":540500,"name":"山南","fullname":"山南市","level":"city","filename":"540000/540500","children":[{"code":540502,"name":"乃东","fullname":"乃东区","level":"district","filename":"540000/540500/540502"},{"code":540521,"name":"扎囊","fullname":"扎囊县","level":"district","filename":"540000/540500/540521"},{"code":540522,"name":"贡嘎","fullname":"贡嘎县","level":"district","filename":"540000/540500/540522"},{"code":540523,"name":"桑日","fullname":"桑日县","level":"district","filename":"540000/540500/540523"},{"code":540524,"name":"琼结","fullname":"琼结县","level":"district","filename":"540000/540500/540524"},{"code":540525,"name":"曲松","fullname":"曲松县","level":"district","filename":"540000/540500/540525"},{"code":540526,"name":"措美","fullname":"措美县","level":"district","filename":"540000/540500/540526"},{"code":540527,"name":"洛扎","fullname":"洛扎县","level":"district","filename":"540000/540500/540527"},{"code":540528,"name":"加查","fullname":"加查县","level":"district","filename":"540000/540500/540528"},{"code":540529,"name":"隆子","fullname":"隆子县","level":"district","filename":"540000/540500/540529"},{"code":540530,"name":"错那","fullname":"错那县","level":"district","filename":"540000/540500/540530"},{"code":540531,"name":"浪卡子","fullname":"浪卡子县","level":"district","filename":"540000/540500/540531"}]},{"code":540600,"name":"那曲","fullname":"那曲市","level":"city","filename":"540000/540600","children":[{"code":540602,"name":"色尼","fullname":"色尼区","level":"district","filename":"540000/540600/540602"},{"code":540621,"name":"嘉黎","fullname":"嘉黎县","level":"district","filename":"540000/540600/540621"},{"code":540622,"name":"比如","fullname":"比如县","level":"district","filename":"540000/540600/540622"},{"code":540623,"name":"聂荣","fullname":"聂荣县","level":"district","filename":"540000/540600/540623"},{"code":540624,"name":"安多","fullname":"安多县","level":"district","filename":"540000/540600/540624"},{"code":540625,"name":"申扎","fullname":"申扎县","level":"district","filename":"540000/540600/540625"},{"code":540626,"name":"索县","fullname":"索县","level":"district","filename":"540000/540600/540626"},{"code":540627,"name":"班戈","fullname":"班戈县","level":"district","filename":"540000/540600/540627"},{"code":540628,"name":"巴青","fullname":"巴青县","level":"district","filename":"540000/540600/540628"},{"code":540629,"name":"尼玛","fullname":"尼玛县","level":"district","filename":"540000/540600/540629"},{"code":540630,"name":"双湖","fullname":"双湖县","level":"district","filename":"540000/540600/540630"}]},{"code":542500,"name":"阿里","fullname":"阿里地区","level":"city","filename":"540000/542500","children":[{"code":542521,"name":"普兰","fullname":"普兰县","level":"district","filename":"540000/542500/542521"},{"code":542522,"name":"札达","fullname":"札达县","level":"district","filename":"540000/542500/542522"},{"code":542523,"name":"噶尔","fullname":"噶尔县","level":"district","filename":"540000/542500/542523"},{"code":542524,"name":"日土","fullname":"日土县","level":"district","filename":"540000/542500/542524"},{"code":542525,"name":"革吉","fullname":"革吉县","level":"district","filename":"540000/542500/542525"},{"code":542526,"name":"改则","fullname":"改则县","level":"district","filename":"540000/542500/542526"},{"code":542527,"name":"措勤","fullname":"措勤县","level":"district","filename":"540000/542500/542527"}]}]},{"code":610000,"name":"陕西","fullname":"陕西省","level":"province","filename":"610000","children":[{"code":610100,"name":"西安","fullname":"西安市","level":"city","filename":"610000/610100","children":[{"code":610102,"name":"新城","fullname":"新城区","level":"district","filename":"610000/610100/610102"},{"code":610103,"name":"碑林","fullname":"碑林区","level":"district","filename":"610000/610100/610103"},{"code":610104,"name":"莲湖","fullname":"莲湖区","level":"district","filename":"610000/610100/610104"},{"code":610111,"name":"灞桥","fullname":"灞桥区","level":"district","filename":"610000/610100/610111"},{"code":610112,"name":"未央","fullname":"未央区","level":"district","filename":"610000/610100/610112"},{"code":610113,"name":"雁塔","fullname":"雁塔区","level":"district","filename":"610000/610100/610113"},{"code":610114,"name":"阎良","fullname":"阎良区","level":"district","filename":"610000/610100/610114"},{"code":610115,"name":"临潼","fullname":"临潼区","level":"district","filename":"610000/610100/610115"},{"code":610116,"name":"长安","fullname":"长安区","level":"district","filename":"610000/610100/610116"},{"code":610117,"name":"高陵","fullname":"高陵区","level":"district","filename":"610000/610100/610117"},{"code":610118,"name":"鄠邑","fullname":"鄠邑区","level":"district","filename":"610000/610100/610118"},{"code":610122,"name":"蓝田","fullname":"蓝田县","level":"district","filename":"610000/610100/610122"},{"code":610124,"name":"周至","fullname":"周至县","level":"district","filename":"610000/610100/610124"}]},{"code":610200,"name":"铜川","fullname":"铜川市","level":"city","filename":"610000/610200","children":[{"code":610202,"name":"王益","fullname":"王益区","level":"district","filename":"610000/610200/610202"},{"code":610203,"name":"印台","fullname":"印台区","level":"district","filename":"610000/610200/610203"},{"code":610204,"name":"耀州","fullname":"耀州区","level":"district","filename":"610000/610200/610204"},{"code":610222,"name":"宜君","fullname":"宜君县","level":"district","filename":"610000/610200/610222"}]},{"code":610300,"name":"宝鸡","fullname":"宝鸡市","level":"city","filename":"610000/610300","children":[{"code":610302,"name":"渭滨","fullname":"渭滨区","level":"district","filename":"610000/610300/610302"},{"code":610303,"name":"金台","fullname":"金台区","level":"district","filename":"610000/610300/610303"},{"code":610304,"name":"陈仓","fullname":"陈仓区","level":"district","filename":"610000/610300/610304"},{"code":610322,"name":"凤翔","fullname":"凤翔区","level":"district","filename":"610000/610300/610322"},{"code":610323,"name":"岐山","fullname":"岐山县","level":"district","filename":"610000/610300/610323"},{"code":610324,"name":"扶风","fullname":"扶风县","level":"district","filename":"610000/610300/610324"},{"code":610326,"name":"眉县","fullname":"眉县","level":"district","filename":"610000/610300/610326"},{"code":610327,"name":"陇县","fullname":"陇县","level":"district","filename":"610000/610300/610327"},{"code":610328,"name":"千阳","fullname":"千阳县","level":"district","filename":"610000/610300/610328"},{"code":610329,"name":"麟游","fullname":"麟游县","level":"district","filename":"610000/610300/610329"},{"code":610330,"name":"凤县","fullname":"凤县","level":"district","filename":"610000/610300/610330"},{"code":610331,"name":"太白","fullname":"太白县","level":"district","filename":"610000/610300/610331"}]},{"code":610400,"name":"咸阳","fullname":"咸阳市","level":"city","filename":"610000/610400","children":[{"code":610402,"name":"秦都","fullname":"秦都区","level":"district","filename":"610000/610400/610402"},{"code":610403,"name":"杨陵","fullname":"杨陵区","level":"district","filename":"610000/610400/610403"},{"code":610404,"name":"渭城","fullname":"渭城区","level":"district","filename":"610000/610400/610404"},{"code":610422,"name":"三原","fullname":"三原县","level":"district","filename":"610000/610400/610422"},{"code":610423,"name":"泾阳","fullname":"泾阳县","level":"district","filename":"610000/610400/610423"},{"code":610424,"name":"乾县","fullname":"乾县","level":"district","filename":"610000/610400/610424"},{"code":610425,"name":"礼泉","fullname":"礼泉县","level":"district","filename":"610000/610400/610425"},{"code":610426,"name":"永寿","fullname":"永寿县","level":"district","filename":"610000/610400/610426"},{"code":610428,"name":"长武","fullname":"长武县","level":"district","filename":"610000/610400/610428"},{"code":610429,"name":"旬邑","fullname":"旬邑县","level":"district","filename":"610000/610400/610429"},{"code":610430,"name":"淳化","fullname":"淳化县","level":"district","filename":"610000/610400/610430"},{"code":610431,"name":"武功","fullname":"武功县","level":"district","filename":"610000/610400/610431"},{"code":610481,"name":"兴平","fullname":"兴平市","level":"district","filename":"610000/610400/610481"},{"code":610482,"name":"彬州","fullname":"彬州市","level":"district","filename":"610000/610400/610482"}]},{"code":610500,"name":"渭南","fullname":"渭南市","level":"city","filename":"610000/610500","children":[{"code":610502,"name":"临渭","fullname":"临渭区","level":"district","filename":"610000/610500/610502"},{"code":610503,"name":"华州","fullname":"华州区","level":"district","filename":"610000/610500/610503"},{"code":610522,"name":"潼关","fullname":"潼关县","level":"district","filename":"610000/610500/610522"},{"code":610523,"name":"大荔","fullname":"大荔县","level":"district","filename":"610000/610500/610523"},{"code":610524,"name":"合阳","fullname":"合阳县","level":"district","filename":"610000/610500/610524"},{"code":610525,"name":"澄城","fullname":"澄城县","level":"district","filename":"610000/610500/610525"},{"code":610526,"name":"蒲城","fullname":"蒲城县","level":"district","filename":"610000/610500/610526"},{"code":610527,"name":"白水","fullname":"白水县","level":"district","filename":"610000/610500/610527"},{"code":610528,"name":"富平","fullname":"富平县","level":"district","filename":"610000/610500/610528"},{"code":610581,"name":"韩城","fullname":"韩城市","level":"district","filename":"610000/610500/610581"},{"code":610582,"name":"华阴","fullname":"华阴市","level":"district","filename":"610000/610500/610582"}]},{"code":610600,"name":"延安","fullname":"延安市","level":"city","filename":"610000/610600","children":[{"code":610602,"name":"宝塔","fullname":"宝塔区","level":"district","filename":"610000/610600/610602"},{"code":610603,"name":"安塞","fullname":"安塞区","level":"district","filename":"610000/610600/610603"},{"code":610621,"name":"延长","fullname":"延长县","level":"district","filename":"610000/610600/610621"},{"code":610622,"name":"延川","fullname":"延川县","level":"district","filename":"610000/610600/610622"},{"code":610625,"name":"志丹","fullname":"志丹县","level":"district","filename":"610000/610600/610625"},{"code":610626,"name":"吴起","fullname":"吴起县","level":"district","filename":"610000/610600/610626"},{"code":610627,"name":"甘泉","fullname":"甘泉县","level":"district","filename":"610000/610600/610627"},{"code":610628,"name":"富县","fullname":"富县","level":"district","filename":"610000/610600/610628"},{"code":610629,"name":"洛川","fullname":"洛川县","level":"district","filename":"610000/610600/610629"},{"code":610630,"name":"宜川","fullname":"宜川县","level":"district","filename":"610000/610600/610630"},{"code":610631,"name":"黄龙","fullname":"黄龙县","level":"district","filename":"610000/610600/610631"},{"code":610632,"name":"黄陵","fullname":"黄陵县","level":"district","filename":"610000/610600/610632"},{"code":610681,"name":"子长","fullname":"子长市","level":"district","filename":"610000/610600/610681"}]},{"code":610700,"name":"汉中","fullname":"汉中市","level":"city","filename":"610000/610700","children":[{"code":610702,"name":"汉台","fullname":"汉台区","level":"district","filename":"610000/610700/610702"},{"code":610703,"name":"南郑","fullname":"南郑区","level":"district","filename":"610000/610700/610703"},{"code":610722,"name":"城固","fullname":"城固县","level":"district","filename":"610000/610700/610722"},{"code":610723,"name":"洋县","fullname":"洋县","level":"district","filename":"610000/610700/610723"},{"code":610724,"name":"西乡","fullname":"西乡县","level":"district","filename":"610000/610700/610724"},{"code":610725,"name":"勉县","fullname":"勉县","level":"district","filename":"610000/610700/610725"},{"code":610726,"name":"宁强","fullname":"宁强县","level":"district","filename":"610000/610700/610726"},{"code":610727,"name":"略阳","fullname":"略阳县","level":"district","filename":"610000/610700/610727"},{"code":610728,"name":"镇巴","fullname":"镇巴县","level":"district","filename":"610000/610700/610728"},{"code":610729,"name":"留坝","fullname":"留坝县","level":"district","filename":"610000/610700/610729"},{"code":610730,"name":"佛坪","fullname":"佛坪县","level":"district","filename":"610000/610700/610730"}]},{"code":610800,"name":"榆林","fullname":"榆林市","level":"city","filename":"610000/610800","children":[{"code":610802,"name":"榆阳","fullname":"榆阳区","level":"district","filename":"610000/610800/610802"},{"code":610803,"name":"横山","fullname":"横山区","level":"district","filename":"610000/610800/610803"},{"code":610822,"name":"府谷","fullname":"府谷县","level":"district","filename":"610000/610800/610822"},{"code":610824,"name":"靖边","fullname":"靖边县","level":"district","filename":"610000/610800/610824"},{"code":610825,"name":"定边","fullname":"定边县","level":"district","filename":"610000/610800/610825"},{"code":610826,"name":"绥德","fullname":"绥德县","level":"district","filename":"610000/610800/610826"},{"code":610827,"name":"米脂","fullname":"米脂县","level":"district","filename":"610000/610800/610827"},{"code":610828,"name":"佳县","fullname":"佳县","level":"district","filename":"610000/610800/610828"},{"code":610829,"name":"吴堡","fullname":"吴堡县","level":"district","filename":"610000/610800/610829"},{"code":610830,"name":"清涧","fullname":"清涧县","level":"district","filename":"610000/610800/610830"},{"code":610831,"name":"子洲","fullname":"子洲县","level":"district","filename":"610000/610800/610831"},{"code":610881,"name":"神木","fullname":"神木市","level":"district","filename":"610000/610800/610881"}]},{"code":610900,"name":"安康","fullname":"安康市","level":"city","filename":"610000/610900","children":[{"code":610902,"name":"汉滨","fullname":"汉滨区","level":"district","filename":"610000/610900/610902"},{"code":610921,"name":"汉阴","fullname":"汉阴县","level":"district","filename":"610000/610900/610921"},{"code":610922,"name":"石泉","fullname":"石泉县","level":"district","filename":"610000/610900/610922"},{"code":610923,"name":"宁陕","fullname":"宁陕县","level":"district","filename":"610000/610900/610923"},{"code":610924,"name":"紫阳","fullname":"紫阳县","level":"district","filename":"610000/610900/610924"},{"code":610925,"name":"岚皋","fullname":"岚皋县","level":"district","filename":"610000/610900/610925"},{"code":610926,"name":"平利","fullname":"平利县","level":"district","filename":"610000/610900/610926"},{"code":610927,"name":"镇坪","fullname":"镇坪县","level":"district","filename":"610000/610900/610927"},{"code":610928,"name":"旬阳","fullname":"旬阳市","level":"district","filename":"610000/610900/610928"},{"code":610929,"name":"白河","fullname":"白河县","level":"district","filename":"610000/610900/610929"}]},{"code":611000,"name":"商洛","fullname":"商洛市","level":"city","filename":"610000/611000","children":[{"code":611002,"name":"商州","fullname":"商州区","level":"district","filename":"610000/611000/611002"},{"code":611021,"name":"洛南","fullname":"洛南县","level":"district","filename":"610000/611000/611021"},{"code":611022,"name":"丹凤","fullname":"丹凤县","level":"district","filename":"610000/611000/611022"},{"code":611023,"name":"商南","fullname":"商南县","level":"district","filename":"610000/611000/611023"},{"code":611024,"name":"山阳","fullname":"山阳县","level":"district","filename":"610000/611000/611024"},{"code":611025,"name":"镇安","fullname":"镇安县","level":"district","filename":"610000/611000/611025"},{"code":611026,"name":"柞水","fullname":"柞水县","level":"district","filename":"610000/611000/611026"}]}]},{"code":620000,"name":"甘肃","fullname":"甘肃省","level":"province","filename":"620000","children":[{"code":620100,"name":"兰州","fullname":"兰州市","level":"city","filename":"620000/620100","children":[{"code":620102,"name":"城关","fullname":"城关区","level":"district","filename":"620000/620100/620102"},{"code":620103,"name":"七里河","fullname":"七里河区","level":"district","filename":"620000/620100/620103"},{"code":620104,"name":"西固","fullname":"西固区","level":"district","filename":"620000/620100/620104"},{"code":620105,"name":"安宁","fullname":"安宁区","level":"district","filename":"620000/620100/620105"},{"code":620111,"name":"红古","fullname":"红古区","level":"district","filename":"620000/620100/620111"},{"code":620121,"name":"永登","fullname":"永登县","level":"district","filename":"620000/620100/620121"},{"code":620122,"name":"皋兰","fullname":"皋兰县","level":"district","filename":"620000/620100/620122"},{"code":620123,"name":"榆中","fullname":"榆中县","level":"district","filename":"620000/620100/620123"}]},{"code":620200,"name":"嘉峪关","fullname":"嘉峪关市","level":"city","filename":"620000/620200"},{"code":620300,"name":"金昌","fullname":"金昌市","level":"city","filename":"620000/620300","children":[{"code":620302,"name":"金川","fullname":"金川区","level":"district","filename":"620000/620300/620302"},{"code":620321,"name":"永昌","fullname":"永昌县","level":"district","filename":"620000/620300/620321"}]},{"code":620400,"name":"白银","fullname":"白银市","level":"city","filename":"620000/620400","children":[{"code":620402,"name":"白银","fullname":"白银区","level":"district","filename":"620000/620400/620402"},{"code":620403,"name":"平川","fullname":"平川区","level":"district","filename":"620000/620400/620403"},{"code":620421,"name":"靖远","fullname":"靖远县","level":"district","filename":"620000/620400/620421"},{"code":620422,"name":"会宁","fullname":"会宁县","level":"district","filename":"620000/620400/620422"},{"code":620423,"name":"景泰","fullname":"景泰县","level":"district","filename":"620000/620400/620423"}]},{"code":620500,"name":"天水","fullname":"天水市","level":"city","filename":"620000/620500","children":[{"code":620502,"name":"秦州","fullname":"秦州区","level":"district","filename":"620000/620500/620502"},{"code":620503,"name":"麦积","fullname":"麦积区","level":"district","filename":"620000/620500/620503"},{"code":620521,"name":"清水","fullname":"清水县","level":"district","filename":"620000/620500/620521"},{"code":620522,"name":"秦安","fullname":"秦安县","level":"district","filename":"620000/620500/620522"},{"code":620523,"name":"甘谷","fullname":"甘谷县","level":"district","filename":"620000/620500/620523"},{"code":620524,"name":"武山","fullname":"武山县","level":"district","filename":"620000/620500/620524"},{"code":620525,"name":"张家川","fullname":"张家川回族自治县","level":"district","filename":"620000/620500/620525"}]},{"code":620600,"name":"武威","fullname":"武威市","level":"city","filename":"620000/620600","children":[{"code":620602,"name":"凉州","fullname":"凉州区","level":"district","filename":"620000/620600/620602"},{"code":620621,"name":"民勤","fullname":"民勤县","level":"district","filename":"620000/620600/620621"},{"code":620622,"name":"古浪","fullname":"古浪县","level":"district","filename":"620000/620600/620622"},{"code":620623,"name":"天祝","fullname":"天祝藏族自治县","level":"district","filename":"620000/620600/620623"}]},{"code":620700,"name":"张掖","fullname":"张掖市","level":"city","filename":"620000/620700","children":[{"code":620702,"name":"甘州","fullname":"甘州区","level":"district","filename":"620000/620700/620702"},{"code":620721,"name":"肃南","fullname":"肃南裕固族自治县","level":"district","filename":"620000/620700/620721"},{"code":620722,"name":"民乐","fullname":"民乐县","level":"district","filename":"620000/620700/620722"},{"code":620723,"name":"临泽","fullname":"临泽县","level":"district","filename":"620000/620700/620723"},{"code":620724,"name":"高台","fullname":"高台县","level":"district","filename":"620000/620700/620724"},{"code":620725,"name":"山丹","fullname":"山丹县","level":"district","filename":"620000/620700/620725"}]},{"code":620800,"name":"平凉","fullname":"平凉市","level":"city","filename":"620000/620800","children":[{"code":620802,"name":"崆峒","fullname":"崆峒区","level":"district","filename":"620000/620800/620802"},{"code":620821,"name":"泾川","fullname":"泾川县","level":"district","filename":"620000/620800/620821"},{"code":620822,"name":"灵台","fullname":"灵台县","level":"district","filename":"620000/620800/620822"},{"code":620823,"name":"崇信","fullname":"崇信县","level":"district","filename":"620000/620800/620823"},{"code":620825,"name":"庄浪","fullname":"庄浪县","level":"district","filename":"620000/620800/620825"},{"code":620826,"name":"静宁","fullname":"静宁县","level":"district","filename":"620000/620800/620826"},{"code":620881,"name":"华亭","fullname":"华亭市","level":"district","filename":"620000/620800/620881"}]},{"code":620900,"name":"酒泉","fullname":"酒泉市","level":"city","filename":"620000/620900","children":[{"code":620902,"name":"肃州","fullname":"肃州区","level":"district","filename":"620000/620900/620902"},{"code":620921,"name":"金塔","fullname":"金塔县","level":"district","filename":"620000/620900/620921"},{"code":620922,"name":"瓜州","fullname":"瓜州县","level":"district","filename":"620000/620900/620922"},{"code":620923,"name":"肃北","fullname":"肃北蒙古族自治县","level":"district","filename":"620000/620900/620923"},{"code":620924,"name":"阿克塞","fullname":"阿克塞哈萨克族自治县","level":"district","filename":"620000/620900/620924"},{"code":620981,"name":"玉门","fullname":"玉门市","level":"district","filename":"620000/620900/620981"},{"code":620982,"name":"敦煌","fullname":"敦煌市","level":"district","filename":"620000/620900/620982"}]},{"code":621000,"name":"庆阳","fullname":"庆阳市","level":"city","filename":"620000/621000","children":[{"code":621002,"name":"西峰","fullname":"西峰区","level":"district","filename":"620000/621000/621002"},{"code":621021,"name":"庆城","fullname":"庆城县","level":"district","filename":"620000/621000/621021"},{"code":621022,"name":"环县","fullname":"环县","level":"district","filename":"620000/621000/621022"},{"code":621023,"name":"华池","fullname":"华池县","level":"district","filename":"620000/621000/621023"},{"code":621024,"name":"合水","fullname":"合水县","level":"district","filename":"620000/621000/621024"},{"code":621025,"name":"正宁","fullname":"正宁县","level":"district","filename":"620000/621000/621025"},{"code":621026,"name":"宁县","fullname":"宁县","level":"district","filename":"620000/621000/621026"},{"code":621027,"name":"镇原","fullname":"镇原县","level":"district","filename":"620000/621000/621027"}]},{"code":621100,"name":"定西","fullname":"定西市","level":"city","filename":"620000/621100","children":[{"code":621102,"name":"安定","fullname":"安定区","level":"district","filename":"620000/621100/621102"},{"code":621121,"name":"通渭","fullname":"通渭县","level":"district","filename":"620000/621100/621121"},{"code":621122,"name":"陇西","fullname":"陇西县","level":"district","filename":"620000/621100/621122"},{"code":621123,"name":"渭源","fullname":"渭源县","level":"district","filename":"620000/621100/621123"},{"code":621124,"name":"临洮","fullname":"临洮县","level":"district","filename":"620000/621100/621124"},{"code":621125,"name":"漳县","fullname":"漳县","level":"district","filename":"620000/621100/621125"},{"code":621126,"name":"岷县","fullname":"岷县","level":"district","filename":"620000/621100/621126"}]},{"code":621200,"name":"陇南","fullname":"陇南市","level":"city","filename":"620000/621200","children":[{"code":621202,"name":"武都","fullname":"武都区","level":"district","filename":"620000/621200/621202"},{"code":621221,"name":"成县","fullname":"成县","level":"district","filename":"620000/621200/621221"},{"code":621222,"name":"文县","fullname":"文县","level":"district","filename":"620000/621200/621222"},{"code":621223,"name":"宕昌","fullname":"宕昌县","level":"district","filename":"620000/621200/621223"},{"code":621224,"name":"康县","fullname":"康县","level":"district","filename":"620000/621200/621224"},{"code":621225,"name":"西和","fullname":"西和县","level":"district","filename":"620000/621200/621225"},{"code":621226,"name":"礼县","fullname":"礼县","level":"district","filename":"620000/621200/621226"},{"code":621227,"name":"徽县","fullname":"徽县","level":"district","filename":"620000/621200/621227"},{"code":621228,"name":"两当","fullname":"两当县","level":"district","filename":"620000/621200/621228"}]},{"code":622900,"name":"临夏","fullname":"临夏回族自治州","level":"city","filename":"620000/622900","children":[{"code":622901,"name":"临夏","fullname":"临夏市","level":"district","filename":"620000/622900/622901"},{"code":622921,"name":"临夏","fullname":"临夏县","level":"district","filename":"620000/622900/622921"},{"code":622922,"name":"康乐","fullname":"康乐县","level":"district","filename":"620000/622900/622922"},{"code":622923,"name":"永靖","fullname":"永靖县","level":"district","filename":"620000/622900/622923"},{"code":622924,"name":"广河","fullname":"广河县","level":"district","filename":"620000/622900/622924"},{"code":622925,"name":"和政","fullname":"和政县","level":"district","filename":"620000/622900/622925"},{"code":622926,"name":"东乡族","fullname":"东乡族自治县","level":"district","filename":"620000/622900/622926"},{"code":622927,"name":"积石山","fullname":"积石山保安族东乡族撒拉族自治县","level":"district","filename":"620000/622900/622927"}]},{"code":623000,"name":"甘南","fullname":"甘南藏族自治州","level":"city","filename":"620000/623000","children":[{"code":623001,"name":"合作","fullname":"合作市","level":"district","filename":"620000/623000/623001"},{"code":623021,"name":"临潭","fullname":"临潭县","level":"district","filename":"620000/623000/623021"},{"code":623022,"name":"卓尼","fullname":"卓尼县","level":"district","filename":"620000/623000/623022"},{"code":623023,"name":"舟曲","fullname":"舟曲县","level":"district","filename":"620000/623000/623023"},{"code":623024,"name":"迭部","fullname":"迭部县","level":"district","filename":"620000/623000/623024"},{"code":623025,"name":"玛曲","fullname":"玛曲县","level":"district","filename":"620000/623000/623025"},{"code":623026,"name":"碌曲","fullname":"碌曲县","level":"district","filename":"620000/623000/623026"},{"code":623027,"name":"夏河","fullname":"夏河县","level":"district","filename":"620000/623000/623027"}]}]},{"code":630000,"name":"青海","fullname":"青海省","level":"province","filename":"630000","children":[{"code":630100,"name":"西宁","fullname":"西宁市","level":"city","filename":"630000/630100","children":[{"code":630102,"name":"城东","fullname":"城东区","level":"district","filename":"630000/630100/630102"},{"code":630103,"name":"城中","fullname":"城中区","level":"district","filename":"630000/630100/630103"},{"code":630104,"name":"城西","fullname":"城西区","level":"district","filename":"630000/630100/630104"},{"code":630105,"name":"城北","fullname":"城北区","level":"district","filename":"630000/630100/630105"},{"code":630106,"name":"湟中","fullname":"湟中区","level":"district","filename":"630000/630100/630106"},{"code":630121,"name":"大通","fullname":"大通回族土族自治县","level":"district","filename":"630000/630100/630121"},{"code":630123,"name":"湟源","fullname":"湟源县","level":"district","filename":"630000/630100/630123"}]},{"code":630200,"name":"海东","fullname":"海东市","level":"city","filename":"630000/630200","children":[{"code":630202,"name":"乐都","fullname":"乐都区","level":"district","filename":"630000/630200/630202"},{"code":630203,"name":"平安","fullname":"平安区","level":"district","filename":"630000/630200/630203"},{"code":630222,"name":"民和","fullname":"民和回族土族自治县","level":"district","filename":"630000/630200/630222"},{"code":630223,"name":"互助","fullname":"互助土族自治县","level":"district","filename":"630000/630200/630223"},{"code":630224,"name":"化隆","fullname":"化隆回族自治县","level":"district","filename":"630000/630200/630224"},{"code":630225,"name":"循化","fullname":"循化撒拉族自治县","level":"district","filename":"630000/630200/630225"}]},{"code":632200,"name":"海北","fullname":"海北藏族自治州","level":"city","filename":"630000/632200","children":[{"code":632221,"name":"门源","fullname":"门源回族自治县","level":"district","filename":"630000/632200/632221"},{"code":632222,"name":"祁连","fullname":"祁连县","level":"district","filename":"630000/632200/632222"},{"code":632223,"name":"海晏","fullname":"海晏县","level":"district","filename":"630000/632200/632223"},{"code":632224,"name":"刚察","fullname":"刚察县","level":"district","filename":"630000/632200/632224"}]},{"code":632300,"name":"黄南","fullname":"黄南藏族自治州","level":"city","filename":"630000/632300","children":[{"code":632301,"name":"同仁","fullname":"同仁市","level":"district","filename":"630000/632300/632301"},{"code":632322,"name":"尖扎","fullname":"尖扎县","level":"district","filename":"630000/632300/632322"},{"code":632323,"name":"泽库","fullname":"泽库县","level":"district","filename":"630000/632300/632323"},{"code":632324,"name":"河南","fullname":"河南蒙古族自治县","level":"district","filename":"630000/632300/632324"}]},{"code":632500,"name":"海南","fullname":"海南藏族自治州","level":"city","filename":"630000/632500","children":[{"code":632521,"name":"共和","fullname":"共和县","level":"district","filename":"630000/632500/632521"},{"code":632522,"name":"同德","fullname":"同德县","level":"district","filename":"630000/632500/632522"},{"code":632523,"name":"贵德","fullname":"贵德县","level":"district","filename":"630000/632500/632523"},{"code":632524,"name":"兴海","fullname":"兴海县","level":"district","filename":"630000/632500/632524"},{"code":632525,"name":"贵南","fullname":"贵南县","level":"district","filename":"630000/632500/632525"}]},{"code":632600,"name":"果洛","fullname":"果洛藏族自治州","level":"city","filename":"630000/632600","children":[{"code":632621,"name":"玛沁","fullname":"玛沁县","level":"district","filename":"630000/632600/632621"},{"code":632622,"name":"班玛","fullname":"班玛县","level":"district","filename":"630000/632600/632622"},{"code":632623,"name":"甘德","fullname":"甘德县","level":"district","filename":"630000/632600/632623"},{"code":632624,"name":"达日","fullname":"达日县","level":"district","filename":"630000/632600/632624"},{"code":632625,"name":"久治","fullname":"久治县","level":"district","filename":"630000/632600/632625"},{"code":632626,"name":"玛多","fullname":"玛多县","level":"district","filename":"630000/632600/632626"}]},{"code":632700,"name":"玉树","fullname":"玉树藏族自治州","level":"city","filename":"630000/632700","children":[{"code":632701,"name":"玉树","fullname":"玉树市","level":"district","filename":"630000/632700/632701"},{"code":632722,"name":"杂多","fullname":"杂多县","level":"district","filename":"630000/632700/632722"},{"code":632723,"name":"称多","fullname":"称多县","level":"district","filename":"630000/632700/632723"},{"code":632724,"name":"治多","fullname":"治多县","level":"district","filename":"630000/632700/632724"},{"code":632725,"name":"囊谦","fullname":"囊谦县","level":"district","filename":"630000/632700/632725"},{"code":632726,"name":"曲麻莱","fullname":"曲麻莱县","level":"district","filename":"630000/632700/632726"}]},{"code":632800,"name":"海西","fullname":"海西蒙古族藏族自治州","level":"city","filename":"630000/632800","children":[{"code":632801,"name":"格尔木","fullname":"格尔木市","level":"district","filename":"630000/632800/632801"},{"code":632802,"name":"德令哈","fullname":"德令哈市","level":"district","filename":"630000/632800/632802"},{"code":632803,"name":"茫崖","fullname":"茫崖市","level":"district","filename":"630000/632800/632803"},{"code":632821,"name":"乌兰","fullname":"乌兰县","level":"district","filename":"630000/632800/632821"},{"code":632822,"name":"都兰","fullname":"都兰县","level":"district","filename":"630000/632800/632822"},{"code":632823,"name":"天峻","fullname":"天峻县","level":"district","filename":"630000/632800/632823"},{"code":632825,"name":"自治州直辖","fullname":"海西蒙古族藏族自治州直辖","level":"district","filename":"630000/632800/632825"}]}]},{"code":640000,"name":"宁夏","fullname":"宁夏回族自治区","level":"province","filename":"640000","children":[{"code":640100,"name":"银川","fullname":"银川市","level":"city","filename":"640000/640100","children":[{"code":640104,"name":"兴庆","fullname":"兴庆区","level":"district","filename":"640000/640100/640104"},{"code":640105,"name":"西夏","fullname":"西夏区","level":"district","filename":"640000/640100/640105"},{"code":640106,"name":"金凤","fullname":"金凤区","level":"district","filename":"640000/640100/640106"},{"code":640121,"name":"永宁","fullname":"永宁县","level":"district","filename":"640000/640100/640121"},{"code":640122,"name":"贺兰","fullname":"贺兰县","level":"district","filename":"640000/640100/640122"},{"code":640181,"name":"灵武","fullname":"灵武市","level":"district","filename":"640000/640100/640181"}]},{"code":640200,"name":"石嘴山","fullname":"石嘴山市","level":"city","filename":"640000/640200","children":[{"code":640202,"name":"大武口","fullname":"大武口区","level":"district","filename":"640000/640200/640202"},{"code":640205,"name":"惠农","fullname":"惠农区","level":"district","filename":"640000/640200/640205"},{"code":640221,"name":"平罗","fullname":"平罗县","level":"district","filename":"640000/640200/640221"}]},{"code":640300,"name":"吴忠","fullname":"吴忠市","level":"city","filename":"640000/640300","children":[{"code":640302,"name":"利通","fullname":"利通区","level":"district","filename":"640000/640300/640302"},{"code":640303,"name":"红寺堡","fullname":"红寺堡区","level":"district","filename":"640000/640300/640303"},{"code":640323,"name":"盐池","fullname":"盐池县","level":"district","filename":"640000/640300/640323"},{"code":640324,"name":"同心","fullname":"同心县","level":"district","filename":"640000/640300/640324"},{"code":640381,"name":"青铜峡","fullname":"青铜峡市","level":"district","filename":"640000/640300/640381"}]},{"code":640400,"name":"固原","fullname":"固原市","level":"city","filename":"640000/640400","children":[{"code":640402,"name":"原州","fullname":"原州区","level":"district","filename":"640000/640400/640402"},{"code":640422,"name":"西吉","fullname":"西吉县","level":"district","filename":"640000/640400/640422"},{"code":640423,"name":"隆德","fullname":"隆德县","level":"district","filename":"640000/640400/640423"},{"code":640424,"name":"泾源","fullname":"泾源县","level":"district","filename":"640000/640400/640424"},{"code":640425,"name":"彭阳","fullname":"彭阳县","level":"district","filename":"640000/640400/640425"}]},{"code":640500,"name":"中卫","fullname":"中卫市","level":"city","filename":"640000/640500","children":[{"code":640502,"name":"沙坡头","fullname":"沙坡头区","level":"district","filename":"640000/640500/640502"},{"code":640521,"name":"中宁","fullname":"中宁县","level":"district","filename":"640000/640500/640521"},{"code":640522,"name":"海原","fullname":"海原县","level":"district","filename":"640000/640500/640522"}]}]},{"code":650000,"name":"新疆","fullname":"新疆维吾尔自治区","level":"province","filename":"650000","children":[{"code":650100,"name":"乌鲁木齐","fullname":"乌鲁木齐市","level":"city","filename":"650000/650100","children":[{"code":650102,"name":"天山","fullname":"天山区","level":"district","filename":"650000/650100/650102"},{"code":650103,"name":"沙依巴克","fullname":"沙依巴克区","level":"district","filename":"650000/650100/650103"},{"code":650104,"name":"新市区","fullname":"新市区","level":"district","filename":"650000/650100/650104"},{"code":650105,"name":"水磨沟","fullname":"水磨沟区","level":"district","filename":"650000/650100/650105"},{"code":650106,"name":"头屯河","fullname":"头屯河区","level":"district","filename":"650000/650100/650106"},{"code":650107,"name":"达坂城","fullname":"达坂城区","level":"district","filename":"650000/650100/650107"},{"code":650109,"name":"米东","fullname":"米东区","level":"district","filename":"650000/650100/650109"},{"code":650121,"name":"乌鲁木齐","fullname":"乌鲁木齐县","level":"district","filename":"650000/650100/650121"}]},{"code":650200,"name":"克拉玛依","fullname":"克拉玛依市","level":"city","filename":"650000/650200","children":[{"code":650202,"name":"独山子","fullname":"独山子区","level":"district","filename":"650000/650200/650202"},{"code":650203,"name":"克拉玛依","fullname":"克拉玛依区","level":"district","filename":"650000/650200/650203"},{"code":650204,"name":"白碱滩","fullname":"白碱滩区","level":"district","filename":"650000/650200/650204"},{"code":650205,"name":"乌尔禾","fullname":"乌尔禾区","level":"district","filename":"650000/650200/650205"}]},{"code":650400,"name":"吐鲁番","fullname":"吐鲁番市","level":"city","filename":"650000/650400","children":[{"code":650402,"name":"高昌","fullname":"高昌区","level":"district","filename":"650000/650400/650402"},{"code":650421,"name":"鄯善","fullname":"鄯善县","level":"district","filename":"650000/650400/650421"},{"code":650422,"name":"托克逊","fullname":"托克逊县","level":"district","filename":"650000/650400/650422"}]},{"code":650500,"name":"哈密","fullname":"哈密市","level":"city","filename":"650000/650500","children":[{"code":650502,"name":"伊州","fullname":"伊州区","level":"district","filename":"650000/650500/650502"},{"code":650521,"name":"巴里坤","fullname":"巴里坤哈萨克自治县","level":"district","filename":"650000/650500/650521"},{"code":650522,"name":"伊吾","fullname":"伊吾县","level":"district","filename":"650000/650500/650522"}]},{"code":652300,"name":"昌吉","fullname":"昌吉回族自治州","level":"city","filename":"650000/652300","children":[{"code":652301,"name":"昌吉","fullname":"昌吉市","level":"district","filename":"650000/652300/652301"},{"code":652302,"name":"阜康","fullname":"阜康市","level":"district","filename":"650000/652300/652302"},{"code":652323,"name":"呼图壁","fullname":"呼图壁县","level":"district","filename":"650000/652300/652323"},{"code":652324,"name":"玛纳斯","fullname":"玛纳斯县","level":"district","filename":"650000/652300/652324"},{"code":652325,"name":"奇台","fullname":"奇台县","level":"district","filename":"650000/652300/652325"},{"code":652327,"name":"吉木萨尔","fullname":"吉木萨尔县","level":"district","filename":"650000/652300/652327"},{"code":652328,"name":"木垒","fullname":"木垒哈萨克自治县","level":"district","filename":"650000/652300/652328"}]},{"code":652700,"name":"博尔塔拉","fullname":"博尔塔拉蒙古自治州","level":"city","filename":"650000/652700","children":[{"code":652701,"name":"博乐","fullname":"博乐市","level":"district","filename":"650000/652700/652701"},{"code":652702,"name":"阿拉山口","fullname":"阿拉山口市","level":"district","filename":"650000/652700/652702"},{"code":652722,"name":"精河","fullname":"精河县","level":"district","filename":"650000/652700/652722"},{"code":652723,"name":"温泉","fullname":"温泉县","level":"district","filename":"650000/652700/652723"}]},{"code":652800,"name":"巴音郭楞","fullname":"巴音郭楞蒙古自治州","level":"city","filename":"650000/652800","children":[{"code":652801,"name":"库尔勒","fullname":"库尔勒市","level":"district","filename":"650000/652800/652801"},{"code":652822,"name":"轮台","fullname":"轮台县","level":"district","filename":"650000/652800/652822"},{"code":652823,"name":"尉犁","fullname":"尉犁县","level":"district","filename":"650000/652800/652823"},{"code":652824,"name":"若羌","fullname":"若羌县","level":"district","filename":"650000/652800/652824"},{"code":652825,"name":"且末","fullname":"且末县","level":"district","filename":"650000/652800/652825"},{"code":652826,"name":"焉耆","fullname":"焉耆回族自治县","level":"district","filename":"650000/652800/652826"},{"code":652827,"name":"和静","fullname":"和静县","level":"district","filename":"650000/652800/652827"},{"code":652828,"name":"和硕","fullname":"和硕县","level":"district","filename":"650000/652800/652828"},{"code":652829,"name":"博湖","fullname":"博湖县","level":"district","filename":"650000/652800/652829"}]},{"code":652900,"name":"阿克苏","fullname":"阿克苏地区","level":"city","filename":"650000/652900","children":[{"code":652901,"name":"阿克苏","fullname":"阿克苏市","level":"district","filename":"650000/652900/652901"},{"code":652902,"name":"库车","fullname":"库车市","level":"district","filename":"650000/652900/652902"},{"code":652922,"name":"温宿","fullname":"温宿县","level":"district","filename":"650000/652900/652922"},{"code":652924,"name":"沙雅","fullname":"沙雅县","level":"district","filename":"650000/652900/652924"},{"code":652925,"name":"新和","fullname":"新和县","level":"district","filename":"650000/652900/652925"},{"code":652926,"name":"拜城","fullname":"拜城县","level":"district","filename":"650000/652900/652926"},{"code":652927,"name":"乌什","fullname":"乌什县","level":"district","filename":"650000/652900/652927"},{"code":652928,"name":"阿瓦提","fullname":"阿瓦提县","level":"district","filename":"650000/652900/652928"},{"code":652929,"name":"柯坪","fullname":"柯坪县","level":"district","filename":"650000/652900/652929"}]},{"code":653000,"name":"克孜勒苏","fullname":"克孜勒苏柯尔克孜自治州","level":"city","filename":"650000/653000","children":[{"code":653001,"name":"阿图什","fullname":"阿图什市","level":"district","filename":"650000/653000/653001"},{"code":653022,"name":"阿克陶","fullname":"阿克陶县","level":"district","filename":"650000/653000/653022"},{"code":653023,"name":"阿合奇","fullname":"阿合奇县","level":"district","filename":"650000/653000/653023"},{"code":653024,"name":"乌恰","fullname":"乌恰县","level":"district","filename":"650000/653000/653024"}]},{"code":653100,"name":"喀什","fullname":"喀什地区","level":"city","filename":"650000/653100","children":[{"code":653101,"name":"喀什","fullname":"喀什市","level":"district","filename":"650000/653100/653101"},{"code":653121,"name":"疏附","fullname":"疏附县","level":"district","filename":"650000/653100/653121"},{"code":653122,"name":"疏勒","fullname":"疏勒县","level":"district","filename":"650000/653100/653122"},{"code":653123,"name":"英吉沙","fullname":"英吉沙县","level":"district","filename":"650000/653100/653123"},{"code":653124,"name":"泽普","fullname":"泽普县","level":"district","filename":"650000/653100/653124"},{"code":653125,"name":"莎车","fullname":"莎车县","level":"district","filename":"650000/653100/653125"},{"code":653126,"name":"叶城","fullname":"叶城县","level":"district","filename":"650000/653100/653126"},{"code":653127,"name":"麦盖提","fullname":"麦盖提县","level":"district","filename":"650000/653100/653127"},{"code":653128,"name":"岳普湖","fullname":"岳普湖县","level":"district","filename":"650000/653100/653128"},{"code":653129,"name":"伽师","fullname":"伽师县","level":"district","filename":"650000/653100/653129"},{"code":653130,"name":"巴楚","fullname":"巴楚县","level":"district","filename":"650000/653100/653130"},{"code":653131,"name":"塔什库尔干","fullname":"塔什库尔干塔吉克自治县","level":"district","filename":"650000/653100/653131"}]},{"code":653200,"name":"和田","fullname":"和田地区","level":"city","filename":"650000/653200","children":[{"code":653201,"name":"和田","fullname":"和田市","level":"district","filename":"650000/653200/653201"},{"code":653221,"name":"和田","fullname":"和田县","level":"district","filename":"650000/653200/653221"},{"code":653222,"name":"墨玉","fullname":"墨玉县","level":"district","filename":"650000/653200/653222"},{"code":653223,"name":"皮山","fullname":"皮山县","level":"district","filename":"650000/653200/653223"},{"code":653224,"name":"洛浦","fullname":"洛浦县","level":"district","filename":"650000/653200/653224"},{"code":653225,"name":"策勒","fullname":"策勒县","level":"district","filename":"650000/653200/653225"},{"code":653226,"name":"于田","fullname":"于田县","level":"district","filename":"650000/653200/653226"},{"code":653227,"name":"民丰","fullname":"民丰县","level":"district","filename":"650000/653200/653227"}]},{"code":654000,"name":"伊犁","fullname":"伊犁哈萨克自治州","level":"city","filename":"650000/654000","children":[{"code":654002,"name":"伊宁","fullname":"伊宁市","level":"district","filename":"650000/654000/654002"},{"code":654003,"name":"奎屯","fullname":"奎屯市","level":"district","filename":"650000/654000/654003"},{"code":654004,"name":"霍尔果斯","fullname":"霍尔果斯市","level":"district","filename":"650000/654000/654004"},{"code":654021,"name":"伊宁","fullname":"伊宁县","level":"district","filename":"650000/654000/654021"},{"code":654022,"name":"察布查尔","fullname":"察布查尔锡伯自治县","level":"district","filename":"650000/654000/654022"},{"code":654023,"name":"霍城","fullname":"霍城县","level":"district","filename":"650000/654000/654023"},{"code":654024,"name":"巩留","fullname":"巩留县","level":"district","filename":"650000/654000/654024"},{"code":654025,"name":"新源","fullname":"新源县","level":"district","filename":"650000/654000/654025"},{"code":654026,"name":"昭苏","fullname":"昭苏县","level":"district","filename":"650000/654000/654026"},{"code":654027,"name":"特克斯","fullname":"特克斯县","level":"district","filename":"650000/654000/654027"},{"code":654028,"name":"尼勒克","fullname":"尼勒克县","level":"district","filename":"650000/654000/654028"}]},{"code":654200,"name":"塔城","fullname":"塔城地区","level":"city","filename":"650000/654200","children":[{"code":654201,"name":"塔城","fullname":"塔城市","level":"district","filename":"650000/654200/654201"},{"code":654202,"name":"乌苏","fullname":"乌苏市","level":"district","filename":"650000/654200/654202"},{"code":654221,"name":"额敏","fullname":"额敏县","level":"district","filename":"650000/654200/654221"},{"code":654223,"name":"沙湾","fullname":"沙湾市","level":"district","filename":"650000/654200/654223"},{"code":654224,"name":"托里","fullname":"托里县","level":"district","filename":"650000/654200/654224"},{"code":654225,"name":"裕民","fullname":"裕民县","level":"district","filename":"650000/654200/654225"},{"code":654226,"name":"和布克赛尔","fullname":"和布克赛尔蒙古自治县","level":"district","filename":"650000/654200/654226"}]},{"code":654300,"name":"阿勒泰","fullname":"阿勒泰地区","level":"city","filename":"650000/654300","children":[{"code":654301,"name":"阿勒泰","fullname":"阿勒泰市","level":"district","filename":"650000/654300/654301"},{"code":654321,"name":"布尔津","fullname":"布尔津县","level":"district","filename":"650000/654300/654321"},{"code":654322,"name":"富蕴","fullname":"富蕴县","level":"district","filename":"650000/654300/654322"},{"code":654323,"name":"福海","fullname":"福海县","level":"district","filename":"650000/654300/654323"},{"code":654324,"name":"哈巴河","fullname":"哈巴河县","level":"district","filename":"650000/654300/654324"},{"code":654325,"name":"青河","fullname":"青河县","level":"district","filename":"650000/654300/654325"},{"code":654326,"name":"吉木乃","fullname":"吉木乃县","level":"district","filename":"650000/654300/654326"}]},{"code":659001,"name":"石河子","fullname":"石河子市","level":"city","filename":"650000/659001"},{"code":659002,"name":"阿拉尔","fullname":"阿拉尔市","level":"city","filename":"650000/659002"},{"code":659003,"name":"图木舒克","fullname":"图木舒克市","level":"city","filename":"650000/659003"},{"code":659004,"name":"五家渠","fullname":"五家渠市","level":"city","filename":"650000/659004"},{"code":659005,"name":"北屯","fullname":"北屯市","level":"city","filename":"650000/659005"},{"code":659006,"name":"铁门关","fullname":"铁门关市","level":"city","filename":"650000/659006"},{"code":659007,"name":"双河","fullname":"双河市","level":"city","filename":"650000/659007"},{"code":659008,"name":"可克达拉","fullname":"可克达拉市","level":"city","filename":"650000/659008"},{"code":659009,"name":"昆玉","fullname":"昆玉市","level":"city","filename":"650000/659009"},{"code":659010,"name":"胡杨河","fullname":"胡杨河市","level":"city","filename":"650000/659010"}]},{"code":710000,"name":"台湾","fullname":"台湾省","level":"province","filename":"710000"},{"code":810000,"name":"香港","fullname":"香港特别行政区","level":"province","filename":"810000","children":[{"code":810001,"name":"中西","fullname":"中西区","level":"district","filename":"810000/810001"},{"code":810002,"name":"湾仔","fullname":"湾仔区","level":"district","filename":"810000/810002"},{"code":810003,"name":"东区","fullname":"东区","level":"district","filename":"810000/810003"},{"code":810004,"name":"南区","fullname":"南区","level":"district","filename":"810000/810004"},{"code":810005,"name":"油尖旺","fullname":"油尖旺区","level":"district","filename":"810000/810005"},{"code":810006,"name":"深水埗","fullname":"深水埗区","level":"district","filename":"810000/810006"},{"code":810007,"name":"九龙城","fullname":"九龙城区","level":"district","filename":"810000/810007"},{"code":810008,"name":"黄大仙","fullname":"黄大仙区","level":"district","filename":"810000/810008"},{"code":810009,"name":"观塘","fullname":"观塘区","level":"district","filename":"810000/810009"},{"code":810010,"name":"荃湾","fullname":"荃湾区","level":"district","filename":"810000/810010"},{"code":810011,"name":"屯门","fullname":"屯门区","level":"district","filename":"810000/810011"},{"code":810012,"name":"元朗","fullname":"元朗区","level":"district","filename":"810000/810012"},{"code":810013,"name":"北区","fullname":"北区","level":"district","filename":"810000/810013"},{"code":810014,"name":"大埔","fullname":"大埔区","level":"district","filename":"810000/810014"},{"code":810015,"name":"西贡","fullname":"西贡区","level":"district","filename":"810000/810015"},{"code":810016,"name":"沙田","fullname":"沙田区","level":"district","filename":"810000/810016"},{"code":810017,"name":"葵青","fullname":"葵青区","level":"district","filename":"810000/810017"},{"code":810018,"name":"离岛","fullname":"离岛区","level":"district","filename":"810000/810018"}]},{"code":820000,"name":"澳门","fullname":"澳门特别行政区","level":"province","filename":"820000","children":[{"code":820001,"name":"花地玛","fullname":"花地玛堂区","level":"district","filename":"820000/820001"},{"code":820002,"name":"花王","fullname":"花王堂区","level":"district","filename":"820000/820002"},{"code":820003,"name":"望德","fullname":"望德堂区","level":"district","filename":"820000/820003"},{"code":820004,"name":"大堂","fullname":"大堂区","level":"district","filename":"820000/820004"},{"code":820005,"name":"风顺","fullname":"风顺堂区","level":"district","filename":"820000/820005"},{"code":820006,"name":"嘉模","fullname":"嘉模堂区","level":"district","filename":"820000/820006"},{"code":820007,"name":"路凼","fullname":"路凼填海区","level":"district","filename":"820000/820007"},{"code":820008,"name":"圣方济各","fullname":"圣方济各堂区","level":"district","filename":"820000/820008"}]}]} \ No newline at end of file diff --git a/wms-webapi/src/main/resources/config/application-dev.yml b/wms-webapi/src/main/resources/config/application-dev.yml new file mode 100644 index 0000000..e4558d2 --- /dev/null +++ b/wms-webapi/src/main/resources/config/application-dev.yml @@ -0,0 +1,346 @@ +--- ### 项目配置 +project: + # URL(跨域配置默认放行此 URL,第三方登录回调默认使用此 URL 为前缀,请注意更改为你实际的前端 URL) + url: http://localhost:6609 + +--- ### 服务器配置 +server: + # HTTP 端口(默认 6609) + port: 6609 + +--- ### 数据源配置 +spring.datasource: + type: com.zaxxer.hikari.HikariDataSource + # 请务必提前创建好名为 ysoft_admin 的数据库,如果使用其他数据库名请注意同步修改 DB_NAME 配置 + url: jdbc:p6spy:mysql://127.0.0.1:3306/continew?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + username: root + password: root + # PostgreSQL 配置 +# url: jdbc:p6spy:mysql://192.168.2.30:${DB_PORT:3306}/continew?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true +# username: ${DB_USER:root} +# password: ${DB_PWD:SQLsql123} +# url: jdbc:p6spy:mysql://81.68.71.142:${DB_PORT:3306}/continew?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true +# username: ${DB_USER:root} +# password: ${DB_PWD:MYsql12@} + driver-class-name: com.p6spy.engine.spy.P6SpyDriver + # Hikari 连接池配置 + hikari: + # 最大连接数量(默认 10,根据实际环境调整) + # 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒 + maximum-pool-size: 20 + # 获取连接超时时间(默认 30000 毫秒,30 秒) + connection-timeout: 30000 + # 空闲连接最大存活时间(默认 600000 毫秒,10 分钟) + idle-timeout: 600000 + # 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime(默认 0,禁用) + keepaliveTime: 30000 + # 连接最大生存时间(默认 1800000 毫秒,30 分钟) + max-lifetime: 1800000 +## Liquibase 配置 +spring.liquibase: + # 是否启用 + enabled: true + # 配置文件路径 + change-log: classpath:/db/changelog/db.changelog-master.yaml + +--- ### 缓存配置 +spring.data: + ## Redis 配置(单机模式) + redis: + # 地址 + host: ${REDIS_HOST:192.168.2.30} +# host: ${REDIS_HOST:127.0.0.1} + # 端口(默认 6379) + port: ${REDIS_PORT:6379} + # 密码(未设置密码时请注释掉) + password: ${REDIS_PWD:redis2025} + # 数据库索引 + database: ${REDIS_DB:1} + # 连接超时时间 + timeout: 10s + # 是否开启 SSL + ssl: + enabled: false + ## Redisson 配置 + redisson: + enabled: true + mode: SINGLE +## JetCache 配置 +jetcache: + # 统计间隔(默认 0,表示不统计) + statIntervalMinutes: 15 + ## 本地/进程级/一级缓存配置 + local: + default: + # 缓存类型 + type: caffeine + # key 转换器的全局配置 + keyConvertor: jackson + # 以毫秒为单位指定超时时间的全局配置 + expireAfterWriteInMillis: 7200000 + # 每个缓存实例的最大元素的全局配置,仅 local 类型的缓存需要指定 + limit: 1000 + ## 远程/分布式/二级缓存配置 + remote: + default: + # 缓存类型 + type: redisson + # key 转换器的全局配置(用于将复杂的 KEY 类型转换为缓存实现可以接受的类型) + keyConvertor: jackson + # 以毫秒为单位指定超时时间的全局配置 + expireAfterWriteInMillis: 7200000 + # 2.7+ 支持两级缓存更新以后失效其他 JVM 中的 local cache,但多个服务共用 Redis 同一个 channel 可能会造成广播风暴,需要在这里指定channel。 + # 你可以决定多个不同的服务是否共用同一个 channel,如果没有指定则不开启。 + broadcastChannel: ${spring.application.name} + # 序列化器的全局配置,仅 remote 类型的缓存需要指定 + valueEncoder: java + valueDecoder: java + +--- ### 验证码配置 +continew-starter.captcha: + ## 行为验证码 + behavior: + enabled: true + cache-type: REDIS + water-mark: + # 一分钟内接口请求次数限制开关(默认:0,关闭,开启后下方失败锁定配置才会生效) + req-frequency-limit-enable: 0 + # 一分钟内验证码最多失败次数限制(默认:5次) + req-get-lock-limit: 5 + # 一分钟内验证码最多失败次数限制达标后锁定时间(默认:300秒) + req-get-lock-seconds: 300 + ## 图形验证码 + graphic: + # 类型 + type: SPEC + # 内容长度 + length: 4 + # 过期时间 + expirationInMinutes: 2 +## 其他验证码配置 +captcha: + ## 邮箱验证码配置 + mail: + # 内容长度 + length: 6 + # 过期时间 + expirationInMinutes: 5 + # 模板路径 + templatePath: mail/captcha.ftl + ## 短信验证码配置 + sms: + # 内容长度 + length: 4 + # 过期时间 + expirationInMinutes: 5 + # 模板 ID + templateId: 1 + +--- ### 日志配置 +continew-starter.log: + # 是否打印日志,开启后可打印访问日志(类似于 Nginx access log) + is-print: true +## 项目日志配置(配置重叠部分,优先级高于 logback-spring.xml 中的配置) +logging: + level: + top.continew.admin: DEBUG + top.continew.starter: DEBUG + file: + path: ./logs + +--- ### 跨域配置 +continew-starter.web.cors: + enabled: true + # 配置允许跨域的域名 + allowed-origins: '*' + # 配置允许跨域的请求方式 + allowed-methods: '*' + # 配置允许跨域的请求头 + allowed-headers: '*' + # 配置允许跨域的响应头 + exposed-headers: '*' + +--- ### 接口文档配置 +springdoc: + swagger-ui: + enabled: true + +--- ### WebSocket 配置 +continew-starter.messaging.websocket: + enabled: true + path: /websocket + # 配置允许跨域的域名 + allowed-origins: '*' + +--- ### 短信配置 +sms: + # 从 YAML 读取配置 + config-type: YAML + http-log: true + is-print: false + blends: + cloopen: + # 短信厂商 + supplier: cloopen + base-url: https://app.cloopen.com:8883/2013-12-26 + access-key-id: 你的Access Key + access-key-secret: 你的Access Key Secret + sdk-app-id: 你的应用ID + +--- ### 邮件配置 +spring.mail: + # 根据需要更换 + host: smtp.126.com + port: 465 + username: 你的邮箱 + password: 你的邮箱授权码 + properties: + mail: + smtp: + auth: true + socketFactory: + class: javax.net.ssl.SSLSocketFactory + port: 465 + +--- ### Just Auth 配置 +justauth: + enabled: true + type: + GITEE: + client-id: 5d271b7f638941812aaf8bfc2e2f08f06d6235ef934e0e39537e2364eb8452c4 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${project.url}/social/callback?source=gitee + GITHUB: + client-id: 38080dad08cfbdfacca9 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${project.url}/social/callback?source=github + cache: + type: REDIS + +--- ### Sa-Token 扩展配置 +sa-token.extension: + # 安全配置:排除(放行)路径配置 + security.excludes: + - /error + # 静态资源 + - /*.html + - /*/*.html + - /*/*.css + - /*/*.js + - /websocket/** + # 接口文档相关资源 + - /favicon.ico + - /doc.html + - /webjars/** + - /swagger-ui/** + - /swagger-resources/** + - /*/api-docs/** + # 本地存储资源 + - /file/** + - /sdk/** + +--- ### 安全配置 +continew-starter.security: + ## 字段加/解密配置 + crypto: + enabled: true + # 对称加密算法密钥 + password: abcdefghijklmnop + # 非对称加密算法密钥(在线生成 RSA 密钥对:http://web.chacuo.net/netrsakeypair) + public-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9uaUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ== + private-key: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAznV2Bi0zIX61NC3zSx8U6lJXbtru325pRV4Wt0aJXGxy6LMTsfxIye1ip+f2WnxrkYfk/X8YZ6FWNQPaAX/iRwIDAQABAkEAk/VcAusrpIqA5Ac2P5Tj0VX3cOuXmyouaVcXonr7f+6y2YTjLQuAnkcfKKocQI/juIRQBFQIqqW/m1nmz1wGeQIhAO8XaA/KxzOIgU0l/4lm0A2Wne6RokJ9HLs1YpOzIUmVAiEA3Q9DQrpAlIuiT1yWAGSxA9RxcjUM/1kdVLTkv0avXWsCIE0X8woEjK7lOSwzMG6RpEx9YHdopjViOj1zPVH61KTxAiBmv/dlhqkJ4rV46fIXELZur0pj6WC3N7a4brR8a+CLLQIhAMQyerWl2cPNVtE/8tkziHKbwW3ZUiBXU24wFxedT9iV + ## 密码编码器配置 + password: + enabled: true + # BCryptPasswordEncoder + encoding-id: bcrypt + ## 限流器配置 + limiter: + enabled: true + key-prefix: RateLimiter + +--- ### 文件上传配置 +spring.servlet: + multipart: + enabled: true + # 单文件上传大小限制 + max-file-size: 10MB + # 单次总上传文件大小限制 + max-request-size: 20MB +## 头像配置 +avatar: + # 存储路径 + path: user/avatar/ + # 支持的后缀 + support-suffix: jpg,jpeg,png,gif + +--- ### Snail Job 配置 +snail-job: + enabled: false + # 客户端地址(默认自动获取本机 IP) + #host: 127.0.0.1 + # 客户端端口(默认:1789) + port: 1789 + # 命名空间 ID + namespace: ${SCHEDULE_NAMESPACE:764d604ec6fc45f68cd92514c40e9e1a} + # 分组名 + group: ${SCHEDULE_GROUP:ysoft-admin} + # 令牌 + token: ${SCHEDULE_TOKEN:SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj} + ## 服务端配置(任务调度中心) + server: + # 服务端地址,若服务端集群部署则此处配置域名 + host: ${SCHEDULE_HOST:127.0.0.1} + # Netty 端口号 + port: ${SCHEDULE_PORT:1788} + # API 配置 + api: + # URL + url: http://127.0.0.1:8001/snail-job + # 用户名 + username: ${SCHEDULE_USERNAME:admin} + # 密码 + password: ${SCHEDULE_PASSWORD:admin} + ## 重试数据批量上报滑动窗口配置 + retry: + reportSlidingWindow: + # 窗口期单位 + chrono-unit: SECONDS + # 窗口期时间长度 + duration: 10 + # 总量窗口期阈值 + total-threshold: 50 + # 窗口数量预警 + window-total-threshold: 150 + ## 调度线程池配置 + dispatcherThreadPool: + # 核心线程数 + corePoolSize: 16 + # 最大线程数 + maximumPoolSize: 16 + # 线程存活时间 + keepAliveTime: 1 + # 时间单位 + timeUnit: SECONDS + # 队列容量 + queueCapacity: 10000 + +uni: + url: http://wo-api.uni-ubi.com/ + appKey: 7834BC94180542C6BFD7031B39266E6F + appSecret: 97386E6DCEFC4758810CC720DC669662 + projectGuid: 114C2FC2EA0B4A848C5F9AF23400E6F9 + +# Minio配置 +minio: + url: http://81.68.71.142:9000 + accessKey: admin + secretKey: JYadmin@1234 + bucketName: employees + bucketName1: visitor + bucketName2: cars + bucketName3: others + gwurl: http://81.68.71.142:9000 + +sdk: + upload: http://81.68.71.142:7701/ diff --git a/wms-webapi/src/main/resources/config/application-generator.yml b/wms-webapi/src/main/resources/config/application-generator.yml new file mode 100644 index 0000000..e8e9530 --- /dev/null +++ b/wms-webapi/src/main/resources/config/application-generator.yml @@ -0,0 +1,141 @@ +--- ### 代码生成器配置 +generator: + # 排除数据表 + excludeTables: + - DATABASECHANGELOG + - DATABASECHANGELOGLOCK + - gen_config + - gen_field_config + ## 类型映射 + typeMappings: + MYSQL: + String: + - varchar + - char + - text + - mediumtext + - longtext + - tinytext + - json + Integer: + - int + - tinyint + - smallint + - mediumint + - integer + Long: + - bigint + Float: + - float + Double: + - double + Boolean: + - bit + BigDecimal: + - decimal + LocalDate: + - date + LocalDateTime: + - datetime + - timestamp + POSTGRE_SQL: + String: + - varchar + - char + - text + - json + Integer: + - int2 + - int4 + Long: + - int8 + Float: + - float4 + Double: + - float8 + Boolean: + - bool + BigDecimal: + - decimal + LocalDate: + - date + LocalDateTime: + - timestamp + ## 模板配置 + templateConfigs: + DO: + # 模板路径 + templatePath: backend/Entity.ftl + # 包名称 + packageName: model.entity + # 排除字段 + excludeFields: + - id + - createUser + - createTime + - updateUser + - updateTime + Query: + templatePath: backend/Query.ftl + packageName: model.query + Req: + templatePath: backend/Req.ftl + packageName: model.req + Resp: + templatePath: backend/Resp.ftl + packageName: model.resp + excludeFields: + - id + - createUser + - createTime + DetailResp: + templatePath: backend/DetailResp.ftl + packageName: model.resp + excludeFields: + - id + - createUser + - createTime + - updateUser + - updateTime + Mapper: + templatePath: backend/Mapper.ftl + packageName: mapper + MapperXml: + templatePath: backend/MapperXml.ftl + packageName: mapper + extension: .xml + suffix: Mapper + Service: + templatePath: backend/Service.ftl + packageName: service + ServiceImpl: + templatePath: backend/ServiceImpl.ftl + packageName: service.impl + Controller: + templatePath: backend/Controller.ftl + packageName: controller + api: + templatePath: frontend/api.ftl + packageName: src/apis + extension: .ts + backend: false + index: + templatePath: frontend/index.ftl + packageName: src/views + extension: .vue + backend: false + AddModal: + templatePath: frontend/AddModal.ftl + packageName: src/views + extension: .vue + backend: false + DetailDrawer: + templatePath: frontend/DetailDrawer.ftl + packageName: src/views + extension: .vue + backend: false + Menu: + template-path: backend/Menu.ftl + packageName: sql + extension: .sql + backend: true \ No newline at end of file diff --git a/wms-webapi/src/main/resources/config/application-prod.yml b/wms-webapi/src/main/resources/config/application-prod.yml new file mode 100644 index 0000000..8d32ae1 --- /dev/null +++ b/wms-webapi/src/main/resources/config/application-prod.yml @@ -0,0 +1,318 @@ +--- ### 项目配置 +project: + # URL(跨域配置默认放行此 URL,第三方登录回调默认使用此 URL 为前缀,请注意更改为你实际的前端 URL) + url: https://admin.ysoft.top + # 是否为生产环境 + production: true + +--- ### 服务器配置 +server: + # HTTP 端口(默认 8080) + port: 18000 + +--- ### 数据源配置 +spring.datasource: + type: com.zaxxer.hikari.HikariDataSource + # 请务必提前创建好名为 ysoft_admin 的数据库,如果使用其他数据库名请注意同步修改 DB_NAME 配置 + url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:ysoft_admin}?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + username: ${DB_USER:root} + password: ${DB_PWD:123456} + driver-class-name: com.mysql.cj.jdbc.Driver +# # PostgreSQL 配置 +# url: jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_NAME:ysoft_admin}?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&stringtype=unspecified +# username: ${DB_USER:root} +# password: ${DB_PWD:123456} +# driver-class-name: org.postgresql.Driver + # Hikari 连接池配置 + hikari: + # 最大连接数量(默认 10,根据实际环境调整) + # 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒 + maximum-pool-size: 20 + # 获取连接超时时间(默认 30000 毫秒,30 秒) + connection-timeout: 30000 + # 空闲连接最大存活时间(默认 600000 毫秒,10 分钟) + idle-timeout: 600000 + # 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime(默认 0,禁用) + keepaliveTime: 30000 + # 连接最大生存时间(默认 1800000 毫秒,30 分钟) + max-lifetime: 1800000 +## Liquibase 配置 +spring.liquibase: + # 是否启用 + enabled: true + # 配置文件路径 + change-log: classpath:/db/changelog/db.changelog-master.yaml + +--- ### 缓存配置 +spring.data: + ## Redis 配置(单机模式) + redis: + # 地址 + host: ${REDIS_HOST:127.0.0.1} + # 端口(默认 6379) + port: ${REDIS_PORT:6379} + # 密码(未设置密码时请注释掉) + password: ${REDIS_PWD:123456} + # 数据库索引 + database: ${REDIS_DB:0} + # 连接超时时间 + timeout: 10s + # 是否开启 SSL + ssl: + enabled: false + ## Redisson 配置 + redisson: + enabled: true + mode: SINGLE +## JetCache 配置 +jetcache: + # 统计间隔(默认 0,表示不统计) + statIntervalMinutes: 0 + ## 本地/进程级/一级缓存配置 + local: + default: + # 缓存类型 + type: caffeine + # key 转换器的全局配置 + keyConvertor: jackson + # 以毫秒为单位指定超时时间的全局配置 + expireAfterWriteInMillis: 7200000 + # 每个缓存实例的最大元素的全局配置,仅 local 类型的缓存需要指定 + limit: 1000 + ## 远程/分布式/二级缓存配置 + remote: + default: + # 缓存类型 + type: redisson + # key 转换器的全局配置(用于将复杂的 KEY 类型转换为缓存实现可以接受的类型) + keyConvertor: jackson + # 以毫秒为单位指定超时时间的全局配置 + expireAfterWriteInMillis: 7200000 + # 2.7+ 支持两级缓存更新以后失效其他 JVM 中的 local cache,但多个服务共用 Redis 同一个 channel 可能会造成广播风暴,需要在这里指定channel。 + # 你可以决定多个不同的服务是否共用同一个 channel,如果没有指定则不开启。 + broadcastChannel: ${spring.application.name} + # 序列化器的全局配置,仅 remote 类型的缓存需要指定 + valueEncoder: java + valueDecoder: java + +--- ### 验证码配置 +continew-starter.captcha: + ## 行为验证码 + behavior: + enabled: true + cache-type: REDIS + water-mark: + # 一分钟内接口请求次数限制开关(默认:0,关闭,开启后下方失败锁定配置才会生效) + req-frequency-limit-enable: 0 + # 一分钟内验证码最多失败次数限制(默认:5次) + req-get-lock-limit: 5 + # 一分钟内验证码最多失败次数限制达标后锁定时间(默认:300秒) + req-get-lock-seconds: 300 + ## 图形验证码 + graphic: + # 类型 + type: SPEC + # 内容长度 + length: 4 + # 过期时间 + expirationInMinutes: 2 +## 其他验证码配置 +captcha: + ## 邮箱验证码配置 + mail: + # 内容长度 + length: 6 + # 过期时间 + expirationInMinutes: 5 + # 模板路径 + templatePath: mail/captcha.ftl + ## 短信验证码配置 + sms: + # 内容长度 + length: 4 + # 过期时间 + expirationInMinutes: 5 + # 模板 ID + templateId: 1 + +--- ### 日志配置 +continew-starter.log: + # 是否打印日志,开启后可打印访问日志(类似于 Nginx access log) + is-print: false +## 项目日志配置(配置重叠部分,优先级高于 logback-spring.xml 中的配置) +logging: + level: + top.ysoft.admin: INFO + top.ysoft.starter: INFO + file: + path: ../logs + +--- ### 跨域配置 +continew-starter.web.cors: + enabled: true + # 配置允许跨域的域名 + allowed-origins: + - ${project.url} + # 配置允许跨域的请求方式 + allowed-methods: '*' + # 配置允许跨域的请求头 + allowed-headers: '*' + # 配置允许跨域的响应头 + exposed-headers: '*' + +--- ### 接口文档配置 +## 接口文档增强配置 +knife4j: + # 开启生产环境屏蔽 + production: ${project.production} + +--- ### WebSocket 配置 +continew-starter.messaging.websocket: + enabled: true + path: /websocket + # 配置允许跨域的域名 + allowed-origins: + - ${project.url} + +--- ### 短信配置 +sms: + # 从 YAML 读取配置 + config-type: YAML + is-print: false + blends: + cloopen: + # 短信厂商 + supplier: cloopen + base-url: https://app.cloopen.com:8883/2013-12-26 + access-key-id: 你的Access Key + access-key-secret: 你的Access Key Secret + sdk-app-id: 你的应用ID + +--- ### 邮件配置 +spring.mail: + # 根据需要更换 + host: smtp.126.com + port: 465 + username: 你的邮箱 + password: 你的邮箱授权码 + properties: + mail: + smtp: + auth: true + socketFactory: + class: javax.net.ssl.SSLSocketFactory + port: 465 + +--- ### Just Auth 配置 +justauth: + enabled: true + type: + GITEE: + client-id: 5d271b7f638941812aaf8bfc2e2f08f06d6235ef934e0e39537e2364eb8452c4 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${project.url}/social/callback?source=gitee + GITHUB: + client-id: 38080dad08cfbdfacca9 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${project.url}/social/callback?source=github + cache: + type: REDIS + +--- ### Sa-Token 扩展配置 +sa-token.extension: + # 安全配置:排除(放行)路径配置 + security.excludes: + - /error + # 静态资源 + - /*.html + - /*/*.html + - /*/*.css + - /*/*.js + - /websocket/** + # 本地存储资源 + - /file/** + +--- ### 安全配置 +continew-starter.security: + ## 字段加/解密配置 + crypto: + enabled: true + # 对称加密算法密钥 + password: abcdefghijklmnop + # 非对称加密算法密钥(在线生成 RSA 密钥对:http://web.chacuo.net/netrsakeypair) + public-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9uaUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ== + private-key: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAznV2Bi0zIX61NC3zSx8U6lJXbtru325pRV4Wt0aJXGxy6LMTsfxIye1ip+f2WnxrkYfk/X8YZ6FWNQPaAX/iRwIDAQABAkEAk/VcAusrpIqA5Ac2P5Tj0VX3cOuXmyouaVcXonr7f+6y2YTjLQuAnkcfKKocQI/juIRQBFQIqqW/m1nmz1wGeQIhAO8XaA/KxzOIgU0l/4lm0A2Wne6RokJ9HLs1YpOzIUmVAiEA3Q9DQrpAlIuiT1yWAGSxA9RxcjUM/1kdVLTkv0avXWsCIE0X8woEjK7lOSwzMG6RpEx9YHdopjViOj1zPVH61KTxAiBmv/dlhqkJ4rV46fIXELZur0pj6WC3N7a4brR8a+CLLQIhAMQyerWl2cPNVtE/8tkziHKbwW3ZUiBXU24wFxedT9iV + ## 密码编码器配置 + password: + enabled: true + # BCryptPasswordEncoder + encoding-id: bcrypt + ## 限流器配置 + limiter: + enabled: true + key-prefix: RateLimiter + +--- ### 文件上传配置 +spring.servlet: + multipart: + enabled: true + # 单文件上传大小限制 + max-file-size: 10MB + # 单次总上传文件大小限制 + max-request-size: 20MB +## 头像配置 +avatar: + # 存储路径 + path: user/avatar/ + # 支持的后缀 + support-suffix: jpg,jpeg,png,gif + +--- ### Snail Job 配置 +snail-job: + # 客户端地址(默认自动获取本机 IP) + #host: 127.0.0.1 + # 客户端端口(默认:1789) + port: 1789 + # 命名空间 ID + namespace: ${SCHEDULE_NAMESPACE:764d604ec6fc45f68cd92514c40e9e1a} + # 分组名 + group: ${SCHEDULE_GROUP:ysoft-admin} + # 令牌 + token: ${SCHEDULE_TOKEN:SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj} + ## 服务端配置(任务调度中心) + server: + # 服务端地址,若服务端集群部署则此处配置域名 + host: ${SCHEDULE_HOST:127.0.0.1} + # Netty 端口号 + port: ${SCHEDULE_PORT:1788} + # API 配置 + api: + # URL + url: http://127.0.0.1:8001/snail-job + # 用户名 + username: ${SCHEDULE_USERNAME:admin} + # 密码 + password: ${SCHEDULE_PASSWORD:admin} + ## 重试数据批量上报滑动窗口配置 + retry: + reportSlidingWindow: + # 窗口期单位 + chrono-unit: SECONDS + # 窗口期时间长度 + duration: 10 + # 总量窗口期阈值 + total-threshold: 50 + # 窗口数量预警 + window-total-threshold: 150 + ## 调度线程池配置 + dispatcherThreadPool: + # 核心线程数 + corePoolSize: 16 + # 最大线程数 + maximumPoolSize: 16 + # 线程存活时间 + keepAliveTime: 1 + # 时间单位 + timeUnit: SECONDS + # 队列容量 + queueCapacity: 10000 \ No newline at end of file diff --git a/wms-webapi/src/main/resources/config/application.yml b/wms-webapi/src/main/resources/config/application.yml new file mode 100644 index 0000000..4e7b390 --- /dev/null +++ b/wms-webapi/src/main/resources/config/application.yml @@ -0,0 +1,295 @@ +--- ### 项目配置 +project: + # 名称 + name: Ysoft Admin + # 应用名称 + app-name: ysoft-admin + # 版本 + version: 3.6.0-SNAPSHOT + # 描述 + description: 持续迭代优化的前后端分离中后台管理系统框架,开箱即用,持续提供舒适的开发体验。 + # 基本包 + base-package: top.ysoft.admin + ## 作者信息配置 + contact: + name: Charles7c + email: charles7c@126.com + url: https://blog.charles7c.top/about/me + ## 许可协议信息配置 + license: + name: Apache-2.0 + url: https://github.com/ysoft-org/ysoft-admin/blob/dev/LICENSE + +--- ### 日志配置 +continew-starter.log: + # 包含信息 + includes: + - DESCRIPTION + - MODULE + - REQUEST_HEADERS + - REQUEST_BODY + - IP_ADDRESS + - BROWSER + - OS + - RESPONSE_HEADERS + - RESPONSE_BODY +## 项目日志配置 +logging: + config: classpath:logback-spring.xml + +--- ### 链路跟踪配置 +continew-starter.web: + trace: + enabled: true + trace-id-name: traceId + ## TLog 配置 + tlog: + enable-invoke-time-print: false + pattern: '[$spanId][$traceId]' + mdc-enable: false + +--- ### 全局响应配置 +continew-starter.web: + response: + # 是否开启国际化(默认:false) + i18n: false + # 自定义失败 HTTP 状态码(默认:200,建议业务和通信状态码区分) + default-http-status-code-on-error: 200 + # 自定义成功响应码(默认:0) + default-success-code: 0 + # 自定义成功提示(默认:ok) + default-success-msg: ok + # 自定义失败响应码(默认:1) + default-error-code: 1 + # 自定义失败提示(默认:error) + default-error-msg: error + # 是否将原生异常错误信息填充到状态信息中 + origin-exception-using-detail-message: false + # 响应类全名(配置后 response-style 将不再生效) + response-class-full-name: top.continew.starter.web.model.R + +--- ### 全局树结构配置(简单树,对应前端 UI) +continew-starter.crud: + tree: + id-key: key + name-key: title + weight-key: sort + +--- ### 接口文档配置 +springdoc: + # 设置对象型参数的展示形式(设为 true 表示将对象型参数平展开,即对象内的属性直接作为参数展示而不是嵌套在对象内,默认 false) + # 如果不添加该全局配置,可以在需要如此处理的对象参数类上使用 @ParameterObject + default-flat-param-object: true + # 分组配置 + group-configs: + - group: all + paths-to-match: /** + paths-to-exclude: + - /error + - group: auth + display-name: 系统认证 + packages-to-scan: ${project.base-package}.controller.auth + - group: common + display-name: 通用接口 + packages-to-scan: ${project.base-package}.controller.common + - group: system + display-name: 系统管理 + packages-to-scan: ${project.base-package}.controller.system + - group: monitor + display-name: 系统监控 + packages-to-scan: ${project.base-package}.controller.monitor + - group: schedule + display-name: 任务调度 + packages-to-scan: ${project.base-package}.controller.schedule + - group: open + display-name: 能力开放 + packages-to-scan: ${project.base-package}.controller.open + - group: code + display-name: 代码生成 + packages-to-scan: ${project.base-package}.controller.code + ## 组件配置 + components: + # 鉴权配置 + security-schemes: + Authorization: + type: HTTP + in: HEADER + name: ${sa-token.token-name} + scheme: ${sa-token.token-prefix} +## 接口文档增强配置 +knife4j: + enable: true + setting: + # 是否显示默认的 footer(默认 true,显示) + enable-footer: false + # 是否自定义 footer(默认 false,非自定义) + enable-footer-custom: true + # 自定义 footer 内容,支持 Markdown 语法 + footer-custom-content: 'Copyright © 2022-present [${project.contact.name}](${project.contact.url}) ⋅ [${project.name}](${project.url}) v${project.version}' + +--- ### Sa-Token 配置 +sa-token: + # Token 名称(同时也是 cookie 名称) + token-name: Authorization + # Token 有效期(单位:秒,默认 30 天,-1 代表永不过期) + timeout: 86400 + # Token 最低活跃频率(单位:秒,默认 -1,代表不限制,永不冻结。如果 token 超过此时间没有访问系统就会被冻结) + active-timeout: 1800 + # 是否打开自动续签(如果此值为 true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + auto-renew: true + # 是否允许同一账号多地同时登录(为 true 时允许一起登录,为 false 时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个 Token(为 true 时所有登录共用一个 Token,为 false 时每次登录新建一个 Token) + is-share: false + # 是否输出操作日志 + is-log: false + # 是否启用动态 activeTimeout 功能 + dynamic-active-timeout: true + # JWT 秘钥 + jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk + ## 扩展配置 + extension: + enabled: true + enableJwt: true + # 持久层配置 + dao.type: REDIS + +--- ### MyBatis Plus 配置 +mybatis-plus: + # Mapper XML 文件目录配置 + mapper-locations: classpath*:/mapper/**/*Mapper.xml + # 类型别名扫描包配置 + type-aliases-package: ${project.base-package}.**.model + ## MyBatis 配置 + configuration: + # MyBatis 自动映射策略 + # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射 + auto-mapping-behavior: PARTIAL + ## 全局配置 + global-config: + banner: true + db-config: + # 主键类型(默认 assign_id,表示自行赋值) + # auto 代表使用数据库自增策略(需要在表中设置好自增约束) +# id-type: ASSIGN_ID + id-type: AUTO + # 逻辑删除字段 + logic-delete-field: isDeleted + # 逻辑删除全局值(默认 1,表示已删除) + logic-delete-value: 1 + # 逻辑未删除全局值(默认 0,表示未删除) + logic-not-delete-value: 0 + ## 扩展配置 + extension: + enabled: true + # Mapper 接口扫描包配置 + mapper-package: ${project.base-package}.**.mapper + # ID 生成器配置 + id-generator: + type: COSID + # 分页插件配置 + pagination: + enabled: true + db-type: MYSQL + +--- ### CosId 配置 +cosid: + namespace: ${spring.application.name} + machine: + enabled: true + # 机器号分配器 + distributor: + type: REDIS + guarder: + # 开启机器号守护 + enabled: true + snowflake: + enabled: true + zone-id: Asia/Shanghai + epoch: 1577203200000 + share: + # 开启时钟回拨同步 + clock-sync: true + friendly: true + provider: + safe-js: + machine-bit: 7 + sequence-bit: 9 + +--- ### 认证配置 +auth: + ## 密码配置 + password: + excludes: + - /auth/user/route + - /auth/user/info + - /auth/logout + - /system/user/password + +--- ### 服务器配置 +server: + servlet: + # 应用访问路径 + context-path: / + ## Undertow 服务器配置 + undertow: + # HTTP POST 请求内容的大小上限(默认 -1,不限制) + max-http-post-size: -1 + # 以下的配置会影响 buffer,这些 buffer 会用于服务器连接的 IO 操作,有点类似 Netty 的池化内存管理 + # 每块 buffer的空间大小(越小的空间被利用越充分,不要设置太大,以免影响其他应用,合适即可) + buffer-size: 512 + # 是否分配的直接内存(NIO 直接分配的堆外内存) + direct-buffers: true + threads: + # 设置 IO 线程数,它主要执行非阻塞的任务,它们会负责多个连接(默认每个 CPU 核心一个线程) + io: 8 + # 阻塞任务线程池,当执行类似 Servlet 请求阻塞操作,Undertow 会从这个线程池中取得线程(它的值设置取决于系统的负载) + worker: 256 + +--- ### Spring 配置 +spring: + application: + name: ${project.app-name} + main: + # 允许定义重名的 bean 对象覆盖原有的 bean + allow-bean-definition-overriding: true + # 允许循环依赖 + allow-circular-references: true + ## 环境配置 + profiles: + # 启用的环境 + active: dev + include: + - generator + ## 线程池配置(默认启用扩展配置,如未指定 corePoolSize、maxPoolSize 则根据机器配置自动设置) + task: + # 异步任务 + execution: + thread-name-prefix: task-pool + # 任务拒绝策略(默认 ABORT,不执行新任务,直接抛出 RejectedExecutionException 异常) + # CALLER_RUNS:提交的任务在执行被拒绝时,会由提交任务的线程去执行 + rejected-policy: CALLER_RUNS + pool: + keep-alive: 300s + shutdown: + # 是否等待任务执行完成再关闭线程池(默认 false) + await-termination: true + # 等待时间 + await-termination-period: 30s + # 定时任务 + scheduling: + thread-name-prefix: schedule-pool + # 任务拒绝策略(默认 ABORT,不执行新任务,直接抛出 RejectedExecutionException 异常) + # CALLER_RUNS:提交的任务在执行被拒绝时,会由提交任务的线程去执行 + rejected-policy: CALLER_RUNS + shutdown: + # 是否等待任务执行完成再关闭线程池(默认 false) + await-termination: true + # 等待时间 + await-termination-period: 30s + +--- ### 健康检查配置 +management.health: + mail: + # 关闭邮箱健康检查(邮箱配置错误或邮箱服务器不可用时,健康检查会报错) + enabled: false diff --git a/wms-webapi/src/main/resources/db/changelog/db.changelog-master.yaml b/wms-webapi/src/main/resources/db/changelog/db.changelog-master.yaml new file mode 100644 index 0000000..e800a80 --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/db.changelog-master.yaml @@ -0,0 +1,26 @@ +databaseChangeLog: +# - include: +# file: db/changelog/mysql/main_table.sql +# - include: +# file: db/changelog/mysql/main_column.sql +# - include: +# file: db/changelog/mysql/main_data.sql +# - include: +# file: db/changelog/mysql/plugin/plugin_schedule.sql +# - include: +# file: db/changelog/mysql/plugin/plugin_open.sql +# - include: +# file: db/changelog/mysql/plugin/plugin_generator.sql +# PostgreSQL +# - include: +# file: db/changelog/postgresql/main_table.sql +# - include: +# file: db/changelog/postgresql/main_column.sql +# - include: +# file: db/changelog/postgresql/main_data.sql +# - include: +# file: db/changelog/postgresql/plugin/plugin_schedule.sql +# - include: +# file: db/changelog/postgresql/plugin/plugin_open.sql +# - include: +# file: db/changelog/postgresql/plugin/plugin_generator.sql \ No newline at end of file diff --git a/wms-webapi/src/main/resources/db/changelog/mysql/main_column.sql b/wms-webapi/src/main/resources/db/changelog/mysql/main_column.sql new file mode 100644 index 0000000..874085b --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/mysql/main_column.sql @@ -0,0 +1,2 @@ +-- liquibase formatted sql + diff --git a/wms-webapi/src/main/resources/db/changelog/mysql/main_data.sql b/wms-webapi/src/main/resources/db/changelog/mysql/main_data.sql new file mode 100644 index 0000000..29436f5 --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/mysql/main_data.sql @@ -0,0 +1,258 @@ +-- liquibase formatted sql + +-- changeset charles7c:1 +-- comment 初始化表数据 +-- 初始化默认菜单 +INSERT INTO `sys_menu` +(`id`, `title`, `parent_id`, `type`, `path`, `name`, `component`, `redirect`, `icon`, `is_external`, `is_cache`, `is_hidden`, `permission`, `sort`, `status`, `create_user`, `create_time`) +VALUES +(1000, '系统管理', 0, 1, '/system', 'System', 'Layout', '/system/user', 'settings', b'0', b'0', b'0', NULL, 1, 1, 1, NOW()), +(1010, '用户管理', 1000, 2, '/system/user', 'SystemUser', 'system/user/index', NULL, 'user', b'0', b'0', b'0', NULL, 1, 1, 1, NOW()), +(1011, '列表', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:list', 1, 1, 1, NOW()), +(1012, '详情', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:detail', 2, 1, 1, NOW()), +(1013, '新增', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:add', 3, 1, 1, NOW()), +(1014, '修改', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:update', 4, 1, 1, NOW()), +(1015, '删除', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:delete', 5, 1, 1, NOW()), +(1016, '导出', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:export', 6, 1, 1, NOW()), +(1017, '导入', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:import', 7, 1, 1, NOW()), +(1018, '重置密码', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:resetPwd', 8, 1, 1, NOW()), +(1019, '分配角色', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:updateRole', 9, 1, 1, NOW()), + +(1030, '角色管理', 1000, 2, '/system/role', 'SystemRole', 'system/role/index', NULL, 'user-group', b'0', b'0', b'0', NULL, 2, 1, 1, NOW()), +(1031, '列表', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:list', 1, 1, 1, NOW()), +(1032, '详情', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:detail', 2, 1, 1, NOW()), +(1033, '新增', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:add', 3, 1, 1, NOW()), +(1034, '修改', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:update', 4, 1, 1, NOW()), +(1035, '删除', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:delete', 5, 1, 1, NOW()), +(1036, '修改权限', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:updatePermission', 6, 1, 1, NOW()), +(1037, '分配', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:assign', 7, 1, 1, NOW()), +(1038, '取消分配', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:unassign', 8, 1, 1, NOW()), + +(1050, '菜单管理', 1000, 2, '/system/menu', 'SystemMenu', 'system/menu/index', NULL, 'menu', b'0', b'0', b'0', NULL, 3, 1, 1, NOW()), +(1051, '列表', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:list', 1, 1, 1, NOW()), +(1052, '详情', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:detail', 2, 1, 1, NOW()), +(1053, '新增', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:add', 3, 1, 1, NOW()), +(1054, '修改', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:update', 4, 1, 1, NOW()), +(1055, '删除', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:delete', 5, 1, 1, NOW()), +(1056, '清除缓存', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:clearCache', 6, 1, 1, NOW()), + +(1060, '部门管理', 1000, 2, '/system/dept', 'SystemDept', 'system/dept/index', NULL, 'mind-mapping', b'0', b'0', b'0', NULL, 4, 1, 1, NOW()), +(1061, '列表', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:list', 1, 1, 1, NOW()), +(1062, '详情', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:detail', 2, 1, 1, NOW()), +(1063, '新增', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:add', 3, 1, 1, NOW()), +(1064, '修改', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:update', 4, 1, 1, NOW()), +(1065, '删除', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:delete', 5, 1, 1, NOW()), +(1066, '导出', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:export', 6, 1, 1, NOW()), + +(1070, '字典管理', 1000, 2, '/system/dict', 'SystemDict', 'system/dict/index', NULL, 'bookmark', b'0', b'0', b'0', NULL, 5, 1, 1, NOW()), +(1071, '列表', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:list', 1, 1, 1, NOW()), +(1072, '详情', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:detail', 2, 1, 1, NOW()), +(1073, '新增', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:add', 3, 1, 1, NOW()), +(1074, '修改', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:update', 4, 1, 1, NOW()), +(1075, '删除', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:delete', 5, 1, 1, NOW()), +(1076, '清除缓存', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:clearCache', 6, 1, 1, NOW()), +(1080, '字典项管理', 1000, 2, '/system/dict/item', 'SystemDictItem', 'system/dict/item/index', NULL, 'bookmark', b'0', b'0', b'1', NULL, 5, 1, 1, NOW()), +(1081, '列表', 1080, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:item:list', 1, 1, 1, NOW()), +(1082, '详情', 1080, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:item:detail', 2, 1, 1, NOW()), +(1083, '新增', 1080, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:item:add', 3, 1, 1, NOW()), +(1084, '修改', 1080, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:item:update', 4, 1, 1, NOW()), +(1085, '删除', 1080, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:item:delete', 5, 1, 1, NOW()), + +(1090, '通知公告', 1000, 2, '/system/notice', 'SystemNotice', 'system/notice/index', NULL, 'notification', b'0', b'0', b'0', NULL, 6, 1, 1, NOW()), +(1091, '列表', 1090, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:notice:list', 1, 1, 1, NOW()), +(1092, '详情', 1090, 2, '/system/notice/detail', 'SystemNoticeDetail', 'system/notice/detail/index', NULL, NULL, b'0', b'0', b'1', 'system:notice:detail', 2, 1, 1, NOW()), +(1093, '新增', 1090, 2, '/system/notice/add', 'SystemNoticeAdd', 'system/notice/add/index', NULL, NULL, b'0', b'0', b'1', 'system:notice:add', 3, 1, 1, NOW()), +(1094, '修改', 1090, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:notice:update', 4, 1, 1, NOW()), +(1095, '删除', 1090, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:notice:delete', 5, 1, 1, NOW()), + +(1100, '文件管理', 1000, 2, '/system/file', 'SystemFile', 'system/file/index', NULL, 'file', b'0', b'0', b'0', NULL, 7, 1, 1, NOW()), +(1101, '列表', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:list', 1, 1, 1, NOW()), +(1102, '详情', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:detail', 2, 1, 1, NOW()), +(1103, '上传', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:upload', 3, 1, 1, NOW()), +(1104, '修改', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:update', 4, 1, 1, NOW()), +(1105, '删除', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:delete', 5, 1, 1, NOW()), +(1106, '下载', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:download', 6, 1, 1, NOW()), + +(1110, '存储管理', 1000, 2, '/system/storage', 'SystemStorage', 'system/storage/index', NULL, 'storage', b'0', b'0', b'0', NULL, 8, 1, 1, NOW()), +(1111, '列表', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:list', 1, 1, 1, NOW()), +(1112, '详情', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:detail', 2, 1, 1, NOW()), +(1113, '新增', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:add', 3, 1, 1, NOW()), +(1114, '修改', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:update', 4, 1, 1, NOW()), +(1115, '删除', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:delete', 5, 1, 1, NOW()), +(1116, '修改状态', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:updateStatus', 6, 1, 1, NOW()), +(1117, '设为默认存储', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:setDefault', 7, 1, 1, NOW()), + +( 1180, '终端管理', 1000, 2, '/system/client', 'SystemClient', 'system/client/index', NULL, 'mobile', b'0', b'0', b'0', NULL, 9, 1, 1, NOW()), +(1181, '列表', 1180, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:list', 1, 1, 1, NOW()), +(1182, '详情', 1180, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:detail', 2, 1, 1, NOW()), +(1183, '新增', 1180, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:add', 3, 1, 1, NOW()), +(1184, '修改', 1180, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:update', 4, 1, 1, NOW()), +(1185, '删除', 1180, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:delete', 5, 1, 1, NOW()), + +(1190, '系统配置', 1000, 2, '/system/config', 'SystemConfig', 'system/config/index', NULL, 'config', b'0', b'0', b'0', NULL, 999, 1, 1, NOW()), +(1191, '查看', 1190, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:config:list', 1, 1, 1, NOW()), +(1192, '修改', 1190, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:config:update', 2, 1, 1, NOW()), +(1193, '重置', 1190, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:config:reset', 3, 1, 1, NOW()), + +(2000, '系统监控', 0, 1, '/monitor', 'Monitor', 'Layout', '/monitor/online', 'computer', b'0', b'0', b'0', NULL, 2, 1, 1, NOW()), +(2010, '在线用户', 2000, 2, '/monitor/online', 'MonitorOnline', 'monitor/online/index', NULL, 'user', b'0', b'0', b'0', NULL, 1, 1, 1, NOW()), +(2011, '列表', 2010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'monitor:online:list', 1, 1, 1, NOW()), +(2012, '强退', 2010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'monitor:online:kickout', 2, 1, 1, NOW()), + +(2020, '系统日志', 2000, 2, '/monitor/log', 'MonitorLog', 'monitor/log/index', NULL, 'history', b'0', b'0', b'0', NULL, 2, 1, 1, NOW()), +(2021, '列表', 2020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'monitor:log:list', 1, 1, 1, NOW()), +(2022, '详情', 2020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'monitor:log:detail', 2, 1, 1, NOW()), +(2023, '导出', 2020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'monitor:log:export', 3, 1, 1, NOW()); + +-- 初始化默认部门 +INSERT INTO `sys_dept` +(`id`, `name`, `parent_id`, `ancestors`, `description`, `sort`, `status`, `is_system`, `create_user`, `create_time`) +VALUES +(1, 'Xxx科技有限公司', 0, '0', '系统初始部门', 1, 1, b'1', 1, NOW()), +(547887852587843590, 'Xxx(天津)科技有限公司', 1, '0,1', NULL, 1, 1, b'0', 1, NOW()), +(547887852587843591, '研发部', 547887852587843590, '0,1,547887852587843590', NULL, 1, 1, b'0', 1, NOW()), +(547887852587843592, 'UI部', 547887852587843590, '0,1,547887852587843590', NULL, 2, 1, b'0', 1, NOW()), +(547887852587843593, '测试部', 547887852587843590, '0,1,547887852587843590', NULL, 3, 1, b'0', 1, NOW()), +(547887852587843594, '运维部', 547887852587843590, '0,1,547887852587843590', NULL, 4, 1, b'0', 1, NOW()), +(547887852587843595, '研发一组', 547887852587843591, '0,1,547887852587843590,547887852587843591', NULL, 1, 1, b'0', 1, NOW()), +(547887852587843596, '研发二组', 547887852587843591, '0,1,547887852587843590,547887852587843591', NULL, 2, 2, b'0', 1, NOW()), + +(547887852587843597, 'Xxx(四川)科技有限公司', 1, '0,1', NULL, 2, 1, b'0', 1, NOW()), +(547887852587843598, '研发部', 547887852587843597, '0,1,547887852587843597', NULL, 1, 1, b'0', 1, NOW()), +(547887852587843599, '研发一组', 547887852587843598, '0,1,547887852587843597,547887852587843598', NULL, 1, 1, b'0', 1, NOW()), + +(547887852587843600, 'Xxx(江西)科技有限公司', 1, '0,1', NULL, 3, 1, b'0', 1, NOW()), +(547887852587843601, '研发部', 547887852587843600, '0,1,547887852587843600', NULL, 1, 1, b'0', 1, NOW()), +(547887852587843602, '研发一组', 547887852587843601, '0,1,547887852587843600,547887852587843601', NULL, 1, 1, b'0', 1, NOW()), + +(547887852587843603, 'Xxx(江苏)科技有限公司', 1, '0,1', NULL, 4, 1, b'0', 1, NOW()), +(547887852587843604, '研发部', 547887852587843603, '0,1,547887852587843603', NULL, 1, 1, b'0', 1, NOW()), +(547887852587843605, '研发一组', 547887852587843604, '0,1,547887852587843603,547887852587843604', NULL, 1, 1, b'0', 1, NOW()), + +(547887852587843606, 'Xxx(浙江)科技有限公司', 1, '0,1', NULL, 5, 1, b'0', 1, NOW()), +(547887852587843607, '研发部', 547887852587843606, '0,1,547887852587843606', NULL, 1, 1, b'0', 1, NOW()), +(547887852587843608, '研发一组', 547887852587843607, '0,1,547887852587843606,547887852587843607', NULL, 1, 1, b'0', 1, NOW()), + +(547887852587843609, 'Xxx(湖南)科技有限公司', 1, '0,1', NULL, 6, 1, b'0', 1, NOW()), +(547887852587843610, '研发部', 547887852587843609, '0,1,547887852587843609', NULL, 1, 1, b'0', 1, NOW()), +(547887852587843611, '研发一组', 547887852587843610, '0,1,547887852587843609,547887852587843610', NULL, 1, 1, b'0', 1, NOW()); + +-- 初始化默认角色 +INSERT INTO `sys_role` +(`id`, `name`, `code`, `data_scope`, `description`, `sort`, `is_system`, `create_user`, `create_time`) +VALUES +(1, '系统管理员', 'admin', 1, '系统初始角色', 1, b'1', 1, NOW()), +(547888897925840927, '测试人员', 'tester', 5, NULL, 2, b'0', 1, NOW()), +(547888897925840928, '研发人员', 'developer', 4, NULL, 3, b'0', 1, NOW()); + +-- 初始化默认用户:admin/admin123;test/test123 +INSERT INTO `sys_user` +(`id`, `username`, `nickname`, `password`, `gender`, `email`, `phone`, `avatar`, `description`, `status`, `is_system`, `pwd_reset_time`, `dept_id`, `create_user`, `create_time`) +VALUES +(1, 'admin', '系统管理员', '{bcrypt}$2a$10$4jGwK2BMJ7FgVR.mgwGodey8.xR8FLoU1XSXpxJ9nZQt.pufhasSa', 1, '42190c6c5639d2ca4edb4150a35e058559ccf8270361a23745a2fd285a273c28', '5bda89a4609a65546422ea56bfe5eab4', NULL, '系统初始用户', 1, b'1', NOW(), 1, 1, NOW()), +(547889293968801822, 'test', '测试员', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 2, NULL, NULL, NULL, NULL, 1, b'0', NOW(), 547887852587843593, 1, NOW()), +(547889293968801823, 'Charles', 'Charles', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '代码写到极致,就是艺术。', 1, b'0', NOW(), 547887852587843595, 1, NOW()), +(547889293968801824, 'Yoofff', 'Yoofff', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '弱小和无知不是生存的障碍,傲慢才是。', 1, b'0', NOW(), 1, 1, NOW()), +(547889293968801825, 'Jasmine', 'Jasmine', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '干就完事了!', 1, b'0', NOW(), 547887852587843605, 1, NOW()), +(547889293968801826, 'AutumnSail', '秋登', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '只有追求完美,才能创造奇迹。', 1, b'0', NOW(), 547887852587843602, 1, NOW()), +(547889293968801827, 'Kils', 'Kils', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '可以摆烂,但不能真的菜。', 1, b'0', NOW(), 547887852587843599, 1, NOW()), +(547889293968801828, 'mochou', '莫愁', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '万事莫愁,皆得所愿。', 1, b'0', NOW(), 547887852587843602, 1, NOW()), +(547889293968801829, 'Jing', 'MS-Jing', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '路虽远,行则将至。', 2, b'0', NOW(), 547887852587843599, 1, NOW()), +(547889293968801830, 'domw', '梓陌', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '胜利是奖赏,挫折是常态。', 1, b'0', NOW(), 547887852587843608, 1, NOW()), +(547889293968801831, 'xtanyu', '小熊', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '不想上班。', 1, b'0', NOW(), 547887852587843611, 1, NOW()); + +-- 初始化默认参数 +INSERT INTO `sys_option` +(`id`, `category`, `name`, `code`, `value`, `default_value`, `description`) +VALUES +(1, 'SITE', '系统名称', 'SITE_TITLE', NULL, 'ContiNew Admin', '显示在浏览器标题栏和登录界面的系统名称'), +(2, 'SITE', '系统描述', 'SITE_DESCRIPTION', NULL, '持续迭代优化的前后端分离中后台管理系统框架', '用于 SEO 的网站元描述'), +(3, 'SITE', '版权声明', 'SITE_COPYRIGHT', NULL, 'Copyright © 2022 - present ContiNew Admin 版权所有', '显示在页面底部的版权声明文本'), +(4, 'SITE', '备案号', 'SITE_BEIAN', NULL, NULL, '工信部 ICP 备案编号(如:京ICP备12345678号)'), +(5, 'SITE', '系统图标', 'SITE_FAVICON', NULL, '/favicon.ico', '浏览器标签页显示的网站图标(建议 .ico 格式)'), +(6, 'SITE', '系统LOGO', 'SITE_LOGO', NULL, '/logo.svg', '显示在登录页面和系统导航栏的网站图标(建议 .svg 格式)'), +(10, 'PASSWORD', '密码错误锁定阈值', 'PASSWORD_ERROR_LOCK_COUNT', NULL, '5', '连续登录失败次数达到该值将锁定账号(0-10次,0表示禁用锁定)'), +(11, 'PASSWORD', '账号锁定时长(分钟)', 'PASSWORD_ERROR_LOCK_MINUTES', NULL, '5', '账号锁定后自动解锁的时间(1-1440分钟,即24小时)'), +(12, 'PASSWORD', '密码有效期(天)', 'PASSWORD_EXPIRATION_DAYS', NULL, '0', '密码强制修改周期(0-999天,0表示永不过期)'), +(13, 'PASSWORD', '密码到期提醒(天)', 'PASSWORD_EXPIRATION_WARNING_DAYS', NULL, '0', '密码过期前的提前提醒天数(0表示不提醒)'), +(14, 'PASSWORD', '历史密码重复校验次数', 'PASSWORD_REPETITION_TIMES', NULL, '3', '禁止使用最近 N 次的历史密码(3-32次)'), +(15, 'PASSWORD', '密码最小长度', 'PASSWORD_MIN_LENGTH', NULL, '8', '密码最小字符长度要求(8-32个字符)'), +(16, 'PASSWORD', '是否允许密码包含用户名', 'PASSWORD_ALLOW_CONTAIN_USERNAME', NULL, '1', '是否允许密码包含正序或倒序的用户名字符'), +(17, 'PASSWORD', '密码是否必须包含特殊字符', 'PASSWORD_REQUIRE_SYMBOLS', NULL, '0', '是否要求密码必须包含特殊字符(如:!@#$%)'), +(20, 'MAIL', '邮件协议', 'MAIL_PROTOCOL', NULL, 'smtp', '邮件发送协议类型'), +(21, 'MAIL', '服务器地址', 'MAIL_HOST', NULL, 'smtp.126.com', '邮件服务器地址'), +(22, 'MAIL', '服务器端口', 'MAIL_PORT', NULL, '465', '邮件服务器连接端口'), +(23, 'MAIL', '邮箱账号', 'MAIL_USERNAME', NULL, 'charles7c@126.com', '发件人邮箱地址'), +(24, 'MAIL', '邮箱密码', 'MAIL_PASSWORD', NULL, NULL, '服务授权密码/客户端专用密码'), +(25, 'MAIL', '启用SSL加密', 'MAIL_SSL_ENABLED', NULL, '1', '是否启用SSL/TLS加密连接'), +(26, 'MAIL', 'SSL端口号', 'MAIL_SSL_PORT', NULL, '465', 'SSL加密连接的备用端口(通常与主端口一致)'), +(27, 'LOGIN', '是否启用验证码', 'LOGIN_CAPTCHA_ENABLED', NULL, '1', NULL); + +-- 初始化默认字典 +INSERT INTO `sys_dict` +(`id`, `name`, `code`, `description`, `is_system`, `create_user`, `create_time`) +VALUES +(1, '公告类型', 'notice_type', NULL, b'1', 1, NOW()), +(2, '消息类型', 'message_type', NULL, b'1', 1, NOW()), +(3, '终端类型', 'client_type', NULL, b'1', 1, NOW()); + +INSERT INTO `sys_dict_item` +(`id`, `label`, `value`, `color`, `sort`, `description`, `status`, `dict_id`, `create_user`, `create_time`) +VALUES +(1, '通知', '1', 'blue', 1, NULL, 1, 1, 1, NOW()), +(2, '活动', '2', 'orangered', 2, NULL, 1, 1, 1, NOW()), +(3, '安全消息', '1', 'blue', 1, NULL, 1, 2, 1, NOW()), +(4, '活动消息', '2', 'orangered', 2, NULL, 1, 2, 1, NOW()), +(5, '桌面端', 'PC', 'blue', 1, NULL, 1, 3, 1, NOW()), +(6, '安卓', 'ANDROID', '#148628', 2, NULL, 1, 3, 1, NOW()), +(7, '小程序', 'XCX', '#7930AD', 3, NULL, 1, 3, 1, NOW()); + +-- 初始化默认用户和角色关联数据 +INSERT INTO `sys_user_role` +(`id`, `user_id`, `role_id`) +VALUES +(1, 1, 1), +(2, 547889293968801822, 547888897925840927), +(3, 547889293968801823, 547888897925840928), +(4, 547889293968801824, 547888897925840928), +(5, 547889293968801825, 547888897925840928), +(6, 547889293968801826, 547888897925840928), +(7, 547889293968801827, 547888897925840928), +(8, 547889293968801828, 547888897925840928), +(9, 547889293968801829, 547888897925840928), +(10, 547889293968801830, 547888897925840928), +(11, 547889293968801831, 547888897925840928); + +-- 初始化默认角色和菜单关联数据 +INSERT INTO `sys_role_menu` +(`role_id`, `menu_id`) +VALUES +(547888897925840927, 1000), +(547888897925840927, 1010), +(547888897925840927, 1011), +(547888897925840927, 1012), +(547888897925840927, 1013), +(547888897925840927, 1014), +(547888897925840928, 2000), +(547888897925840928, 2010), +(547888897925840928, 2011), +(547888897925840928, 2020), +(547888897925840928, 2021), +(547888897925840928, 2022), +(547888897925840928, 2023); + +-- 初始化默认角色和部门关联数据 +INSERT INTO `sys_role_dept` (`role_id`, `dept_id`) VALUES (547888897925840927, 547887852587843593); + +-- 初始化默认存储 +INSERT INTO `sys_storage` +(`id`, `name`, `code`, `type`, `access_key`, `secret_key`, `endpoint`, `bucket_name`, `domain`, `description`, `is_default`, `sort`, `status`, `create_user`, `create_time`) +VALUES +(1, '开发环境', 'local_dev', 1, NULL, NULL, NULL, 'C:/ysoft-admin/data/file/', 'http://localhost:8000/file', '本地存储', b'1', 1, 1, 1, NOW()), +(2, '生产环境', 'local_prod', 1, NULL, NULL, NULL, '../data/file/', 'http://api.ysoft.top/file', '本地存储', b'0', 2, 2, 1, NOW()); + +-- 初始化终端数据 +INSERT INTO `sys_client` +(`id`, `client_id`, `client_key`, `client_secret`, `auth_type`, `client_type`, `active_timeout`, `timeout`, `status`, `create_user`, `create_time`) +VALUES +(1, 'ef51c9a3e9046c4f2ea45142c8a8344a', 'pc', 'dd77ab1e353a027e0d60ce3b151e8642', '["ACCOUNT", "EMAIL", "PHONE", "SOCIAL"]', 'PC', 1800, 86400, 1, 1, NOW()); diff --git a/wms-webapi/src/main/resources/db/changelog/mysql/main_table.sql b/wms-webapi/src/main/resources/db/changelog/mysql/main_table.sql new file mode 100644 index 0000000..3d66091 --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/mysql/main_table.sql @@ -0,0 +1,323 @@ +-- liquibase formatted sql + +-- changeset charles7c:1 +-- comment 初始化表结构 +CREATE TABLE IF NOT EXISTS `sys_menu` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `title` varchar(30) NOT NULL COMMENT '标题', + `parent_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '上级菜单ID', + `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:目录;2:菜单;3:按钮)', + `path` varchar(255) DEFAULT NULL COMMENT '路由地址', + `name` varchar(50) DEFAULT NULL COMMENT '组件名称', + `component` varchar(255) DEFAULT NULL COMMENT '组件路径', + `redirect` varchar(255) DEFAULT NULL COMMENT '重定向地址', + `icon` varchar(50) DEFAULT NULL COMMENT '图标', + `is_external` bit(1) DEFAULT b'0' COMMENT '是否外链', + `is_cache` bit(1) DEFAULT b'0' COMMENT '是否缓存', + `is_hidden` bit(1) DEFAULT b'0' COMMENT '是否隐藏', + `permission` varchar(100) DEFAULT NULL COMMENT '权限标识', + `sort` int NOT NULL DEFAULT 999 COMMENT '排序', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)', + `create_user` bigint(20) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_title_parent_id`(`title`, `parent_id`), + INDEX `idx_parent_id`(`parent_id`), + INDEX `idx_create_user`(`create_user`), + INDEX `idx_update_user`(`update_user`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单表'; + +CREATE TABLE IF NOT EXISTS `sys_dept` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(30) NOT NULL COMMENT '名称', + `parent_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '上级部门ID', + `ancestors` varchar(512) NOT NULL DEFAULT '' COMMENT '祖级列表', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `sort` int NOT NULL DEFAULT 999 COMMENT '排序', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)', + `is_system` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为系统内置数据', + `create_user` bigint(20) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_name_parent_id`(`name`, `parent_id`), + INDEX `idx_parent_id`(`parent_id`), + INDEX `idx_create_user`(`create_user`), + INDEX `idx_update_user`(`update_user`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表'; + +CREATE TABLE IF NOT EXISTS `sys_role` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(30) NOT NULL COMMENT '名称', + `code` varchar(30) NOT NULL COMMENT '编码', + `data_scope` tinyint(1) NOT NULL DEFAULT 4 COMMENT '数据权限(1:全部数据权限;2:本部门及以下数据权限;3:本部门数据权限;4:仅本人数据权限;5:自定义数据权限)', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `sort` int NOT NULL DEFAULT 999 COMMENT '排序', + `is_system` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为系统内置数据', + `menu_check_strictly` bit(1) DEFAULT b'0' COMMENT '菜单选择是否父子节点关联', + `dept_check_strictly` bit(1) DEFAULT b'0' COMMENT '部门选择是否父子节点关联', + `create_user` bigint(20) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_name`(`name`), + UNIQUE INDEX `uk_code`(`code`), + INDEX `idx_create_user`(`create_user`), + INDEX `idx_update_user`(`update_user`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; + +CREATE TABLE IF NOT EXISTS `sys_user` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `username` varchar(64) NOT NULL COMMENT '用户名', + `nickname` varchar(30) NOT NULL COMMENT '昵称', + `password` varchar(255) DEFAULT NULL COMMENT '密码', + `gender` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '性别(0:未知;1:男;2:女)', + `email` varchar(255) DEFAULT NULL COMMENT '邮箱', + `phone` varchar(255) DEFAULT NULL COMMENT '手机号码', + `avatar` longtext DEFAULT NULL COMMENT '头像', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)', + `is_system` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为系统内置数据', + `pwd_reset_time` datetime DEFAULT NULL COMMENT '最后一次修改密码时间', + `dept_id` bigint(20) NOT NULL COMMENT '部门ID', + `create_user` bigint(20) DEFAULT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_username`(`username`), + UNIQUE INDEX `uk_email`(`email`), + UNIQUE INDEX `uk_phone`(`phone`), + INDEX `idx_dept_id`(`dept_id`), + INDEX `idx_create_user`(`create_user`), + INDEX `idx_update_user`(`update_user`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; + +CREATE TABLE IF NOT EXISTS `sys_user_password_history` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `password` varchar(255) NOT NULL COMMENT '密码', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + INDEX `idx_user_id`(`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户历史密码表'; + +CREATE TABLE IF NOT EXISTS `sys_user_social` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `source` varchar(255) NOT NULL COMMENT '来源', + `open_id` varchar(255) NOT NULL COMMENT '开放ID', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `meta_json` text DEFAULT NULL COMMENT '附加信息', + `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_source_open_id`(`source`, `open_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户社会化关联表'; + +CREATE TABLE IF NOT EXISTS `sys_user_role` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_user_id_role_id`(`user_id`, `role_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户和角色关联表'; + +CREATE TABLE IF NOT EXISTS `sys_role_menu` ( + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', + PRIMARY KEY (`role_id`, `menu_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色和菜单关联表'; + +CREATE TABLE IF NOT EXISTS `sys_role_dept` ( + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `dept_id` bigint(20) NOT NULL COMMENT '部门ID', + PRIMARY KEY (`role_id`, `dept_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色和部门关联表'; + +CREATE TABLE IF NOT EXISTS `sys_option` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `category` varchar(50) NOT NULL COMMENT '类别', + `name` varchar(50) NOT NULL COMMENT '名称', + `code` varchar(100) NOT NULL COMMENT '键', + `value` longtext DEFAULT NULL COMMENT '值', + `default_value` longtext DEFAULT NULL COMMENT '默认值', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_category_code`(`category`, `code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='参数表'; + +CREATE TABLE IF NOT EXISTS `sys_dict` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(30) NOT NULL COMMENT '名称', + `code` varchar(30) NOT NULL COMMENT '编码', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `is_system` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为系统内置数据', + `create_user` bigint(20) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_name`(`name`), + UNIQUE INDEX `uk_code`(`code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='字典表'; + +CREATE TABLE IF NOT EXISTS `sys_dict_item` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `label` varchar(30) NOT NULL COMMENT '标签', + `value` varchar(30) NOT NULL COMMENT '值', + `color` varchar(30) DEFAULT NULL COMMENT '标签颜色', + `sort` int NOT NULL DEFAULT 999 COMMENT '排序', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)', + `dict_id` bigint(20) NOT NULL COMMENT '字典ID', + `create_user` bigint(20) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_value_dict_id`(`value`, `dict_id`), + INDEX `idx_dict_id`(`dict_id`), + INDEX `idx_create_user`(`create_user`), + INDEX `idx_update_user`(`update_user`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='字典项表'; + +CREATE TABLE IF NOT EXISTS `sys_log` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `trace_id` varchar(255) DEFAULT NULL COMMENT '链路ID', + `description` varchar(255) NOT NULL COMMENT '日志描述', + `module` varchar(100) NOT NULL COMMENT '所属模块', + `request_url` varchar(512) NOT NULL COMMENT '请求URL', + `request_method` varchar(10) NOT NULL COMMENT '请求方式', + `request_headers` text DEFAULT NULL COMMENT '请求头', + `request_body` text DEFAULT NULL COMMENT '请求体', + `status_code` int NOT NULL COMMENT '状态码', + `response_headers` text DEFAULT NULL COMMENT '响应头', + `response_body` mediumtext DEFAULT NULL COMMENT '响应体', + `time_taken` bigint(20) NOT NULL COMMENT '耗时(ms)', + `ip` varchar(100) DEFAULT NULL COMMENT 'IP', + `address` varchar(255) DEFAULT NULL COMMENT 'IP归属地', + `browser` varchar(100) DEFAULT NULL COMMENT '浏览器', + `os` varchar(100) DEFAULT NULL COMMENT '操作系统', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:成功;2:失败)', + `error_msg` text DEFAULT NULL COMMENT '错误信息', + `create_user` bigint(20) DEFAULT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + INDEX `idx_module`(`module`), + INDEX `idx_ip`(`ip`), + INDEX `idx_address`(`address`), + INDEX `idx_create_time`(`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统日志表'; + +CREATE TABLE IF NOT EXISTS `sys_message` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `title` varchar(50) NOT NULL COMMENT '标题', + `content` varchar(255) DEFAULT NULL COMMENT '内容', + `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:系统消息)', + `create_user` bigint(20) DEFAULT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息表'; + +CREATE TABLE IF NOT EXISTS `sys_message_user` ( + `message_id` bigint(20) NOT NULL COMMENT '消息ID', + `user_id` bigint(11) NOT NULL COMMENT '用户ID', + `is_read` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已读', + `read_time` datetime DEFAULT NULL COMMENT '读取时间', + PRIMARY KEY (`message_id`, `user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息和用户关联表'; + +CREATE TABLE IF NOT EXISTS `sys_notice` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `title` varchar(150) NOT NULL COMMENT '标题', + `content` mediumtext NOT NULL COMMENT '内容', + `type` varchar(30) NOT NULL COMMENT '类型', + `effective_time` datetime DEFAULT NULL COMMENT '生效时间', + `terminate_time` datetime DEFAULT NULL COMMENT '终止时间', + `notice_scope` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '通知范围(1:所有人;2:指定用户)', + `notice_users` json DEFAULT NULL COMMENT '通知用户', + `sort` int NOT NULL DEFAULT 999 COMMENT '排序', + `create_user` bigint(20) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + INDEX `idx_create_user`(`create_user`), + INDEX `idx_update_user`(`update_user`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公告表'; + +CREATE TABLE IF NOT EXISTS `sys_storage` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(100) NOT NULL COMMENT '名称', + `code` varchar(30) NOT NULL COMMENT '编码', + `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:本地存储;2:对象存储)', + `access_key` varchar(255) DEFAULT NULL COMMENT 'Access Key', + `secret_key` varchar(255) DEFAULT NULL COMMENT 'Secret Key', + `endpoint` varchar(255) DEFAULT NULL COMMENT 'Endpoint', + `bucket_name` varchar(255) DEFAULT NULL COMMENT 'Bucket', + `domain` varchar(255) NOT NULL DEFAULT '' COMMENT '域名', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `is_default` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为默认存储', + `sort` int NOT NULL DEFAULT 999 COMMENT '排序', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)', + `create_user` bigint(20) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_code`(`code`), + INDEX `idx_create_user`(`create_user`), + INDEX `idx_update_user`(`update_user`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存储表'; + +CREATE TABLE IF NOT EXISTS `sys_file` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(255) NOT NULL COMMENT '名称', + `size` bigint(20) NOT NULL COMMENT '大小(字节)', + `url` varchar(512) NOT NULL COMMENT 'URL', + `parent_path` varchar(512) DEFAULT '/' COMMENT '上级目录', + `abs_path` varchar(1024) NOT NULL COMMENT '绝对路径', + `extension` varchar(100) DEFAULT NULL COMMENT '扩展名', + `content_type` varchar(64) NOT NULL COMMENT '内容类型', + `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(0: 目录;1:其他;2:图片;3:文档;4:视频;5:音频)', + `md5` varchar(128) NOT NULL COMMENT 'MD5值', + `metadata` text DEFAULT NULL COMMENT '元数据', + `thumbnail_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小(字节)', + `thumbnail_url` varchar(512) DEFAULT NULL COMMENT '缩略图URL', + `thumbnail_metadata` text DEFAULT NULL COMMENT '缩略图元数据', + `storage_id` bigint(20) NOT NULL COMMENT '存储ID', + `create_user` bigint(20) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + INDEX `idx_url`(`url`), + INDEX `idx_md5`(`md5`), + INDEX `idx_type`(`type`), + INDEX `idx_create_user`(`create_user`), + INDEX `idx_update_user`(`update_user`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件表'; + +CREATE TABLE IF NOT EXISTS `sys_client` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `client_id` varchar(50) NOT NULL COMMENT '终端ID', + `client_key` varchar(255) NOT NULL COMMENT '终端Key', + `client_secret` varchar(255) NOT NULL COMMENT '终端秘钥', + `auth_type` json NOT NULL COMMENT '认证类型', + `client_type` varchar(50) NOT NULL COMMENT '终端类型', + `active_timeout` bigint(20) DEFAULT -1 COMMENT 'Token最低活跃频率(单位:秒,-1:不限制,永不冻结)', + `timeout` bigint(20) DEFAULT 2592000 COMMENT 'Token有效期(单位:秒,-1:永不过期)', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)', + `create_user` bigint(20) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_client_id`(`client_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='终端表'; diff --git a/wms-webapi/src/main/resources/db/changelog/mysql/plugin/plugin_generator.sql b/wms-webapi/src/main/resources/db/changelog/mysql/plugin/plugin_generator.sql new file mode 100644 index 0000000..2ad7fbe --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/mysql/plugin/plugin_generator.sql @@ -0,0 +1,50 @@ +-- liquibase formatted sql + +-- changeset charles7c:1 +-- comment 初始化代码生成插件 +-- 初始化表结构 +CREATE TABLE IF NOT EXISTS `gen_config` ( + `table_name` varchar(64) NOT NULL COMMENT '表名称', + `module_name` varchar(60) NOT NULL COMMENT '模块名称', + `package_name` varchar(60) NOT NULL COMMENT '包名称', + `business_name` varchar(50) NOT NULL COMMENT '业务名称', + `author` varchar(100) NOT NULL COMMENT '作者', + `table_prefix` varchar(20) DEFAULT NULL COMMENT '表前缀', + `is_override` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否覆盖', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`table_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='生成配置表'; + +CREATE TABLE IF NOT EXISTS `gen_field_config` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `table_name` varchar(64) NOT NULL COMMENT '表名称', + `column_name` varchar(64) NOT NULL COMMENT '列名称', + `column_type` varchar(25) NOT NULL COMMENT '列类型', + `column_size` bigint(20) DEFAULT NULL COMMENT '列大小', + `field_name` varchar(64) NOT NULL COMMENT '字段名称', + `field_type` varchar(25) NOT NULL COMMENT '字段类型', + `field_sort` int NOT NULL DEFAULT 999 COMMENT '字段排序', + `comment` varchar(512) DEFAULT NULL COMMENT '注释', + `is_required` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否必填', + `show_in_list` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否在列表中显示', + `show_in_form` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否在表单中显示', + `show_in_query` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否在查询中显示', + `form_type` tinyint(1) UNSIGNED DEFAULT NULL COMMENT '表单类型', + `query_type` tinyint(1) UNSIGNED DEFAULT NULL COMMENT '查询方式', + `dict_code` varchar(30) DEFAULT NULL COMMENT '字典编码', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + INDEX `idx_table_name`(`table_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='字段配置表'; + +-- 初始化默认菜单 +INSERT INTO `sys_menu` +(`id`, `title`, `parent_id`, `type`, `path`, `name`, `component`, `redirect`, `icon`, `is_external`, `is_cache`, `is_hidden`, `permission`, `sort`, `status`, `create_user`, `create_time`) +VALUES +(9000, '代码生成', 0, 1, '/code', 'Code', 'Layout', '/code/generator', 'code-release-managment', b'0', b'0', b'0', NULL, 9, 1, 1, NOW()), +(9010, '代码生成', 9000, 2, '/code/generator', 'CodeGenerator', 'code/generator/index', NULL, 'code', b'0', b'0', b'0', NULL, 1, 1, 1, NOW()), +(9011, '列表', 9010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'code:generator:list', 1, 1, 1, NOW()), +(9012, '配置', 9010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'code:generator:config', 2, 1, 1, NOW()), +(9013, '预览', 9010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'code:generator:preview', 3, 1, 1, NOW()), +(9014, '生成', 9010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'code:generator:generate', 4, 1, 1, NOW()); \ No newline at end of file diff --git a/wms-webapi/src/main/resources/db/changelog/mysql/plugin/plugin_open.sql b/wms-webapi/src/main/resources/db/changelog/mysql/plugin/plugin_open.sql new file mode 100644 index 0000000..8fe9809 --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/mysql/plugin/plugin_open.sql @@ -0,0 +1,37 @@ +-- liquibase formatted sql + +-- changeset chengzi:1 +-- comment 初始化能力开放插件 +-- 初始化表结构 +CREATE TABLE IF NOT EXISTS `sys_app` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(100) NOT NULL COMMENT '名称', + `access_key` varchar(255) NOT NULL COMMENT 'Access Key(访问密钥)', + `secret_key` varchar(255) NOT NULL COMMENT 'Secret Key(私有密钥)', + `expire_time` datetime DEFAULT NULL COMMENT '失效时间', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)', + `create_user` bigint(20) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_access_key`(`access_key`), + INDEX `idx_create_user`(`create_user`), + INDEX `idx_update_user`(`update_user`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='应用表'; + +-- 初始化默认菜单 +INSERT INTO `sys_menu` +(`id`, `title`, `parent_id`, `type`, `path`, `name`, `component`, `redirect`, `icon`, `is_external`, `is_cache`, `is_hidden`, `permission`, `sort`, `status`, `create_user`, `create_time`) +VALUES +(7000, '能力开放', 0, 1, '/open', 'Open', 'Layout', '/open/app', 'expand', b'0', b'0', b'0', NULL, 7, 1, 1, NOW()), +(7010, '应用管理', 7000, 2, '/open/app', 'OpenApp', 'open/app/index', NULL, 'common', b'0', b'0', b'0', NULL, 1, 1, 1, NOW()), +(7011, '列表', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:list', 1, 1, 1, NOW()), +(7012, '详情', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:detail', 2, 1, 1, NOW()), +(7013, '新增', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:add', 3, 1, 1, NOW()), +(7014, '修改', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:update', 4, 1, 1, NOW()), +(7015, '删除', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:delete', 5, 1, 1, NOW()), +(7016, '导出', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:export', 6, 1, 1, NOW()), +(7017, '查看密钥', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:secret', 7, 1, 1, NOW()), +(7018, '重置密钥', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:resetSecret', 8, 1, 1, NOW()); diff --git a/wms-webapi/src/main/resources/db/changelog/mysql/plugin/plugin_schedule.sql b/wms-webapi/src/main/resources/db/changelog/mysql/plugin/plugin_schedule.sql new file mode 100644 index 0000000..a673fab --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/mysql/plugin/plugin_schedule.sql @@ -0,0 +1,21 @@ +-- liquibase formatted sql + +-- changeset kai:1 +-- comment 初始化任务调度插件 +-- 初始化默认菜单 +INSERT INTO `sys_menu` +(`id`, `title`, `parent_id`, `type`, `path`, `name`, `component`, `redirect`, `icon`, `is_external`, `is_cache`, `is_hidden`, `permission`, `sort`, `status`, `create_user`, `create_time`) +VALUES +(3000, '任务调度', 0, 1, '/schedule', 'Schedule', 'Layout', '/schedule/job', 'schedule', b'0', b'0', b'0', NULL, 3, 1, 1, NOW()), +(3010, '任务管理', 3000, 2, '/schedule/job', 'ScheduleJob', 'schedule/job/index', NULL, 'select-all', b'0', b'0', b'0', NULL, 1, 1, 1, NOW()), +(3011, '列表', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:list', 1, 1, 1, NOW()), +(3012, '详情', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:detail', 2, 1, 1, NOW()), +(3013, '新增', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:add', 3, 1, 1, NOW()), +(3014, '修改', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:update', 4, 1, 1, NOW()), +(3015, '删除', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:delete', 5, 1, 1, NOW()), +(3016, '执行', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:trigger', 6, 1, 1, NOW()), +(3020, '任务日志', 3000, 2, '/schedule/log', 'ScheduleLog', 'schedule/log/index', NULL, 'find-replace', b'0', b'0', b'0', NULL, 2, 1, 1, NOW()), +(3021, '列表', 3020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:log:list', 1, 1, 1, NOW()), +(3022, '详情', 3020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:log:detail', 2, 1, 1, NOW()), +(3023, '停止', 3020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:log:stop', 3, 1, 1, NOW()), +(3024, '重试', 3020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:log:retry', 4, 1, 1, NOW()); diff --git a/wms-webapi/src/main/resources/db/changelog/postgresql/main_column.sql b/wms-webapi/src/main/resources/db/changelog/postgresql/main_column.sql new file mode 100644 index 0000000..874085b --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/postgresql/main_column.sql @@ -0,0 +1,2 @@ +-- liquibase formatted sql + diff --git a/wms-webapi/src/main/resources/db/changelog/postgresql/main_data.sql b/wms-webapi/src/main/resources/db/changelog/postgresql/main_data.sql new file mode 100644 index 0000000..5e1a415 --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/postgresql/main_data.sql @@ -0,0 +1,258 @@ +-- liquibase formatted sql + +-- changeset charles7c:1 +-- comment 初始化表数据 +-- 初始化默认菜单 +INSERT INTO "sys_menu" +("id", "title", "parent_id", "type", "path", "name", "component", "redirect", "icon", "is_external", "is_cache", "is_hidden", "permission", "sort", "status", "create_user", "create_time") +VALUES +(1000, '系统管理', 0, 1, '/system', 'System', 'Layout', '/system/user', 'settings', false, false, false, NULL, 1, 1, 1, NOW()), +(1010, '用户管理', 1000, 2, '/system/user', 'SystemUser', 'system/user/index', NULL, 'user', false, false, false, NULL, 1, 1, 1, NOW()), +(1011, '列表', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:list', 1, 1, 1, NOW()), +(1012, '详情', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:detail', 2, 1, 1, NOW()), +(1013, '新增', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:add', 3, 1, 1, NOW()), +(1014, '修改', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:update', 4, 1, 1, NOW()), +(1015, '删除', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:delete', 5, 1, 1, NOW()), +(1016, '导出', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:export', 6, 1, 1, NOW()), +(1017, '导入', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:import', 7, 1, 1, NOW()), +(1018, '重置密码', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:resetPwd', 8, 1, 1, NOW()), +(1019, '分配角色', 1010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:user:updateRole', 9, 1, 1, NOW()), + +(1030, '角色管理', 1000, 2, '/system/role', 'SystemRole', 'system/role/index', NULL, 'user-group', false, false, false, NULL, 2, 1, 1, NOW()), +(1031, '列表', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:list', 1, 1, 1, NOW()), +(1032, '详情', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:detail', 2, 1, 1, NOW()), +(1033, '新增', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:add', 3, 1, 1, NOW()), +(1034, '修改', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:update', 4, 1, 1, NOW()), +(1035, '删除', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:delete', 5, 1, 1, NOW()), +(1036, '修改权限', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:updatePermission', 6, 1, 1, NOW()), +(1037, '分配', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:assign', 7, 1, 1, NOW()), +(1038, '取消分配', 1030, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:role:unassign', 8, 1, 1, NOW()), + +(1050, '菜单管理', 1000, 2, '/system/menu', 'SystemMenu', 'system/menu/index', NULL, 'menu', false, false, false, NULL, 3, 1, 1, NOW()), +(1051, '列表', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:list', 1, 1, 1, NOW()), +(1052, '详情', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:detail', 2, 1, 1, NOW()), +(1053, '新增', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:add', 3, 1, 1, NOW()), +(1054, '修改', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:update', 4, 1, 1, NOW()), +(1055, '删除', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:delete', 5, 1, 1, NOW()), +(1056, '清除缓存', 1050, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:menu:clearCache', 6, 1, 1, NOW()), + +(1060, '部门管理', 1000, 2, '/system/dept', 'SystemDept', 'system/dept/index', NULL, 'mind-mapping', false, false, false, NULL, 4, 1, 1, NOW()), +(1061, '列表', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:list', 1, 1, 1, NOW()), +(1062, '详情', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:detail', 2, 1, 1, NOW()), +(1063, '新增', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:add', 3, 1, 1, NOW()), +(1064, '修改', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:update', 4, 1, 1, NOW()), +(1065, '删除', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:delete', 5, 1, 1, NOW()), +(1066, '导出', 1060, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dept:export', 6, 1, 1, NOW()), + +(1070, '字典管理', 1000, 2, '/system/dict', 'SystemDict', 'system/dict/index', NULL, 'bookmark', false, false, false, NULL, 5, 1, 1, NOW()), +(1071, '列表', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:list', 1, 1, 1, NOW()), +(1072, '详情', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:detail', 2, 1, 1, NOW()), +(1073, '新增', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:add', 3, 1, 1, NOW()), +(1074, '修改', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:update', 4, 1, 1, NOW()), +(1075, '删除', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:delete', 5, 1, 1, NOW()), +(1076, '清除缓存', 1070, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:clearCache', 6, 1, 1, NOW()), +(1080, '字典项管理', 1000, 2, '/system/dict/item', 'SystemDictItem', 'system/dict/item/index', NULL, 'bookmark', false, false, true, NULL, 5, 1, 1, NOW()), +(1081, '列表', 1080, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:item:list', 1, 1, 1, NOW()), +(1082, '详情', 1080, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:item:detail', 2, 1, 1, NOW()), +(1083, '新增', 1080, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:item:add', 3, 1, 1, NOW()), +(1084, '修改', 1080, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:item:update', 4, 1, 1, NOW()), +(1085, '删除', 1080, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:item:delete', 5, 1, 1, NOW()), + +(1090, '通知公告', 1000, 2, '/system/notice', 'SystemNotice', 'system/notice/index', NULL, 'notification', false, false, false, NULL, 6, 1, 1, NOW()), +(1091, '列表', 1090, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:notice:list', 1, 1, 1, NOW()), +(1092, '详情', 1090, 2, '/system/notice/detail', 'SystemNoticeDetail', 'system/notice/detail/index', NULL, NULL, false, false, true, 'system:notice:detail', 2, 1, 1, NOW()), +(1093, '新增', 1090, 2, '/system/notice/add', 'SystemNoticeAdd', 'system/notice/add/index', NULL, NULL, false, false, true, 'system:notice:add', 3, 1, 1, NOW()), +(1094, '修改', 1090, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:notice:update', 4, 1, 1, NOW()), +(1095, '删除', 1090, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:notice:delete', 5, 1, 1, NOW()), + +(1100, '文件管理', 1000, 2, '/system/file', 'SystemFile', 'system/file/index', NULL, 'file', false, false, false, NULL, 7, 1, 1, NOW()), +(1101, '列表', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:list', 1, 1, 1, NOW()), +(1102, '详情', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:detail', 2, 1, 1, NOW()), +(1103, '上传', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:upload', 3, 1, 1, NOW()), +(1104, '修改', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:update', 4, 1, 1, NOW()), +(1105, '删除', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:delete', 5, 1, 1, NOW()), +(1106, '下载', 1100, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:download', 6, 1, 1, NOW()), + +(1110, '存储管理', 1000, 2, '/system/storage', 'SystemStorage', 'system/storage/index', NULL, 'storage', false, false, false, NULL, 8, 1, 1, NOW()), +(1111, '列表', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:list', 1, 1, 1, NOW()), +(1112, '详情', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:detail', 2, 1, 1, NOW()), +(1113, '新增', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:add', 3, 1, 1, NOW()), +(1114, '修改', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:update', 4, 1, 1, NOW()), +(1115, '删除', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:delete', 5, 1, 1, NOW()), +(1116, '修改状态', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:updateStatus', 6, 1, 1, NOW()), +(1117, '设为默认存储', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:storage:setDefault', 7, 1, 1, NOW()), + +( 1180, '终端管理', 1000, 2, '/system/client', 'SystemClient', 'system/client/index', NULL, 'mobile', false, false, false, NULL, 9, 1, 1, NOW()), +(1181, '列表', 1180, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:list', 1, 1, 1, NOW()), +(1182, '详情', 1180, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:detail', 2, 1, 1, NOW()), +(1183, '新增', 1180, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:add', 3, 1, 1, NOW()), +(1184, '修改', 1180, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:update', 4, 1, 1, NOW()), +(1185, '删除', 1180, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:delete', 5, 1, 1, NOW()), + +(1190, '系统配置', 1000, 2, '/system/config', 'SystemConfig', 'system/config/index', NULL, 'config', false, false, false, NULL, 999, 1, 1, NOW()), +(1191, '查看', 1190, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:config:list', 1, 1, 1, NOW()), +(1192, '修改', 1190, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:config:update', 2, 1, 1, NOW()), +(1193, '重置', 1190, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:config:reset', 3, 1, 1, NOW()), + +(2000, '系统监控', 0, 1, '/monitor', 'Monitor', 'Layout', '/monitor/online', 'computer', false, false, false, NULL, 2, 1, 1, NOW()), +(2010, '在线用户', 2000, 2, '/monitor/online', 'MonitorOnline', 'monitor/online/index', NULL, 'user', false, false, false, NULL, 1, 1, 1, NOW()), +(2011, '列表', 2010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'monitor:online:list', 1, 1, 1, NOW()), +(2012, '强退', 2010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'monitor:online:kickout', 2, 1, 1, NOW()), + +(2020, '系统日志', 2000, 2, '/monitor/log', 'MonitorLog', 'monitor/log/index', NULL, 'history', false, false, false, NULL, 2, 1, 1, NOW()), +(2021, '列表', 2020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'monitor:log:list', 1, 1, 1, NOW()), +(2022, '详情', 2020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'monitor:log:detail', 2, 1, 1, NOW()), +(2023, '导出', 2020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'monitor:log:export', 3, 1, 1, NOW()); + +-- 初始化默认部门 +INSERT INTO "sys_dept" +("id", "name", "parent_id", "ancestors", "description", "sort", "status", "is_system", "create_user", "create_time") +VALUES +(1, 'Xxx科技有限公司', 0, '0', '系统初始部门', 1, 1, true, 1, NOW()), +(547887852587843590, 'Xxx(天津)科技有限公司', 1, '0,1', NULL, 1, 1, false, 1, NOW()), +(547887852587843591, '研发部', 547887852587843590, '0,1,547887852587843590', NULL, 1, 1, false, 1, NOW()), +(547887852587843592, 'UI部', 547887852587843590, '0,1,547887852587843590', NULL, 2, 1, false, 1, NOW()), +(547887852587843593, '测试部', 547887852587843590, '0,1,547887852587843590', NULL, 3, 1, false, 1, NOW()), +(547887852587843594, '运维部', 547887852587843590, '0,1,547887852587843590', NULL, 4, 1, false, 1, NOW()), +(547887852587843595, '研发一组', 547887852587843591, '0,1,547887852587843590,547887852587843591', NULL, 1, 1, false, 1, NOW()), +(547887852587843596, '研发二组', 547887852587843591, '0,1,547887852587843590,547887852587843591', NULL, 2, 2, false, 1, NOW()), + +(547887852587843597, 'Xxx(四川)科技有限公司', 1, '0,1', NULL, 2, 1, false, 1, NOW()), +(547887852587843598, '研发部', 547887852587843597, '0,1,547887852587843597', NULL, 1, 1, false, 1, NOW()), +(547887852587843599, '研发一组', 547887852587843598, '0,1,547887852587843597,547887852587843598', NULL, 1, 1, false, 1, NOW()), + +(547887852587843600, 'Xxx(江西)科技有限公司', 1, '0,1', NULL, 3, 1, false, 1, NOW()), +(547887852587843601, '研发部', 547887852587843600, '0,1,547887852587843600', NULL, 1, 1, false, 1, NOW()), +(547887852587843602, '研发一组', 547887852587843601, '0,1,547887852587843600,547887852587843601', NULL, 1, 1, false, 1, NOW()), + +(547887852587843603, 'Xxx(江苏)科技有限公司', 1, '0,1', NULL, 4, 1, false, 1, NOW()), +(547887852587843604, '研发部', 547887852587843603, '0,1,547887852587843603', NULL, 1, 1, false, 1, NOW()), +(547887852587843605, '研发一组', 547887852587843604, '0,1,547887852587843603,547887852587843604', NULL, 1, 1, false, 1, NOW()), + +(547887852587843606, 'Xxx(浙江)科技有限公司', 1, '0,1', NULL, 5, 1, false, 1, NOW()), +(547887852587843607, '研发部', 547887852587843606, '0,1,547887852587843606', NULL, 1, 1, false, 1, NOW()), +(547887852587843608, '研发一组', 547887852587843607, '0,1,547887852587843606,547887852587843607', NULL, 1, 1, false, 1, NOW()), + +(547887852587843609, 'Xxx(湖南)科技有限公司', 1, '0,1', NULL, 6, 1, false, 1, NOW()), +(547887852587843610, '研发部', 547887852587843609, '0,1,547887852587843609', NULL, 1, 1, false, 1, NOW()), +(547887852587843611, '研发一组', 547887852587843610, '0,1,547887852587843609,547887852587843610', NULL, 1, 1, false, 1, NOW()); + +-- 初始化默认角色 +INSERT INTO "sys_role" +("id", "name", "code", "data_scope", "description", "sort", "is_system", "create_user", "create_time") +VALUES +(1, '系统管理员', 'admin', 1, '系统初始角色', 1, true, 1, NOW()), +(547888897925840927, '测试人员', 'tester', 5, NULL, 2, false, 1, NOW()), +(547888897925840928, '研发人员', 'developer', 4, NULL, 3, false, 1, NOW()); + +-- 初始化默认用户:admin/admin123;test/test123 +INSERT INTO "sys_user" +("id", "username", "nickname", "password", "gender", "email", "phone", "avatar", "description", "status", "is_system", "pwd_reset_time", "dept_id", "create_user", "create_time") +VALUES +(1, 'admin', '系统管理员', '{bcrypt}$2a$10$4jGwK2BMJ7FgVR.mgwGodey8.xR8FLoU1XSXpxJ9nZQt.pufhasSa', 1, '42190c6c5639d2ca4edb4150a35e058559ccf8270361a23745a2fd285a273c28', '5bda89a4609a65546422ea56bfe5eab4', NULL, '系统初始用户', 1, true, NOW(), 1, 1, NOW()), +(547889293968801822, 'test', '测试员', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 2, NULL, NULL, NULL, NULL, 1, false, NOW(), 547887852587843593, 1, NOW()), +(547889293968801823, 'Charles', 'Charles', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '代码写到极致,就是艺术。', 1, false, NOW(), 547887852587843595, 1, NOW()), +(547889293968801824, 'Yoofff', 'Yoofff', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '弱小和无知不是生存的障碍,傲慢才是。', 1, false, NOW(), 1, 1, NOW()), +(547889293968801825, 'Jasmine', 'Jasmine', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '干就完事了!', 1, false, NOW(), 547887852587843605, 1, NOW()), +(547889293968801826, 'AutumnSail', '秋登', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '只有追求完美,才能创造奇迹。', 1, false, NOW(), 547887852587843602, 1, NOW()), +(547889293968801827, 'Kils', 'Kils', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '可以摆烂,但不能真的菜。', 1, false, NOW(), 547887852587843599, 1, NOW()), +(547889293968801828, 'mochou', '莫愁', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '万事莫愁,皆得所愿。', 1, false, NOW(), 547887852587843602, 1, NOW()), +(547889293968801829, 'Jing', 'MS-Jing', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '路虽远,行则将至。', 2, false, NOW(), 547887852587843599, 1, NOW()), +(547889293968801830, 'domw', '梓陌', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '胜利是奖赏,挫折是常态。', 1, false, NOW(), 547887852587843608, 1, NOW()), +(547889293968801831, 'xtanyu', '小熊', '{bcrypt}$2a$10$xAsoeMJ.jc/kSxhviLAg7.j2iFrhi6yYAdniNdjLiIUWU/BRZl2Ti', 1, NULL, NULL, NULL, '不想上班。', 1, false, NOW(), 547887852587843611, 1, NOW()); + +-- 初始化默认参数 +INSERT INTO "sys_option" +("id", "category", "name", "code", "value", "default_value", "description") +VALUES +(1, 'SITE', '系统名称', 'SITE_TITLE', NULL, 'ContiNew Admin', '显示在浏览器标题栏和登录界面的系统名称'), +(2, 'SITE', '系统描述', 'SITE_DESCRIPTION', NULL, '持续迭代优化的前后端分离中后台管理系统框架', '用于 SEO 的网站元描述'), +(3, 'SITE', '版权声明', 'SITE_COPYRIGHT', NULL, 'Copyright © 2022 - present ContiNew Admin 版权所有', '显示在页面底部的版权声明文本'), +(4, 'SITE', '备案号', 'SITE_BEIAN', NULL, NULL, '工信部 ICP 备案编号(如:京ICP备12345678号)'), +(5, 'SITE', '系统图标', 'SITE_FAVICON', NULL, '/favicon.ico', '浏览器标签页显示的网站图标(建议 .ico 格式)'), +(6, 'SITE', '系统LOGO', 'SITE_LOGO', NULL, '/logo.svg', '显示在登录页面和系统导航栏的网站图标(建议 .svg 格式)'), +(10, 'PASSWORD', '密码错误锁定阈值', 'PASSWORD_ERROR_LOCK_COUNT', NULL, '5', '连续登录失败次数达到该值将锁定账号(0-10次,0表示禁用锁定)'), +(11, 'PASSWORD', '账号锁定时长(分钟)', 'PASSWORD_ERROR_LOCK_MINUTES', NULL, '5', '账号锁定后自动解锁的时间(1-1440分钟,即24小时)'), +(12, 'PASSWORD', '密码有效期(天)', 'PASSWORD_EXPIRATION_DAYS', NULL, '0', '密码强制修改周期(0-999天,0表示永不过期)'), +(13, 'PASSWORD', '密码到期提醒(天)', 'PASSWORD_EXPIRATION_WARNING_DAYS', NULL, '0', '密码过期前的提前提醒天数(0表示不提醒)'), +(14, 'PASSWORD', '历史密码重复校验次数', 'PASSWORD_REPETITION_TIMES', NULL, '3', '禁止使用最近 N 次的历史密码(3-32次)'), +(15, 'PASSWORD', '密码最小长度', 'PASSWORD_MIN_LENGTH', NULL, '8', '密码最小字符长度要求(8-32个字符)'), +(16, 'PASSWORD', '是否允许密码包含用户名', 'PASSWORD_ALLOW_CONTAIN_USERNAME', NULL, '1', '是否允许密码包含正序或倒序的用户名字符'), +(17, 'PASSWORD', '密码是否必须包含特殊字符', 'PASSWORD_REQUIRE_SYMBOLS', NULL, '0', '是否要求密码必须包含特殊字符(如:!@#$%)'), +(20, 'MAIL', '邮件协议', 'MAIL_PROTOCOL', NULL, 'smtp', '邮件发送协议类型'), +(21, 'MAIL', '服务器地址', 'MAIL_HOST', NULL, 'smtp.126.com', '邮件服务器地址'), +(22, 'MAIL', '服务器端口', 'MAIL_PORT', NULL, '465', '邮件服务器连接端口'), +(23, 'MAIL', '邮箱账号', 'MAIL_USERNAME', NULL, 'charles7c@126.com', '发件人邮箱地址'), +(24, 'MAIL', '邮箱密码', 'MAIL_PASSWORD', NULL, NULL, '服务授权密码/客户端专用密码'), +(25, 'MAIL', '启用SSL加密', 'MAIL_SSL_ENABLED', NULL, '1', '是否启用SSL/TLS加密连接'), +(26, 'MAIL', 'SSL端口号', 'MAIL_SSL_PORT', NULL, '465', 'SSL加密连接的备用端口(通常与主端口一致)'), +(27, 'LOGIN', '是否启用验证码', 'LOGIN_CAPTCHA_ENABLED', NULL, '1', NULL); + +-- 初始化默认字典 +INSERT INTO "sys_dict" +("id", "name", "code", "description", "is_system", "create_user", "create_time") +VALUES +(1, '公告类型', 'notice_type', NULL, true, 1, NOW()), +(2, '消息类型', 'message_type', NULL, true, 1, NOW()), +(3, '终端类型', 'client_type', NULL, true, 1, NOW()); + +INSERT INTO "sys_dict_item" +("id", "label", "value", "color", "sort", "description", "status", "dict_id", "create_user", "create_time") +VALUES +(1, '通知', '1', 'blue', 1, NULL, 1, 1, 1, NOW()), +(2, '活动', '2', 'orangered', 2, NULL, 1, 1, 1, NOW()), +(3, '安全消息', '1', 'blue', 1, NULL, 1, 2, 1, NOW()), +(4, '活动消息', '2', 'orangered', 2, NULL, 1, 2, 1, NOW()), +(5, '桌面端', 'PC', 'blue', 1, NULL, 1, 3, 1, NOW()), +(6, '安卓', 'ANDROID', '#148628', 2, NULL, 1, 3, 1, NOW()), +(7, '小程序', 'XCX', '#7930AD', 3, NULL, 1, 3, 1, NOW()); + +-- 初始化默认用户和角色关联数据 +INSERT INTO "sys_user_role" +("id", "user_id", "role_id") +VALUES +(1, 1, 1), +(2, 547889293968801822, 547888897925840927), +(3, 547889293968801823, 547888897925840928), +(4, 547889293968801824, 547888897925840928), +(5, 547889293968801825, 547888897925840928), +(6, 547889293968801826, 547888897925840928), +(7, 547889293968801827, 547888897925840928), +(8, 547889293968801828, 547888897925840928), +(9, 547889293968801829, 547888897925840928), +(10, 547889293968801830, 547888897925840928), +(11, 547889293968801831, 547888897925840928); + +-- 初始化默认角色和菜单关联数据 +INSERT INTO "sys_role_menu" +("role_id", "menu_id") +VALUES +(547888897925840927, 1000), +(547888897925840927, 1010), +(547888897925840927, 1011), +(547888897925840927, 1012), +(547888897925840927, 1013), +(547888897925840927, 1014), +(547888897925840928, 2000), +(547888897925840928, 2010), +(547888897925840928, 2011), +(547888897925840928, 2020), +(547888897925840928, 2021), +(547888897925840928, 2022), +(547888897925840928, 2023); + +-- 初始化默认角色和部门关联数据 +INSERT INTO "sys_role_dept" ("role_id", "dept_id") VALUES (547888897925840927, 547887852587843593); + +-- 初始化默认存储 +INSERT INTO "sys_storage" +("id", "name", "code", "type", "access_key", "secret_key", "endpoint", "bucket_name", "domain", "description", "is_default", "sort", "status", "create_user", "create_time") +VALUES +(1, '开发环境', 'local_dev', 1, NULL, NULL, NULL, 'C:/ysoft-admin/data/file/', 'http://localhost:8000/file', '本地存储', true, 1, 1, 1, NOW()), +(2, '生产环境', 'local_prod', 1, NULL, NULL, NULL, '../data/file/', 'http://api.ysoft.top/file', '本地存储', false, 2, 2, 1, NOW()); + +-- 初始化终端数据 +INSERT INTO "sys_client" +("id", "client_id", "client_key", "client_secret", "auth_type", "client_type", "active_timeout", "timeout", "status", "create_user", "create_time") +VALUES +(1, 'ef51c9a3e9046c4f2ea45142c8a8344a', 'pc', 'dd77ab1e353a027e0d60ce3b151e8642', '["ACCOUNT", "EMAIL", "PHONE", "SOCIAL"]', 'PC', 1800, 86400, 1, 1, NOW()); diff --git a/wms-webapi/src/main/resources/db/changelog/postgresql/main_table.sql b/wms-webapi/src/main/resources/db/changelog/postgresql/main_table.sql new file mode 100644 index 0000000..61655f1 --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/postgresql/main_table.sql @@ -0,0 +1,543 @@ +-- liquibase formatted sql + +-- changeset charles7c:1 +-- comment 初始化表结构 +CREATE TABLE IF NOT EXISTS "sys_menu" ( + "id" int8 NOT NULL, + "title" varchar(30) NOT NULL, + "parent_id" int8 NOT NULL DEFAULT 0, + "type" int2 NOT NULL DEFAULT 1, + "path" varchar(255) DEFAULT NULL, + "name" varchar(50) DEFAULT NULL, + "component" varchar(255) DEFAULT NULL, + "redirect" varchar(255) DEFAULT NULL, + "icon" varchar(50) DEFAULT NULL, + "is_external" bool DEFAULT false, + "is_cache" bool DEFAULT false, + "is_hidden" bool DEFAULT false, + "permission" varchar(100) DEFAULT NULL, + "sort" int4 NOT NULL DEFAULT 999, + "status" int2 NOT NULL DEFAULT 1, + "create_user" int8 NOT NULL, + "create_time" timestamp NOT NULL, + "update_user" int8 DEFAULT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "idx_menu_parent_id" ON "sys_menu" ("parent_id"); +CREATE INDEX "idx_menu_create_user" ON "sys_menu" ("create_user"); +CREATE INDEX "idx_menu_update_user" ON "sys_menu" ("update_user"); +CREATE UNIQUE INDEX "uk_menu_title_parent_id" ON "sys_menu" ("title", "parent_id"); +COMMENT ON COLUMN "sys_menu"."id" IS 'ID'; +COMMENT ON COLUMN "sys_menu"."title" IS '标题'; +COMMENT ON COLUMN "sys_menu"."parent_id" IS '上级菜单ID'; +COMMENT ON COLUMN "sys_menu"."type" IS '类型(1:目录;2:菜单;3:按钮)'; +COMMENT ON COLUMN "sys_menu"."path" IS '路由地址'; +COMMENT ON COLUMN "sys_menu"."name" IS '组件名称'; +COMMENT ON COLUMN "sys_menu"."component" IS '组件路径'; +COMMENT ON COLUMN "sys_menu"."redirect" IS '重定向地址'; +COMMENT ON COLUMN "sys_menu"."icon" IS '图标'; +COMMENT ON COLUMN "sys_menu"."is_external" IS '是否外链'; +COMMENT ON COLUMN "sys_menu"."is_cache" IS '是否缓存'; +COMMENT ON COLUMN "sys_menu"."is_hidden" IS '是否隐藏'; +COMMENT ON COLUMN "sys_menu"."permission" IS '权限标识'; +COMMENT ON COLUMN "sys_menu"."sort" IS '排序'; +COMMENT ON COLUMN "sys_menu"."status" IS '状态(1:启用;2:禁用)'; +COMMENT ON COLUMN "sys_menu"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_menu"."create_time" IS '创建时间'; +COMMENT ON COLUMN "sys_menu"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_menu"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_menu" IS '菜单表'; + +CREATE TABLE IF NOT EXISTS "sys_dept" ( + "id" int8 NOT NULL, + "name" varchar(30) NOT NULL, + "parent_id" int8 NOT NULL DEFAULT 0, + "ancestors" varchar(512) NOT NULL DEFAULT '', + "description" varchar(200) DEFAULT NULL, + "sort" int4 NOT NULL DEFAULT 999, + "status" int2 NOT NULL DEFAULT 1, + "is_system" bool NOT NULL DEFAULT false, + "create_user" int8 NOT NULL, + "create_time" timestamp NOT NULL, + "update_user" int8 DEFAULT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "idx_dept_parent_id" ON "sys_dept" ("parent_id"); +CREATE INDEX "idx_dept_create_user" ON "sys_dept" ("create_user"); +CREATE INDEX "idx_dept_update_user" ON "sys_dept" ("update_user"); +CREATE UNIQUE INDEX "uk_dept_name_parent_id" ON "sys_dept" ("name", "parent_id"); +COMMENT ON COLUMN "sys_dept"."id" IS 'ID'; +COMMENT ON COLUMN "sys_dept"."name" IS '名称'; +COMMENT ON COLUMN "sys_dept"."parent_id" IS '上级部门ID'; +COMMENT ON COLUMN "sys_dept"."ancestors" IS '祖级列表'; +COMMENT ON COLUMN "sys_dept"."description" IS '描述'; +COMMENT ON COLUMN "sys_dept"."sort" IS '排序'; +COMMENT ON COLUMN "sys_dept"."status" IS '状态(1:启用;2:禁用)'; +COMMENT ON COLUMN "sys_dept"."is_system" IS '是否为系统内置数据'; +COMMENT ON COLUMN "sys_dept"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_dept"."create_time" IS '创建时间'; +COMMENT ON COLUMN "sys_dept"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_dept"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_dept" IS '部门表'; + +CREATE TABLE IF NOT EXISTS "sys_role" ( + "id" int8 NOT NULL, + "name" varchar(30) NOT NULL, + "code" varchar(30) NOT NULL, + "data_scope" int2 NOT NULL DEFAULT 4, + "description" varchar(200) DEFAULT NULL, + "sort" int4 NOT NULL DEFAULT 999, + "is_system" bool NOT NULL DEFAULT false, + "menu_check_strictly" bool DEFAULT false, + "dept_check_strictly" bool DEFAULT false, + "create_user" int8 NOT NULL, + "create_time" timestamp NOT NULL, + "update_user" int8 DEFAULT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "uk_role_name" ON "sys_role" ("name"); +CREATE UNIQUE INDEX "uk_role_code" ON "sys_role" ("code"); +CREATE INDEX "idx_role_create_user" ON "sys_role" ("create_user"); +CREATE INDEX "idx_role_update_user" ON "sys_role" ("update_user"); +COMMENT ON COLUMN "sys_role"."id" IS 'ID'; +COMMENT ON COLUMN "sys_role"."name" IS '名称'; +COMMENT ON COLUMN "sys_role"."code" IS '编码'; +COMMENT ON COLUMN "sys_role"."data_scope" IS '数据权限(1:全部数据权限;2:本部门及以下数据权限;3:本部门数据权限;4:仅本人数据权限;5:自定义数据权限)'; +COMMENT ON COLUMN "sys_role"."description" IS '描述'; +COMMENT ON COLUMN "sys_role"."sort" IS '排序'; +COMMENT ON COLUMN "sys_role"."is_system" IS '是否为系统内置数据'; +COMMENT ON COLUMN "sys_role"."menu_check_strictly" IS '菜单选择是否父子节点关联'; +COMMENT ON COLUMN "sys_role"."dept_check_strictly" IS '部门选择是否父子节点关联'; +COMMENT ON COLUMN "sys_role"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_role"."create_time" IS '创建时间'; +COMMENT ON COLUMN "sys_role"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_role"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_role" IS '角色表'; + +CREATE TABLE IF NOT EXISTS "sys_user" ( + "id" int8 NOT NULL, + "username" varchar(64) NOT NULL, + "nickname" varchar(30) NOT NULL, + "password" varchar(255) DEFAULT NULL, + "gender" int2 NOT NULL DEFAULT 0, + "email" varchar(255) DEFAULT NULL, + "phone" varchar(255) DEFAULT NULL, + "avatar" text DEFAULT NULL, + "description" varchar(200) DEFAULT NULL, + "status" int2 NOT NULL DEFAULT 1, + "is_system" bool NOT NULL DEFAULT false, + "pwd_reset_time" timestamp DEFAULT NULL, + "dept_id" int8 NOT NULL, + "create_user" int8 DEFAULT NULL, + "create_time" timestamp NOT NULL, + "update_user" int8 DEFAULT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "uk_user_username" ON "sys_user" ("username"); +CREATE UNIQUE INDEX "uk_user_email" ON "sys_user" ("email"); +CREATE UNIQUE INDEX "uk_user_phone" ON "sys_user" ("phone"); +CREATE INDEX "idx_user_dept_id" ON "sys_user" ("dept_id"); +CREATE INDEX "idx_user_create_user" ON "sys_user" ("create_user"); +CREATE INDEX "idx_user_update_user" ON "sys_user" ("update_user"); +COMMENT ON COLUMN "sys_user"."id" IS 'ID'; +COMMENT ON COLUMN "sys_user"."username" IS '用户名'; +COMMENT ON COLUMN "sys_user"."nickname" IS '昵称'; +COMMENT ON COLUMN "sys_user"."password" IS '密码'; +COMMENT ON COLUMN "sys_user"."gender" IS '性别(0:未知;1:男;2:女)'; +COMMENT ON COLUMN "sys_user"."email" IS '邮箱'; +COMMENT ON COLUMN "sys_user"."phone" IS '手机号码'; +COMMENT ON COLUMN "sys_user"."avatar" IS '头像'; +COMMENT ON COLUMN "sys_user"."description" IS '描述'; +COMMENT ON COLUMN "sys_user"."status" IS '状态(1:启用;2:禁用)'; +COMMENT ON COLUMN "sys_user"."is_system" IS '是否为系统内置数据'; +COMMENT ON COLUMN "sys_user"."pwd_reset_time" IS '最后一次修改密码时间'; +COMMENT ON COLUMN "sys_user"."dept_id" IS '部门ID'; +COMMENT ON COLUMN "sys_user"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_user"."create_time" IS '创建时间'; +COMMENT ON COLUMN "sys_user"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_user"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_user" IS '用户表'; + +CREATE TABLE IF NOT EXISTS "sys_user_password_history" ( + "id" int8 NOT NULL, + "user_id" int8 NOT NULL, + "password" varchar(255) NOT NULL, + "create_time" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "idx_uph_user_id" ON "sys_user_password_history" ("user_id"); +COMMENT ON COLUMN "sys_user_password_history"."id" IS 'ID'; +COMMENT ON COLUMN "sys_user_password_history"."user_id" IS '用户ID'; +COMMENT ON COLUMN "sys_user_password_history"."password" IS '密码'; +COMMENT ON COLUMN "sys_user_password_history"."create_time" IS '创建时间'; +COMMENT ON TABLE "sys_user_password_history" IS '用户历史密码表'; + +CREATE TABLE IF NOT EXISTS "sys_user_social" ( + "id" int8 NOT NULL, + "source" varchar(255) NOT NULL, + "open_id" varchar(255) NOT NULL, + "user_id" int8 NOT NULL, + "meta_json" text DEFAULT NULL, + "last_login_time" timestamp DEFAULT NULL, + "create_time" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "uk_user_source_open_id" ON "sys_user_social" ("source", "open_id"); +COMMENT ON COLUMN "sys_user_social"."id" IS 'ID'; +COMMENT ON COLUMN "sys_user_social"."source" IS '来源'; +COMMENT ON COLUMN "sys_user_social"."open_id" IS '开放ID'; +COMMENT ON COLUMN "sys_user_social"."user_id" IS '用户ID'; +COMMENT ON COLUMN "sys_user_social"."meta_json" IS '附加信息'; +COMMENT ON COLUMN "sys_user_social"."last_login_time" IS '最后登录时间'; +COMMENT ON COLUMN "sys_user_social"."create_time" IS '创建时间'; +COMMENT ON TABLE "sys_user_social" IS '用户社会化关联表'; + +CREATE TABLE IF NOT EXISTS "sys_user_role" ( + "id" int8 NOT NULL, + "user_id" int8 NOT NULL, + "role_id" int8 NOT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "uk_user_id_role_id" ON "sys_user_role" ("user_id", "role_id"); +COMMENT ON COLUMN "sys_user_role"."id" IS 'ID'; +COMMENT ON COLUMN "sys_user_role"."user_id" IS '用户ID'; +COMMENT ON COLUMN "sys_user_role"."role_id" IS '角色ID'; +COMMENT ON TABLE "sys_user_role" IS '用户和角色关联表'; + +CREATE TABLE IF NOT EXISTS "sys_role_menu" ( + "role_id" int8 NOT NULL, + "menu_id" int8 NOT NULL, + PRIMARY KEY ("role_id", "menu_id") +); +COMMENT ON COLUMN "sys_role_menu"."role_id" IS '角色ID'; +COMMENT ON COLUMN "sys_role_menu"."menu_id" IS '菜单ID'; +COMMENT ON TABLE "sys_role_menu" IS '角色和菜单关联表'; + +CREATE TABLE IF NOT EXISTS "sys_role_dept" ( + "role_id" int8 NOT NULL, + "dept_id" int8 NOT NULL, + PRIMARY KEY ("role_id", "dept_id") +); +COMMENT ON COLUMN "sys_role_dept"."role_id" IS '角色ID'; +COMMENT ON COLUMN "sys_role_dept"."dept_id" IS '部门ID'; +COMMENT ON TABLE "sys_role_dept" IS '角色和部门关联表'; + +CREATE TABLE IF NOT EXISTS "sys_option" ( + "id" int8 NOT NULL, + "category" varchar(50) NOT NULL, + "name" varchar(50) NOT NULL, + "code" varchar(100) NOT NULL, + "value" text DEFAULT NULL, + "default_value" text DEFAULT NULL, + "description" varchar(200) DEFAULT NULL, + "update_user" int8 DEFAULT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "uk_option_category_code" ON "sys_option" ("category", "code"); +COMMENT ON COLUMN "sys_option"."id" IS 'ID'; +COMMENT ON COLUMN "sys_option"."category" IS '类别'; +COMMENT ON COLUMN "sys_option"."name" IS '名称'; +COMMENT ON COLUMN "sys_option"."code" IS '键'; +COMMENT ON COLUMN "sys_option"."value" IS '值'; +COMMENT ON COLUMN "sys_option"."default_value" IS '默认值'; +COMMENT ON COLUMN "sys_option"."description" IS '描述'; +COMMENT ON COLUMN "sys_option"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_option"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_option" IS '参数表'; + +CREATE TABLE IF NOT EXISTS "sys_dict" ( + "id" int8 NOT NULL, + "name" varchar(30) NOT NULL, + "code" varchar(30) NOT NULL, + "description" varchar(200) DEFAULT NULL, + "is_system" bool NOT NULL DEFAULT false, + "create_user" int8 NOT NULL, + "create_time" timestamp NOT NULL, + "update_user" int8 DEFAULT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "uk_dict_name" ON "sys_dict" ("name"); +CREATE UNIQUE INDEX "uk_dict_code" ON "sys_dict" ("code"); +COMMENT ON COLUMN "sys_dict"."id" IS 'ID'; +COMMENT ON COLUMN "sys_dict"."name" IS '名称'; +COMMENT ON COLUMN "sys_dict"."code" IS '编码'; +COMMENT ON COLUMN "sys_dict"."description" IS '描述'; +COMMENT ON COLUMN "sys_dict"."is_system" IS '是否为系统内置数据'; +COMMENT ON COLUMN "sys_dict"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_dict"."create_time" IS '创建时间'; +COMMENT ON COLUMN "sys_dict"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_dict"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_dict" IS '字典表'; + +CREATE TABLE IF NOT EXISTS "sys_dict_item" ( + "id" int8 NOT NULL, + "label" varchar(30) NOT NULL, + "value" varchar(30) NOT NULL, + "color" varchar(30) DEFAULT NULL, + "sort" int4 NOT NULL DEFAULT 999, + "description" varchar(200) DEFAULT NULL, + "status" int2 NOT NULL DEFAULT 1, + "dict_id" int8 NOT NULL, + "create_user" int8 NOT NULL, + "create_time" timestamp NOT NULL, + "update_user" int8 DEFAULT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "uk_dict_item_value_dict_id" ON "sys_dict_item" ("value", "dict_id"); +CREATE INDEX "idx_dict_item_dict_id" ON "sys_dict_item" ("dict_id"); +CREATE INDEX "idx_dict_item_create_user" ON "sys_dict_item" ("create_user"); +CREATE INDEX "idx_dict_item_update_user" ON "sys_dict_item" ("update_user"); +COMMENT ON COLUMN "sys_dict_item"."id" IS 'ID'; +COMMENT ON COLUMN "sys_dict_item"."label" IS '标签'; +COMMENT ON COLUMN "sys_dict_item"."value" IS '值'; +COMMENT ON COLUMN "sys_dict_item"."color" IS '标签颜色'; +COMMENT ON COLUMN "sys_dict_item"."sort" IS '排序'; +COMMENT ON COLUMN "sys_dict_item"."description" IS '描述'; +COMMENT ON COLUMN "sys_dict_item"."status" IS '状态(1:启用;2:禁用)'; +COMMENT ON COLUMN "sys_dict_item"."dict_id" IS '字典ID'; +COMMENT ON COLUMN "sys_dict_item"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_dict_item"."create_time" IS '创建时间'; +COMMENT ON COLUMN "sys_dict_item"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_dict_item"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_dict_item" IS '字典项表'; + +CREATE TABLE IF NOT EXISTS "sys_log" ( + "id" int8 NOT NULL, + "trace_id" varchar(255) DEFAULT NULL, + "description" varchar(255) NOT NULL, + "module" varchar(100) NOT NULL, + "request_url" varchar(512) NOT NULL, + "request_method" varchar(10) NOT NULL, + "request_headers" text DEFAULT NULL, + "request_body" text DEFAULT NULL, + "status_code" int4 NOT NULL, + "response_headers" text DEFAULT NULL, + "response_body" text DEFAULT NULL, + "time_taken" int8 NOT NULL, + "ip" varchar(100) DEFAULT NULL, + "address" varchar(255) DEFAULT NULL, + "browser" varchar(100) DEFAULT NULL, + "os" varchar(100) DEFAULT NULL, + "status" int2 NOT NULL DEFAULT 1, + "error_msg" text DEFAULT NULL, + "create_user" int8 DEFAULT NULL, + "create_time" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "idx_log_module" ON "sys_log" ("module"); +CREATE INDEX "idx_log_ip" ON "sys_log" ("ip"); +CREATE INDEX "idx_log_address" ON "sys_log" ("address"); +CREATE INDEX "idx_log_create_time" ON "sys_log" ("create_time"); +COMMENT ON COLUMN "sys_log"."id" IS 'ID'; +COMMENT ON COLUMN "sys_log"."trace_id" IS '链路ID'; +COMMENT ON COLUMN "sys_log"."description" IS '日志描述'; +COMMENT ON COLUMN "sys_log"."module" IS '所属模块'; +COMMENT ON COLUMN "sys_log"."request_url" IS '请求URL'; +COMMENT ON COLUMN "sys_log"."request_method" IS '请求方式'; +COMMENT ON COLUMN "sys_log"."request_headers" IS '请求头'; +COMMENT ON COLUMN "sys_log"."request_body" IS '请求体'; +COMMENT ON COLUMN "sys_log"."status_code" IS '状态码'; +COMMENT ON COLUMN "sys_log"."response_headers" IS '响应头'; +COMMENT ON COLUMN "sys_log"."response_body" IS '响应体'; +COMMENT ON COLUMN "sys_log"."time_taken" IS '耗时(ms)'; +COMMENT ON COLUMN "sys_log"."ip" IS 'IP'; +COMMENT ON COLUMN "sys_log"."address" IS 'IP归属地'; +COMMENT ON COLUMN "sys_log"."browser" IS '浏览器'; +COMMENT ON COLUMN "sys_log"."os" IS '操作系统'; +COMMENT ON COLUMN "sys_log"."status" IS '状态(1:成功;2:失败)'; +COMMENT ON COLUMN "sys_log"."error_msg" IS '错误信息'; +COMMENT ON COLUMN "sys_log"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_log"."create_time" IS '创建时间'; +COMMENT ON TABLE "sys_log" IS '系统日志表'; + +CREATE TABLE IF NOT EXISTS "sys_message" ( + "id" int8 NOT NULL, + "title" varchar(50) NOT NULL, + "content" varchar(255) DEFAULT NULL, + "type" int2 NOT NULL DEFAULT 1, + "create_user" int8 DEFAULT NULL, + "create_time" timestamp NOT NULL, + PRIMARY KEY ("id") +); +COMMENT ON COLUMN "sys_message"."id" IS 'ID'; +COMMENT ON COLUMN "sys_message"."title" IS '标题'; +COMMENT ON COLUMN "sys_message"."content" IS '内容'; +COMMENT ON COLUMN "sys_message"."type" IS '类型(1:系统消息)'; +COMMENT ON COLUMN "sys_message"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_message"."create_time" IS '创建时间'; +COMMENT ON TABLE "sys_message" IS '消息表'; + +CREATE TABLE IF NOT EXISTS "sys_message_user" ( + "message_id" int8 NOT NULL, + "user_id" int8 NOT NULL, + "is_read" bool NOT NULL DEFAULT false, + "read_time" timestamp DEFAULT NULL, + PRIMARY KEY ("message_id", "user_id") +); +COMMENT ON COLUMN "sys_message_user"."message_id" IS '消息ID'; +COMMENT ON COLUMN "sys_message_user"."user_id" IS '用户ID'; +COMMENT ON COLUMN "sys_message_user"."is_read" IS '是否已读'; +COMMENT ON COLUMN "sys_message_user"."read_time" IS '读取时间'; +COMMENT ON TABLE "sys_message_user" IS '消息和用户关联表'; + +CREATE TABLE IF NOT EXISTS "sys_notice" ( + "id" int8 NOT NULL, + "title" varchar(150) NOT NULL, + "content" text NOT NULL, + "type" varchar(30) NOT NULL, + "effective_time" timestamp DEFAULT NULL, + "terminate_time" timestamp DEFAULT NULL, + "notice_scope" int2 NOT NULL DEFAULT 1, + "notice_users" json DEFAULT NULL, + "sort" int4 NOT NULL DEFAULT 999, + "create_user" int8 NOT NULL, + "create_time" timestamp NOT NULL, + "update_user" int8 DEFAULT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "idx_notice_create_user" ON "sys_notice" ("create_user"); +CREATE INDEX "idx_notice_update_user" ON "sys_notice" ("update_user"); +COMMENT ON COLUMN "sys_notice"."id" IS 'ID'; +COMMENT ON COLUMN "sys_notice"."title" IS '标题'; +COMMENT ON COLUMN "sys_notice"."content" IS '内容'; +COMMENT ON COLUMN "sys_notice"."type" IS '类型'; +COMMENT ON COLUMN "sys_notice"."effective_time" IS '生效时间'; +COMMENT ON COLUMN "sys_notice"."terminate_time" IS '终止时间'; +COMMENT ON COLUMN "sys_notice"."notice_scope" IS '通知范围(1:所有人;2:指定用户)'; +COMMENT ON COLUMN "sys_notice"."notice_users" IS '通知用户'; +COMMENT ON COLUMN "sys_notice"."sort" IS '排序'; +COMMENT ON COLUMN "sys_notice"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_notice"."create_time" IS '创建时间'; +COMMENT ON COLUMN "sys_notice"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_notice"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_notice" IS '公告表'; + +CREATE TABLE IF NOT EXISTS "sys_storage" ( + "id" int8 NOT NULL, + "name" varchar(100) NOT NULL, + "code" varchar(30) NOT NULL, + "type" int2 NOT NULL DEFAULT 1, + "access_key" varchar(255) DEFAULT NULL, + "secret_key" varchar(255) DEFAULT NULL, + "endpoint" varchar(255) DEFAULT NULL, + "bucket_name" varchar(255) DEFAULT NULL, + "domain" varchar(255) NOT NULL DEFAULT '', + "description" varchar(200) DEFAULT NULL, + "is_default" bool NOT NULL DEFAULT false, + "sort" int4 NOT NULL DEFAULT 999, + "status" int2 NOT NULL DEFAULT 1, + "create_user" int8 NOT NULL, + "create_time" timestamp NOT NULL, + "update_user" int8 DEFAULT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "uk_storage_code" ON "sys_storage" ("code"); +CREATE INDEX "idx_storage_create_user" ON "sys_storage" ("create_user"); +CREATE INDEX "idx_storage_update_user" ON "sys_storage" ("update_user"); +COMMENT ON COLUMN "sys_storage"."id" IS 'ID'; +COMMENT ON COLUMN "sys_storage"."name" IS '名称'; +COMMENT ON COLUMN "sys_storage"."code" IS '编码'; +COMMENT ON COLUMN "sys_storage"."type" IS '类型(1:本地存储;2:对象存储)'; +COMMENT ON COLUMN "sys_storage"."access_key" IS 'Access Key'; +COMMENT ON COLUMN "sys_storage"."secret_key" IS 'Secret Key'; +COMMENT ON COLUMN "sys_storage"."endpoint" IS 'Endpoint'; +COMMENT ON COLUMN "sys_storage"."bucket_name" IS 'Bucket'; +COMMENT ON COLUMN "sys_storage"."domain" IS '域名'; +COMMENT ON COLUMN "sys_storage"."description" IS '描述'; +COMMENT ON COLUMN "sys_storage"."is_default" IS '是否为默认存储'; +COMMENT ON COLUMN "sys_storage"."sort" IS '排序'; +COMMENT ON COLUMN "sys_storage"."status" IS '状态(1:启用;2:禁用)'; +COMMENT ON COLUMN "sys_storage"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_storage"."create_time" IS '创建时间'; +COMMENT ON COLUMN "sys_storage"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_storage"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_storage" IS '存储表'; + +CREATE TABLE IF NOT EXISTS "sys_file" ( + "id" int8 NOT NULL, + "name" varchar(255) NOT NULL, + "size" int8 NOT NULL, + "url" varchar(512) NOT NULL, + "parent_path" varchar(512) NOT NULL DEFAULT '/', + "abs_path" varchar(512) NOT NULL, + "extension" varchar(100) DEFAULT NULL, + "content_type" varchar(64) NOT NULL, + "type" int2 NOT NULL DEFAULT 1, + "md5" varchar(128) NOT NULL, + "metadata" text DEFAULT NULL, + "thumbnail_size" int8 DEFAULT NULL, + "thumbnail_url" varchar(512) DEFAULT NULL, + "thumbnail_metadata" text DEFAULT NULL, + "storage_id" int8 NOT NULL, + "create_user" int8 NOT NULL, + "create_time" timestamp NOT NULL, + "update_user" int8 NOT NULL, + "update_time" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "idx_file_url" ON "sys_file" ("url"); +CREATE INDEX "idx_file_type" ON "sys_file" ("type"); +CREATE INDEX "idx_file_md5" ON "sys_file" ("md5"); +CREATE INDEX "idx_file_create_user" ON "sys_file" ("create_user"); +CREATE INDEX "idx_file_update_user" ON "sys_file" ("update_user"); +COMMENT ON COLUMN "sys_file"."id" IS 'ID'; +COMMENT ON COLUMN "sys_file"."name" IS '名称'; +COMMENT ON COLUMN "sys_file"."size" IS '大小(字节)'; +COMMENT ON COLUMN "sys_file"."url" IS 'URL'; +COMMENT ON COLUMN "sys_file"."parent_path" IS '上级目录'; +COMMENT ON COLUMN "sys_file"."abs_path" IS '绝对路径'; +COMMENT ON COLUMN "sys_file"."extension" IS '扩展名'; +COMMENT ON COLUMN "sys_file"."content_type" IS '内容类型'; +COMMENT ON COLUMN "sys_file"."type" IS '类型(0: 目录;1:其他;2:图片;3:文档;4:视频;5:音频)'; +COMMENT ON COLUMN "sys_file"."md5" IS 'MD5值'; +COMMENT ON COLUMN "sys_file"."metadata" IS '元数据'; +COMMENT ON COLUMN "sys_file"."thumbnail_size" IS '缩略图大小(字节)'; +COMMENT ON COLUMN "sys_file"."thumbnail_url" IS '缩略图URL'; +COMMENT ON COLUMN "sys_file"."thumbnail_metadata" IS '缩略图元数据'; +COMMENT ON COLUMN "sys_file"."storage_id" IS '存储ID'; +COMMENT ON COLUMN "sys_file"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_file"."create_time" IS '创建时间'; +COMMENT ON COLUMN "sys_file"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_file"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_file" IS '文件表'; + +CREATE TABLE IF NOT EXISTS "sys_client" ( + "id" int8 NOT NULL, + "client_id" varchar(50) NOT NULL, + "client_key" varchar(255) NOT NULL, + "client_secret" varchar(255) NOT NULL, + "auth_type" json NOT NULL, + "client_type" varchar(50) NOT NULL, + "active_timeout" int8 NOT NULL DEFAULT -1, + "timeout" int8 NOT NULL DEFAULT 2592000, + "status" int2 NOT NULL DEFAULT 1, + "create_user" int8 NOT NULL, + "create_time" timestamp NOT NULL, + "update_user" int8 DEFAULT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "uk_client_client_id" ON "sys_client" ("client_id"); +COMMENT ON COLUMN "sys_client"."id" IS 'ID'; +COMMENT ON COLUMN "sys_client"."client_id" IS '终端ID'; +COMMENT ON COLUMN "sys_client"."client_key" IS '终端Key'; +COMMENT ON COLUMN "sys_client"."client_secret" IS '终端秘钥'; +COMMENT ON COLUMN "sys_client"."auth_type" IS '认证类型'; +COMMENT ON COLUMN "sys_client"."client_type" IS '终端类型'; +COMMENT ON COLUMN "sys_client"."active_timeout" IS 'Token最低活跃频率(单位:秒,-1:不限制,永不冻结)'; +COMMENT ON COLUMN "sys_client"."timeout" IS 'Token有效期(单位:秒,-1:永不过期)'; +COMMENT ON COLUMN "sys_client"."status" IS '状态(1:启用;2:禁用)'; +COMMENT ON COLUMN "sys_client"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_client"."create_time" IS '创建时间'; +COMMENT ON COLUMN "sys_client"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_client"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_client" IS '终端表'; \ No newline at end of file diff --git a/wms-webapi/src/main/resources/db/changelog/postgresql/plugin/plugin_generator.sql b/wms-webapi/src/main/resources/db/changelog/postgresql/plugin/plugin_generator.sql new file mode 100644 index 0000000..c7bc70c --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/postgresql/plugin/plugin_generator.sql @@ -0,0 +1,78 @@ +-- liquibase formatted sql + +-- changeset charles7c:1 +-- comment 初始化代码生成插件 +-- 初始化表结构 +CREATE TABLE IF NOT EXISTS "gen_config" ( + "table_name" varchar(64) NOT NULL, + "module_name" varchar(60) NOT NULL, + "package_name" varchar(60) NOT NULL, + "business_name" varchar(50) NOT NULL, + "author" varchar(100) NOT NULL, + "table_prefix" varchar(20) DEFAULT NULL, + "is_override" bool NOT NULL DEFAULT false, + "create_time" timestamp NOT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("table_name") +); +COMMENT ON COLUMN "gen_config"."table_name" IS '表名称'; +COMMENT ON COLUMN "gen_config"."module_name" IS '模块名称'; +COMMENT ON COLUMN "gen_config"."package_name" IS '包名称'; +COMMENT ON COLUMN "gen_config"."business_name" IS '业务名称'; +COMMENT ON COLUMN "gen_config"."author" IS '作者'; +COMMENT ON COLUMN "gen_config"."table_prefix" IS '表前缀'; +COMMENT ON COLUMN "gen_config"."is_override" IS '是否覆盖'; +COMMENT ON COLUMN "gen_config"."create_time" IS '创建时间'; +COMMENT ON COLUMN "gen_config"."update_time" IS '修改时间'; +COMMENT ON TABLE "gen_config" IS '生成配置表'; + +CREATE TABLE IF NOT EXISTS "gen_field_config" ( + "id" int8 NOT NULL, + "table_name" varchar(64) NOT NULL, + "column_name" varchar(64) NOT NULL, + "column_type" varchar(25) NOT NULL, + "column_size" int8 DEFAULT NULL, + "field_name" varchar(64) NOT NULL, + "field_type" varchar(25) NOT NULL, + "field_sort" int4 NOT NULL DEFAULT 999, + "comment" varchar(512) DEFAULT NULL, + "is_required" bool NOT NULL DEFAULT true, + "show_in_list" bool NOT NULL DEFAULT true, + "show_in_form" bool NOT NULL DEFAULT true, + "show_in_query" bool NOT NULL DEFAULT true, + "form_type" int2 DEFAULT NULL, + "query_type" int2 DEFAULT NULL, + "dict_code" varchar(30) DEFAULT NULL, + "create_time" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "idx_field_config_table_name" ON "gen_field_config" ("table_name"); +COMMENT ON COLUMN "gen_field_config"."id" IS 'ID'; +COMMENT ON COLUMN "gen_field_config"."table_name" IS '表名称'; +COMMENT ON COLUMN "gen_field_config"."column_name" IS '列名称'; +COMMENT ON COLUMN "gen_field_config"."column_type" IS '列类型'; +COMMENT ON COLUMN "gen_field_config"."column_size" IS '列大小'; +COMMENT ON COLUMN "gen_field_config"."field_name" IS '字段名称'; +COMMENT ON COLUMN "gen_field_config"."field_type" IS '字段类型'; +COMMENT ON COLUMN "gen_field_config"."field_sort" IS '字段排序'; +COMMENT ON COLUMN "gen_field_config"."comment" IS '注释'; +COMMENT ON COLUMN "gen_field_config"."is_required" IS '是否必填'; +COMMENT ON COLUMN "gen_field_config"."show_in_list" IS '是否在列表中显示'; +COMMENT ON COLUMN "gen_field_config"."show_in_form" IS '是否在表单中显示'; +COMMENT ON COLUMN "gen_field_config"."show_in_query" IS '是否在查询中显示'; +COMMENT ON COLUMN "gen_field_config"."form_type" IS '表单类型'; +COMMENT ON COLUMN "gen_field_config"."query_type" IS '查询方式'; +COMMENT ON COLUMN "gen_field_config"."dict_code" IS '字典编码'; +COMMENT ON COLUMN "gen_field_config"."create_time" IS '创建时间'; +COMMENT ON TABLE "gen_field_config" IS '字段配置表'; + +-- 初始化默认菜单 +INSERT INTO "sys_menu" +("id", "title", "parent_id", "type", "path", "name", "component", "redirect", "icon", "is_external", "is_cache", "is_hidden", "permission", "sort", "status", "create_user", "create_time") +VALUES +(9000, '代码生成', 0, 1, '/code', 'Code', 'Layout', '/code/generator', 'code-release-managment', false, false, false, NULL, 9, 1, 1, NOW()), +(9010, '代码生成', 9000, 2, '/code/generator', 'CodeGenerator', 'code/generator/index', NULL, 'code', false, false, false, NULL, 1, 1, 1, NOW()), +(9011, '列表', 9010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'code:generator:list', 1, 1, 1, NOW()), +(9012, '配置', 9010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'code:generator:config', 2, 1, 1, NOW()), +(9013, '预览', 9010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'code:generator:preview', 3, 1, 1, NOW()), +(9014, '生成', 9010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'code:generator:generate', 4, 1, 1, NOW()); \ No newline at end of file diff --git a/wms-webapi/src/main/resources/db/changelog/postgresql/plugin/plugin_open.sql b/wms-webapi/src/main/resources/db/changelog/postgresql/plugin/plugin_open.sql new file mode 100644 index 0000000..616d258 --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/postgresql/plugin/plugin_open.sql @@ -0,0 +1,49 @@ +-- liquibase formatted sql + +-- changeset chengzi:1 +-- comment 初始化能力开放插件 +-- 初始化表结构 +CREATE TABLE IF NOT EXISTS "sys_app" ( + "id" int8 NOT NULL, + "name" varchar(100) NOT NULL, + "access_key" varchar(255) NOT NULL, + "secret_key" varchar(255) NOT NULL, + "expire_time" timestamp DEFAULT NULL, + "description" varchar(200) DEFAULT NULL, + "status" int2 NOT NULL DEFAULT 1, + "create_user" int8 NOT NULL, + "create_time" timestamp NOT NULL, + "update_user" int8 DEFAULT NULL, + "update_time" timestamp DEFAULT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "uk_app_access_key" ON "sys_app" ("access_key"); +CREATE INDEX "idx_app_create_user" ON "sys_app" ("create_user"); +CREATE INDEX "idx_app_update_user" ON "sys_app" ("update_user"); +COMMENT ON COLUMN "sys_app"."id" IS 'ID'; +COMMENT ON COLUMN "sys_app"."name" IS '名称'; +COMMENT ON COLUMN "sys_app"."access_key" IS 'Access Key(访问密钥)'; +COMMENT ON COLUMN "sys_app"."secret_key" IS 'Secret Key(私有密钥)'; +COMMENT ON COLUMN "sys_app"."expire_time" IS '失效时间'; +COMMENT ON COLUMN "sys_app"."description" IS '描述'; +COMMENT ON COLUMN "sys_app"."status" IS '状态(1:启用;2:禁用)'; +COMMENT ON COLUMN "sys_app"."create_user" IS '创建人'; +COMMENT ON COLUMN "sys_app"."create_time" IS '创建时间'; +COMMENT ON COLUMN "sys_app"."update_user" IS '修改人'; +COMMENT ON COLUMN "sys_app"."update_time" IS '修改时间'; +COMMENT ON TABLE "sys_app" IS '应用表'; + +-- 初始化默认菜单 +INSERT INTO "sys_menu" +("id", "title", "parent_id", "type", "path", "name", "component", "redirect", "icon", "is_external", "is_cache", "is_hidden", "permission", "sort", "status", "create_user", "create_time") +VALUES +(7000, '能力开放', 0, 1, '/open', 'Open', 'Layout', '/open/app', 'expand', false, false, false, NULL, 7, 1, 1, NOW()), +(7010, '应用管理', 7000, 2, '/open/app', 'OpenApp', 'open/app/index', NULL, 'common', false, false, false, NULL, 1, 1, 1, NOW()), +(7011, '列表', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:list', 1, 1, 1, NOW()), +(7012, '详情', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:detail', 2, 1, 1, NOW()), +(7013, '新增', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:add', 3, 1, 1, NOW()), +(7014, '修改', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:update', 4, 1, 1, NOW()), +(7015, '删除', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:delete', 5, 1, 1, NOW()), +(7016, '导出', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:export', 6, 1, 1, NOW()), +(7017, '查看密钥', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:secret', 7, 1, 1, NOW()), +(7018, '重置密钥', 7010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'open:app:resetSecret', 8, 1, 1, NOW()); diff --git a/wms-webapi/src/main/resources/db/changelog/postgresql/plugin/plugin_schedule.sql b/wms-webapi/src/main/resources/db/changelog/postgresql/plugin/plugin_schedule.sql new file mode 100644 index 0000000..2c93324 --- /dev/null +++ b/wms-webapi/src/main/resources/db/changelog/postgresql/plugin/plugin_schedule.sql @@ -0,0 +1,21 @@ +-- liquibase formatted sql + +-- changeset kai:1 +-- comment 初始化任务调度插件 +-- 初始化默认菜单 +INSERT INTO "sys_menu" +("id", "title", "parent_id", "type", "path", "name", "component", "redirect", "icon", "is_external", "is_cache", "is_hidden", "permission", "sort", "status", "create_user", "create_time") +VALUES +(3000, '任务调度', 0, 1, '/schedule', 'Schedule', 'Layout', '/schedule/job', 'schedule', false, false, false, NULL, 3, 1, 1, NOW()), +(3010, '任务管理', 3000, 2, '/schedule/job', 'ScheduleJob', 'schedule/job/index', NULL, 'select-all', false, false, false, NULL, 1, 1, 1, NOW()), +(3011, '列表', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:list', 1, 1, 1, NOW()), +(3012, '详情', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:detail', 2, 1, 1, NOW()), +(3013, '新增', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:add', 3, 1, 1, NOW()), +(3014, '修改', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:update', 4, 1, 1, NOW()), +(3015, '删除', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:delete', 5, 1, 1, NOW()), +(3016, '执行', 3010, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:job:trigger', 6, 1, 1, NOW()), +(3020, '任务日志', 3000, 2, '/schedule/log', 'ScheduleLog', 'schedule/log/index', NULL, 'find-replace', false, false, false, NULL, 2, 1, 1, NOW()), +(3021, '列表', 3020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:log:list', 1, 1, 1, NOW()), +(3022, '详情', 3020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:log:detail', 2, 1, 1, NOW()), +(3023, '停止', 3020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:log:stop', 3, 1, 1, NOW()), +(3024, '重试', 3020, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'schedule:log:retry', 4, 1, 1, NOW()); diff --git a/wms-webapi/src/main/resources/favicon.ico b/wms-webapi/src/main/resources/favicon.ico new file mode 100644 index 0000000..f3c48de Binary files /dev/null and b/wms-webapi/src/main/resources/favicon.ico differ diff --git a/wms-webapi/src/main/resources/logback-spring.xml b/wms-webapi/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..a69cea8 --- /dev/null +++ b/wms-webapi/src/main/resources/logback-spring.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + ${LOG_CHARSET} + + + + + + + ${FILE_LOG_PATTERN} + ${LOG_CHARSET} + + + + + + + ${LOG_PATH}/${APP_NAME}.log + + + + ${LOG_PATH}/%d{yyyy-MM-dd}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log.gz + + ${FILE_MAX_SIZE} + + ${FILE_MAX_HISTORY} + + + ${FILE_LOG_PATTERN} + ${LOG_CHARSET} + + + + + + + 0 + + 512 + + + + + + + + + + + + + + + + + + + + + + diff --git a/wms-webapi/src/main/resources/templates/import/consumePeopleDeposit.xlsx b/wms-webapi/src/main/resources/templates/import/consumePeopleDeposit.xlsx new file mode 100644 index 0000000..7fa77ce Binary files /dev/null and b/wms-webapi/src/main/resources/templates/import/consumePeopleDeposit.xlsx differ diff --git a/wms-webapi/src/main/resources/templates/import/people.xlsx b/wms-webapi/src/main/resources/templates/import/people.xlsx new file mode 100644 index 0000000..3c1fe9c Binary files /dev/null and b/wms-webapi/src/main/resources/templates/import/people.xlsx differ diff --git a/wms-webapi/src/main/resources/templates/import/user.xlsx b/wms-webapi/src/main/resources/templates/import/user.xlsx new file mode 100644 index 0000000..03a9360 Binary files /dev/null and b/wms-webapi/src/main/resources/templates/import/user.xlsx differ diff --git a/wms-webapi/src/main/resources/templates/mail/captcha.ftl b/wms-webapi/src/main/resources/templates/mail/captcha.ftl new file mode 100644 index 0000000..09332af --- /dev/null +++ b/wms-webapi/src/main/resources/templates/mail/captcha.ftl @@ -0,0 +1,47 @@ + + + + + + + + + + + +
+
+ +
+
+

亲爱的用户:

+

+ 您好!感谢您使用 ${siteTitle},本次请求的验证码为:${captcha},请在 ${expiration} 分钟内使用此验证码完成验证。 +

+
+

Dear user:

+

+ Hello! Thanks for using ${siteTitle}, The verification code for this request is: ${captcha}, please use this verification code to complete the verification within ${expiration} minutes. +

+
+
+

+ 若非本人操作,请忽略此邮件。此邮件由系统自动发送,请勿直接回复该邮件。
+ Please ignore this email if not by yourself. This email is sent automatically by the system, please do not reply to this email directly. +

+

${siteCopyright}

+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/wms-webapi/src/test/java/top/ysoft/admin/YsoftAdminApplicationTests.java b/wms-webapi/src/test/java/top/ysoft/admin/YsoftAdminApplicationTests.java new file mode 100644 index 0000000..6b27c28 --- /dev/null +++ b/wms-webapi/src/test/java/top/ysoft/admin/YsoftAdminApplicationTests.java @@ -0,0 +1,12 @@ +package top.ysoft.admin; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class YsoftAdminApplicationTests { + + @Test + void contextLoads() { + } +}