Просмотр исходного кода

新增 kyl-sanatorium 康养项目后端 Maven 多模块代码,含 common/framework/pay/security/service/web 六个子模块

WanJL 3 недель назад
Родитель
Сommit
3628567c8a
100 измененных файлов с 7139 добавлено и 0 удалено
  1. 46 0
      kyl-sanatorium/Dockerfile
  2. 136 0
      kyl-sanatorium/README.md
  3. 103 0
      kyl-sanatorium/kyl-common/pom.xml
  4. 61 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/BaseDto.java
  5. 95 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/BaseEntity.java
  6. 103 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/BaseVo.java
  7. 21 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/DataScope.java
  8. 23 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/IBasicEnum.java
  9. 247 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/PageResponse.java
  10. 149 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/ResponseResult.java
  11. 41 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/AccraditationRecordConstant.java
  12. 140 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/Constants.java
  13. 34 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/DeptCacheConstant.java
  14. 31 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/PendingTasksConstant.java
  15. 47 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/RetreatConstant.java
  16. 17 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/SecurityConstant.java
  17. 60 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/SuperConstant.java
  18. 48 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/UserCacheConstant.java
  19. 27 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/enums/BasicEnum.java
  20. 73 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/exception/BaseException.java
  21. 139 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/exception/GlobalExceptionHandler.java
  22. 83 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/exception/ProjectException.java
  23. 45 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/AjaxResultBuild.java
  24. 69 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/AliOSSUtil.java
  25. 104 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/BeanConv.java
  26. 52 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/ClientIpUtil.java
  27. 60 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/CodeUtil.java
  28. 18 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/ConvertHandler.java
  29. 124 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/EmptyUtil.java
  30. 81 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/ExceptionsUtil.java
  31. 92 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/HttpStatus.java
  32. 58 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/JwtUtil.java
  33. 74 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/NoProcessing.java
  34. 30 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/ObjectUtil.java
  35. 56 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/RegisterBeanHandler.java
  36. 23 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/RequestUtil.java
  37. 126 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/SnowflakeIdWorker.java
  38. 385 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/StringUtils.java
  39. 439 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/UUID.java
  40. 128 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/UserThreadLocal.java
  41. 12 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/DataScopeDto.java
  42. 29 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/DataSecurity.java
  43. 55 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/DeptVo.java
  44. 33 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/MenuMetaVo.java
  45. 51 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/MenuVo.java
  46. 48 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/PostVo.java
  47. 74 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/ResourceVo.java
  48. 59 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/RoleVo.java
  49. 34 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/TreeItemVo.java
  50. 25 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/TreeVo.java
  51. 58 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/UserAddVo.java
  52. 24 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/UserRoleVo.java
  53. 110 0
      kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/UserVo.java
  54. 119 0
      kyl-sanatorium/kyl-framework/pom.xml
  55. 53 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/CustomRedisConfig.java
  56. 21 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/MybatisConfig.java
  57. 86 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/OSSAliyunFileStorageService.java
  58. 48 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/OssAliyunAutoConfig.java
  59. 62 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/SecurityConfig.java
  60. 93 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/SwaggerConfig.java
  61. 110 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/WebMvcConfig.java
  62. 39 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/XxlJobConfig.java
  63. 134 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/intercept/AutoFillInterceptor.java
  64. 76 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/intercept/UserInterceptor.java
  65. 56 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/intercept/UserTokenIntercept.java
  66. 48 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/properties/AliIoTConfigProperties.java
  67. 43 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/properties/AliOssConfigProperties.java
  68. 32 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/properties/JwtTokenManagerProperties.java
  69. 33 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/properties/SecurityConfigProperties.java
  70. 32 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/properties/SwaggerConfigProperties.java
  71. 114 0
      kyl-sanatorium/kyl-framework/src/main/java/com/kyl/security/JwtAuthorizationManager.java
  72. 49 0
      kyl-sanatorium/kyl-pay/pom.xml
  73. 62 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/Config.java
  74. 35 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/Factory.java
  75. 151 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/WechatPayHttpClient.java
  76. 149 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/operators/Common.java
  77. 68 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/operators/Wap.java
  78. 19 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/AmountResponse.java
  79. 19 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/AppPreCreateResponse.java
  80. 23 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/BasicResponse.java
  81. 14 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/CloseResponse.java
  82. 18 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/PreCreateResponse.java
  83. 27 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/QueryResponse.java
  84. 32 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/RefundResponse.java
  85. 17 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/WapPayResponse.java
  86. 34 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/config/WechatPayConfig.java
  87. 33 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/config/WechatPayProperties.java
  88. 20 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/constant/SuperConstant.java
  89. 32 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/constant/TradingCacheConstant.java
  90. 80 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/constant/TradingConstant.java
  91. 106 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/controller/CommonPayFeignController.java
  92. 75 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/entity/RefundRecord.java
  93. 100 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/entity/Trading.java
  94. 34 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/enums/RefundStatusEnum.java
  95. 63 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/enums/TradingEnum.java
  96. 39 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/enums/TradingStateEnum.java
  97. 71 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/handler/BeforePayHandler.java
  98. 55 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/handler/CommonPayHandler.java
  99. 17 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/handler/WapPayHandler.java
  100. 198 0
      kyl-sanatorium/kyl-pay/src/main/java/com/kyl/handler/basic/BasicBeforePayHandler.java

+ 46 - 0
kyl-sanatorium/Dockerfile

@@ -0,0 +1,46 @@
+# ========== 构建阶段 ==========
+FROM maven:3.8-openjdk-11-slim AS builder
+
+WORKDIR /build
+
+# 先拷贝 pom.xml 和公共模块,利用 Docker 缓存加速
+COPY pom.xml ./
+COPY kyl-common/pom.xml kyl-common/
+COPY kyl-framework/pom.xml kyl-framework/
+COPY kyl-security/pom.xml kyl-security/
+COPY kyl-pay/pom.xml kyl-pay/
+COPY kyl-service/pom.xml kyl-service/
+COPY kyl-web/pom.xml kyl-web/
+
+# 下载依赖(只下载,后续构建可复用缓存层)
+RUN mvn dependency:go-offline -B || true
+
+# 拷贝全部源码
+COPY . .
+
+# 构建项目,跳过测试
+RUN mvn clean package -DskipTests -B
+
+# ========== 运行阶段 ==========
+FROM openjdk:11-jre-slim
+
+WORKDIR /app
+
+# 从构建阶段复制打好的 jar
+COPY --from=builder /build/kyl-web/target/*.jar app.jar
+
+# 暴露端口
+EXPOSE 9995
+
+# 环境变量(建议通过 docker run -e 覆盖,这里只给默认值)
+ENV SPRING_PROFILES_ACTIVE=dev
+ENV MYSQL_HOST=192.168.200.128
+ENV MYSQL_PORT=3306
+ENV MYSQL_USERNAME=root
+ENV MYSQL_PASSWORD=123456
+ENV REDIS_HOST=192.168.200.128
+ENV REDIS_PORT=6379
+ENV XXL_JOB_ADMIN_ADDRESSES=http://192.168.200.128:8888/xxl-job-admin
+
+# 启动
+CMD ["java", "-jar", "app.jar"]

+ 136 - 0
kyl-sanatorium/README.md

@@ -0,0 +1,136 @@
+# 康悦林疗养院 — 智慧疗养系统(后端)
+
+> 基于 Spring Boot 2.7 + MyBatis-Plus 的智慧疗养院管理系统,提供 PC 管理后台与 UniApp 移动端双端支持。
+
+---
+
+## 项目结构
+
+```
+kyl-sanatorium/
+├── kyl-common/          # 公共模块(基类、工具类、常量、VO)
+├── kyl-framework/       # 框架层(配置、拦截器、安全管理)
+├── kyl-security/        # 权限模块(用户/角色/部门/菜单/岗位)
+├── kyl-service/         # 业务模块(入住/退住/财务/护理/IoT)
+├── kyl-pay/             # 支付模块(微信支付、交易、退款)
+├── kyl-web/             # Web 入口(Controller、主启动类)
+└── pom.xml              # 父 POM(多模块聚合)
+```
+
+---
+
+## 技术栈
+
+| 技术              | 版本       | 说明                        |
+|-----------------|----------|---------------------------|
+| Spring Boot     | 2.7.4    | 基础框架                      |
+| Java            | 11       | JDK 版本                    |
+| MyBatis-Plus    | 3.5.15   | ORM 框架                    |
+| MyBatis         | 3.5.19   | MyBatis 核心                |
+| MySQL           | 8.0.19   | 数据库                       |
+| Druid           | 1.2.1    | 数据库连接池                    |
+| Redis           | -        | 缓存(@Cacheable + Redisson) |
+| Activiti        | 7.10.0   | 工作流引擎(入住/退住流程审批)          |
+| PageHelper      | 1.3.0    | 分页插件                      |
+| Knife4j         | 3.0.3    | API 文档(访问 /doc.html)      |
+| Spring Security | -        | 认证授权(JWT Token)           |
+| Hutool          | 5.8.0.M3 | 工具类库                      |
+| XXL-Job         | 2.3.0    | 分布式定时任务                   |
+| 阿里云 OSS         | -        | 文件存储                      |
+| 阿里云 IoT         | -        | 设备数据接入                    |
+| 微信支付            | -        | 微信支付 v3 API               |
+
+---
+
+## 模块功能说明
+
+### 1. 权限管理(kyl-security)
+- 用户管理(CRUD、密码重置、角色分配)
+- 角色管理(CRUD、菜单权限、数据权限)
+- 部门管理(树形结构、领导指派)
+- 岗位管理
+- 菜单/资源管理(动态路由、按钮权限)
+
+### 2. 业务管理(kyl-service)
+
+| 模块        | 说明                                |
+|-----------|-----------------------------------|
+| 入住管理      | 入住申请 → 评估 → 签约 → 审批(Activiti 工作流) |
+| 退住管理      | 退住申请 → 清算 → 审批 → 退款(Activiti 工作流) |
+| 预约来访      | PC/移动端预约、来访记录管理                   |
+| 老人管理      | 老人档案、身份证校验                        |
+| 居住管理      | 楼层/房间/床位管理、房型管理                   |
+| 护理服务      | 护理等级、护理项目、护理计划、护理任务               |
+| 财务管理      | 账单、欠费、余额、预充值、退款                   |
+| 合同管理      | 合同签署、到期提醒                         |
+| 订单管理      | 服务订单、商品订单、退款                      |
+| IoT 设备    | 阿里云 IoT 设备管理、物模型、属性数据             |
+| 报警规则      | 设备报警规则配置、报警处理                     |
+| WebSocket | 实时消息推送                            |
+
+### 3. 支付模块(kyl-pay)
+- 微信支付(H5/JSAPI)
+- 交易记录
+- 退款处理
+
+### 4. 移动端(UniApp)
+- 家属端:老人绑定、健康数据查看、预约、账单
+- 客户登录:微信授权、手机号
+
+---
+
+## 快速启动
+
+### 环境要求
+- JDK 11+
+- MySQL 8.0+
+- Redis
+- Maven 3.6+
+
+### 启动步骤
+
+```bash
+# 1. 创建数据库并导入 SQL
+mysql -u root -p < kyl-sanatorium-sql/kyl_sanatorium_db.sql
+
+# 2. 修改数据库配置
+#    编辑 kyl-web/src/main/resources/application.yml
+#    修改 datasource.druid.url / username / password
+
+# 3. 编译运行
+cd kyl-sanatorium
+mvn clean package -DskipTests
+java -jar kyl-web/target/kyl-web.jar
+```
+
+访问地址:
+- 后台 API:`http://localhost:9995`
+- API 文档:`http://localhost:9995/doc.html`
+- 移动端 API:`http://localhost:9995/customer`
+
+### 安全提示
+
+> **生产部署前必须**:替换 `application.yml` 中的明文凭据(数据库密码、阿里云AK/SK、微信Secret 等),使用环境变量或配置中心管理敏感信息。
+
+---
+
+## 接口概览
+
+| 分类   | 路径前缀                                                     | 说明             |
+|------|----------------------------------------------------------|----------------|
+| 认证   | `/security`                                              | 登录、获取用户信息      |
+| 权限管理 | `/user`, `/role`, `/dept`, `/post`, `/resource`          | 用户/角色/部门/岗位/菜单 |
+| 入住   | `/checkIn`                                               | 入住申请、评估、签约、审批  |
+| 退住   | `/elder`                                                 | 退住申请、审批、清算     |
+| 预约来访 | `/reservation`, `/visit`                                 | 预约、来访记录        |
+| 居住   | `/floor`, `/room`, `/bed`, `/roomTypes`                  | 楼层/房间/床位/房型    |
+| 老人   | `/elder`                                                 | 老人档案           |
+| 财务   | `/bill`                                                  | 账单、欠费、余额、充值    |
+| 订单   | `/orders`                                                | 服务订单           |
+| 护理   | `/nursingLevel`, `/nursing_project`, `/nursing/plan`, `/nursingTask` | 等级/项目/计划/任务    |
+| IoT  | `/iot`, `/alert-rule`, `/device-data`                    | 设备管理/报警/数据     |
+| 合同   | `/contract`                                              | 合同管理           |
+| 移动端  | `/customer/*`                                            | 家属端接口          |
+| 支付   | `/trade-common-feign`                                    | 交易/退款          |
+
+> 详细 API 文档见:`kyl-sanatorium-api-docs.md`

+ 103 - 0
kyl-sanatorium/kyl-common/pom.xml

@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.kyl</groupId>
+        <artifactId>kyl-sanatorium</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>kyl-common</artifactId>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <!--分页工具-PageHelper-->
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!--工具包-HuTool-->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+        <!---->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <!-- 拷贝对象 -->
+        <dependency>
+            <groupId>ma.glasnost.orika</groupId>
+            <artifactId>orika-core</artifactId>
+        </dependency>
+        <!---->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!---->
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+        </dependency>
+        <!--  knife4j版接口文档 访问/doc.html-->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+        </dependency>
+        <!--redis starter-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <!--JWT-->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.xml.bind</groupId>
+            <artifactId>jaxb-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <!-- MyBatis-Plus 核心注解 (TableName, TableId, IdType 等) -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-annotation</artifactId>
+            <version>${mybatis.plus.version}</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 61 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/BaseDto.java

@@ -0,0 +1,61 @@
+package com.kyl.base;
+
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title BaseDto
+ * @description DTO基类
+ * @create 2024/12/25
+ */
+@Data
+@NoArgsConstructor
+@ApiModel("DTO基类")
+public class BaseDto  implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @ApiModelProperty(value = "主键")
+    private Long id;
+
+    /**
+     * 搜索值
+     */
+    @JsonIgnore
+    private String searchValue;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    /**
+     * 请求参数
+     */
+    @JsonIgnore
+    private Map<String, Object> params;
+
+    public Map<String, Object> getParams() {
+        if (params == null) {
+            params = new HashMap<>();
+        }
+        return params;
+    }
+
+    public void setParams(Map<String, Object> params) {
+        this.params = params;
+    }
+}

+ 95 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/BaseEntity.java

@@ -0,0 +1,95 @@
+package com.kyl.base;
+
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title BaseEntity
+ * @description 实体基础类
+ * @create 2024/12/25
+ */
+@Data
+@NoArgsConstructor
+public class BaseEntity implements Serializable {
+    /**
+     * 主键
+     */
+    @ApiModelProperty(value = "主键")
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty(value = "创建时间")
+    public LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty(value = "更新时间")
+    public LocalDateTime updateTime;
+
+    /**
+     * 创建人
+     */
+    @ApiModelProperty(value = "创建人")
+    private Long createBy;
+
+    /**
+     * 更新人
+     */
+    @ApiModelProperty(value = "更新人")
+    private Long updateBy;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    /**
+     * 创建人 (非数据库字段,通过 JOIN sys_user 查询)
+     */
+    @ApiModelProperty(value = "创建人")
+    @TableField(exist = false)
+    private String creator;
+
+    /**
+     * 更新人 (非数据库字段,通过 JOIN sys_user 查询)
+     */
+    @ApiModelProperty(value = "更新人")
+    @TableField(exist = false)
+    private String updater;
+
+    public BaseEntity(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * 请求参数 (非数据库字段,仅用于请求参数传递)
+     */
+    @JsonIgnore
+    @TableField(exist = false)
+    private Map<String, Object> params;
+
+    public Map<String, Object> getParams() {
+        if (params == null) {
+            params = new HashMap<>();
+        }
+        return params;
+    }
+}

+ 103 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/BaseVo.java

@@ -0,0 +1,103 @@
+package com.kyl.base;
+
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title BaseVo VO基类
+ * @description
+ * @create 2024/12/25
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BaseVo implements Serializable {
+    /**
+     * 主键
+     */
+    @ApiModelProperty(value = "主键")
+    private Long id;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get
+    protected LocalDateTime createTime;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")//get
+    protected String createDay;
+
+    /**
+     * 修改时间
+     */
+    @ApiModelProperty(value = "修改时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get
+    protected LocalDateTime updateTime;
+
+    /**
+     * 创建者:username
+     */
+    @ApiModelProperty(value = "创建者:username")
+    private Long createBy;
+
+    /**
+     * 更新者:username
+     */
+    @ApiModelProperty(value = "更新者:username")
+    private Long updateBy;
+
+    /**
+     * 是否有效
+     */
+    @ApiModelProperty(value = "是否有效")
+    protected String dataState;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    /**
+     * 创建人类型 1 前台 2后台
+     */
+    @ApiModelProperty(value = "创建人类型 1 前台 2后台")
+    private Integer createType;
+
+    /**
+     * 创建人名称
+     */
+    @ApiModelProperty(value = "创建人名称")
+    private String creator;
+
+    /**
+     * 后台管理端创建人名称
+     */
+    @ApiModelProperty(value = "后台管理端创建人名称")
+    private String adminCreator;
+
+    /**
+     * 更新人名称
+     */
+    @ApiModelProperty(value = "更新人名称")
+    private String updater;
+
+    public BaseVo(Long id, String dataState) {
+        this.id = id;
+        this.dataState = dataState;
+    }
+}

+ 21 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/DataScope.java

@@ -0,0 +1,21 @@
+package com.kyl.base;
+
+
+import java.lang.annotation.*;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title DataScope
+ * @description 数据权限过滤注解
+ * @create 2024/12/25
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataScope {
+
+    public String deptAlias() default "";
+
+    public String userAlias() default "";
+}

+ 23 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/IBasicEnum.java

@@ -0,0 +1,23 @@
+package com.kyl.base;
+
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title IBasicEnum
+ * @description 枚举接口
+ * @create 2024/12/25
+ */
+public interface IBasicEnum {
+
+    /**
+     * 编码
+     */
+    public int getCode();
+
+    /**
+     * 信息
+     */
+    public String getMsg();
+
+}

+ 247 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/PageResponse.java

@@ -0,0 +1,247 @@
+package com.kyl.base;
+
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.github.pagehelper.Page;
+import com.kyl.utils.ConvertHandler;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title PageResponse
+ * @description 分页结果包装
+ * @create 2024/12/25
+ */
+@Data
+@ApiModel(value = "分页数据消息体", description = "分页数据统一对象")
+@Builder
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@NoArgsConstructor
+public class PageResponse<T> {
+
+    @ApiModelProperty(value = "总条目数", required = true)
+    private Long total = 0L;
+
+    @ApiModelProperty(value = "页尺寸", required = true)
+    private Integer pageSize = 0;
+
+    @ApiModelProperty(value = "总页数", required = true)
+    private Long pages = 0L;
+
+    @ApiModelProperty(value = "页码", required = true)
+    private Integer page = 0;
+
+    @ApiModelProperty(value = "数据列表", required = true)
+    private List<T> records = Collections.EMPTY_LIST;
+
+
+    /**
+     * 通过mybatis的分页对象构造对象,不封装 items 属性
+     *
+     * @param page 分页对象
+     */
+    public PageResponse(Page<?> page) {
+        this.page = Convert.toInt(page.getPageNum());
+        this.total = page.getTotal();
+        this.pageSize = Convert.toInt(page.getPageSize());
+        this.pages = (long) page.getPages();
+    }
+
+    /**
+     * 通过mybatis的分页对象构造对象,封装 items 属性
+     *
+     * @param page  分页对象
+     * @param clazz 指定items 属性的类型
+     */
+    public PageResponse(Page<?> page, Class<T> clazz) {
+        this.page = Convert.toInt(page.getPageNum());
+        this.total = page.getTotal();
+        this.pageSize = Convert.toInt(page.getPageSize());
+        this.pages = (long) page.getPages();
+    }
+
+    /**
+     * 返回一个分页对象实例
+     *
+     * @return 分页数据对象
+     */
+    public static <T> PageResponse<T> getInstance() {
+        return PageResponse.<T>builder().build();
+    }
+
+    /**
+     * Page{@link Page}对象封装为PageResponse,不封装 items 属性
+     *
+     * @param page 源分页对象
+     * @return 目标分页数据对象
+     */
+    public static <T> PageResponse<T> of(Page<?> page) {
+        //封装分页数据
+        return PageResponse.<T>builder()
+                .page(Convert.toInt(page.getPageNum()))
+                .pageSize(Convert.toInt(page.getPageSize()))
+                .pages((long) page.getPages())
+                .total(page.getTotal())
+                .build();
+    }
+
+    /**
+     * Page{@link Page}对象封装为PageResponse,
+     * 并将Page中的Records转换为指定类型封装为items
+     *
+     * @param page 源分页对象
+     * @return 目标分页数据对象
+     */
+    public static <T> PageResponse<T> of(Page<?> page, Class<T> clazz) {
+        return of(page, clazz, null);
+    }
+
+    /**
+     * Page{@link Page}对象封装为PageResponse,
+     * 并将Page中的Records转换为指定类型封装为items
+     *
+     * @param page           源分页对象
+     * @param convertHandler 特殊对象类型转换器,可传null,即不进行特殊处理
+     * @return 目标分页数据对象
+     */
+    public static <O, T> PageResponse<T> of(Page<O> page, Class<T> clazz, ConvertHandler<O, T> convertHandler) {
+        //封装分页数据
+        return PageResponse.<T>builder()
+                .page(Convert.toInt(page.getPageNum()))
+                .pageSize(Convert.toInt(page.getPageSize()))
+                .pages((long) page.getPages())
+                .total(page.getTotal())
+                .records(copyToList(page.getResult(), clazz, convertHandler))
+                .build();
+    }
+
+    /**
+     * 对items进行类型转换
+     *
+     * @param origin 源分页数据对象
+     * @param clazz  指定items 属性的类型,不能为null
+     * @return 目标分页数据对象
+     */
+    public static <O, T> PageResponse<T> of(PageResponse<O> origin, Class<T> clazz) {
+        return of(origin, clazz, null);
+    }
+
+    /**
+     * 对items进行类型转换
+     *
+     * @param origin         源分页数据对象
+     * @param clazz          指定items 属性的类型,不能为null
+     * @param convertHandler 特殊对象类型转换器,可传null,即不进行特殊处理
+     * @return 目标分页数据对象
+     */
+    public static <O, T> PageResponse<T> of(PageResponse<O> origin, Class<T> clazz, ConvertHandler<O, T> convertHandler) {
+        //复制除items外的属性
+        PageResponse<T> target = PageResponse.getInstance();
+        BeanUtil.copyProperties(origin, target, "items");
+
+        //items为空,直接返回
+        if (CollUtil.isEmpty(origin.getRecords())) {
+            return target;
+        }
+        List<T> targetList = copyToList(origin.getRecords(), clazz, convertHandler);
+        target.setRecords(targetList);
+
+        //封装分页数据
+        return target;
+    }
+
+    /**
+     * List{@link List}封装为分页数据对象
+     *
+     * @param items    item数据
+     * @param page     页码,可不传,数据不为空时默认为1
+     * @param pageSize 页尺寸,可不传,数据不为空时默认为1
+     * @param pages    页尺寸,可不传,数据不为空时默认为1
+     * @param counts   总条目数,可不传,数据不为空时默认为1
+     * @return 目标分页数据对象
+     */
+    public static <T> PageResponse<T> of(List<T> items, Integer page, Integer pageSize, Long pages, Long counts) {
+        //封装分页数据
+        PageResponse<T> pageResponse = PageResponse.<T>builder()
+                .page(Optional.ofNullable(page).orElse(1))
+                .pageSize(Optional.ofNullable(pageSize).orElse(1))
+                .pages(Optional.ofNullable(pages).orElse(1L))
+                .total(Optional.ofNullable(counts).orElse(1L))
+                .build();
+
+        if (CollUtil.isEmpty(items)) {
+            return pageResponse;
+        }
+
+        pageResponse.setRecords(items);
+        return pageResponse;
+    }
+
+    /**
+     * List{@link List}封装为分页数据对象
+     * 数据不为空时,page、pageSize、pages、counts均默认为1
+     *
+     * @param items item数据
+     * @return 目标分页数据对象
+     */
+    public static <T> PageResponse<T> of(List<T> items) {
+        return of(items, null, null, null, null);
+    }
+
+    /**
+     * 返回包含任意数量元素的分页对象
+     * 数据不为空时,page、pageSize、pages、counts均默认为1
+     *
+     * @param elements items元素
+     * @return 目标分页数据对象
+     */
+    @SafeVarargs
+    public static <E> PageResponse<E> of(E... elements) {
+        return of(List.of(elements));
+    }
+
+    /**
+     * 对items进行类型转换
+     *
+     * @param origin         源分页数据对象
+     * @param function 自定义函数
+     * @return 目标分页数据对象
+     */
+    public static <O, T> PageResponse<T> of(PageResponse<O> origin, Function<List<O>, List<T>> function) {
+        List<T> orderVOList = function.apply(origin.getRecords());
+        return PageResponse.of(orderVOList, origin.getPage(), origin.getPageSize(), origin.getPages(), origin.total);
+    }
+
+    /**
+     * 转换结构
+     * @param content
+     * @param clazz
+     * @param convertHandler
+     * @param <T>
+     * @param <O>
+     * @return
+     */
+    private static <T, O> List<T> copyToList(List<O> content, Class<T> clazz, ConvertHandler<O, T> convertHandler) {
+        //对items进行类型转换
+        List<T> targetList = BeanUtil.copyToList(content, clazz);
+        //特殊类型转换
+        //特殊类型转换
+        if (CollUtil.isNotEmpty(targetList) && ObjectUtil.isNotEmpty(convertHandler)) {
+            for (int i = 0; i < content.size(); i++) {
+                convertHandler.map(content.get(i), targetList.get(i));
+            }
+        }
+        return targetList;
+    }
+}

+ 149 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/base/ResponseResult.java

@@ -0,0 +1,149 @@
+package com.kyl.base;
+
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.kyl.utils.HttpStatus;
+import com.kyl.utils.StringUtils;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ResponseResult
+ * @description 返回结果
+ * @create 2024/12/25
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class ResponseResult<T> implements Serializable {
+
+    /**
+     * 响应返回编码
+     */
+    @ApiModelProperty(value = "状态码")
+    private int code;
+
+    /**
+     * 响应返回信息
+     */
+    @ApiModelProperty(value = "状态信息")
+    private String msg;
+
+    /**
+     * 返回结果
+     */
+    @ApiModelProperty(value = "返回结果")
+    private T data;
+
+    /**
+     * 创建时间,处理json的时间参数解析
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss",timezone = "GMT+8")
+    @ApiModelProperty(value = "操作时间")
+    private Date operationTime;
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     *
+     * @param code 状态码
+     * @param msg  返回内容
+     */
+    public ResponseResult(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     *
+     * @param code 状态码
+     * @param msg  返回内容
+     * @param data 数据对象
+     */
+    public ResponseResult(int code, String msg, T data) {
+        this.code = code;
+        this.msg = msg;
+        if (StringUtils.isNotNull(data)) {
+            this.data = data;
+        }
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @return 成功消息
+     */
+    public static ResponseResult success() {
+        return ResponseResult.success("操作成功");
+    }
+
+    /**
+     * 返回成功数据
+     *
+     * @return 成功消息
+     */
+    public static ResponseResult success(Object data) {
+        return ResponseResult.success("操作成功", data);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg 返回内容
+     * @return 成功消息
+     */
+    public static ResponseResult success(String msg) {
+        return ResponseResult.success(msg, null);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg  返回内容
+     * @param data 数据对象
+     * @return 成功消息
+     */
+    public static ResponseResult success(String msg, Object data) {
+        return new ResponseResult(HttpStatus.SUCCESS, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @return
+     */
+    public static ResponseResult error() {
+        return ResponseResult.error("操作失败");
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg 返回内容
+     * @return 警告消息
+     */
+    public static ResponseResult error(String msg) {
+        return ResponseResult.error(msg, null);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg  返回内容
+     * @param data 数据对象
+     * @return 警告消息
+     */
+    public static ResponseResult error(String msg, Object data) {
+        return new ResponseResult(HttpStatus.ERROR, msg, data);
+    }
+
+}

+ 41 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/AccraditationRecordConstant.java

@@ -0,0 +1,41 @@
+package com.kyl.constant;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title AccraditationRecordConstant
+ * @description 本类定义了与认证记录相关的常量,包括审核状态、记录类型和处理类型
+ * @create 2025/1/3
+ */
+public class AccraditationRecordConstant {
+
+    /**
+     * 审核状态
+     * 1:通过
+     * 2:拒绝
+     * 3:驳回
+     * 4:撤回
+     * 5:撤销
+     */
+    public static final Integer AUDIT_STATUS_PASS = 1;
+    public static final Integer AUDIT_STATUS_REJECT = 2;
+    public static final Integer AUDIT_STATUS_DISAPPROVE = 3;
+    public static final Integer AUDIT_STATUS_WITHDRAWS = 4;
+    public static final Integer AUDIT_STATUS_CANCEL = 5;
+
+    /**
+     * 记录类型
+     * 1:退住
+     * 2:请假
+     * 3:入住
+     */
+    public static final Integer RECORD_TYPE_RETREAT = 1;
+    public static final Integer RECORD_TYPE_LEAVE = 2;
+    public static final Integer RECORD_TYPE_CHECK_IN = 3;
+
+    /**
+     * 处理类型(0:已审批,1:已处理)
+     */
+    public static final Integer RECORD_HANDLE_TYPE_AUDIT = 0;
+    public static final Integer RECORD_HANDLE_TYPE_PROCESSED = 1;
+}

+ 140 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/Constants.java

@@ -0,0 +1,140 @@
+package com.kyl.constant;
+
+/**
+ * Constants
+ * @author WanJl
+ **/
+public class Constants {
+    /**
+     * UTF-8 字符集
+     */
+    public static final String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    public static final String GBK = "GBK";
+
+    /**
+     * http请求
+     */
+    public static final String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    public static final String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    public static final String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    public static final String FAIL = "1";
+
+    /**
+     * 登录成功
+     */
+    public static final String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 注销
+     */
+    public static final String LOGOUT = "Logout";
+
+    /**
+     * 登录失败
+     */
+    public static final String LOGIN_FAIL = "Error";
+
+    /**
+     * 验证码 redis key
+     */
+    public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
+
+    /**
+     * 登录用户 redis key
+     */
+    public static final String LOGIN_TOKEN_KEY = "login_tokens:";
+
+    /**
+     * 防重提交 redis key
+     */
+    public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    public static final Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 令牌
+     */
+    public static final String TOKEN = "token";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String LOGIN_USER_KEY = "login_user_key";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String MINI_APP_LOGIN_USER_KEY = "mini_app_login_user_key";
+    /**
+     * 用户ID
+     */
+    public static final String JWT_USERID = "userid";
+
+    /**
+     * 用户名称
+     */
+    public static final String JWT_USERNAME = "sub";
+
+    /**
+     * 用户头像
+     */
+    public static final String JWT_AVATAR = "avatar";
+
+    /**
+     * 创建时间
+     */
+    public static final String JWT_CREATED = "created";
+
+    /**
+     * 用户权限
+     */
+    public static final String JWT_AUTHORITIES = "authorities";
+
+    /**
+     * 参数管理 cache key
+     */
+    public static final String SYS_CONFIG_KEY = "sys_config:";
+
+    /**
+     * 字典管理 cache key
+     */
+    public static final String SYS_DICT_KEY = "sys_dict:";
+
+
+    public static final String ACT_CODE_KEY = "ac_dict:";
+
+    /**
+     * 资源映射路径 前缀
+     */
+    public static final String RESOURCE_PREFIX = "/profile";
+
+    /**
+     * 设备最新数据缓存KEY
+     */
+    public static final String DEVICE_LASTDATA_CACHE_KEY = "deviceLastData";
+
+}

+ 34 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/DeptCacheConstant.java

@@ -0,0 +1,34 @@
+package com.kyl.constant;
+
+/**
+* 部门表缓存常量
+*/
+public class DeptCacheConstant {
+
+    /**
+     * 缓存父包
+     */
+    public static final String PREFIX= "dept:";
+
+    /**
+     * 缓存父包
+     */
+    public static final String BASIC= PREFIX+"basic";
+
+    /**
+     * page分页
+     */
+    public static final String PAGE= PREFIX+"page";
+
+    /**
+     * list下拉框
+     */
+    public static final String LIST= PREFIX+"list";
+
+    /**
+     * 树形
+     */
+    public static final String TREE= PREFIX+"tree";
+
+
+}

+ 31 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/PendingTasksConstant.java

@@ -0,0 +1,31 @@
+package com.kyl.constant;
+
+/**
+ * @author WanJl
+ */
+public class PendingTasksConstant {
+
+    //状态(1:申请中,2:已完成,3:已关闭)
+    public static final Integer TASK_STATUS_APPLICATION = 1;
+    public static final Integer TASK_STATUS_FINISHED = 2;
+    public static final Integer TASK_STATUS_CLOSED = 3;
+
+
+    //类型(1:退住,2:请假,3:入住)
+    public static final Integer TASK_TYPE_RETREAT = 1;
+    public static final Integer TASK_TYPE_LEAVE = 2;
+    public static final Integer TASK_TYPE_CHECK_IN = 3;
+
+
+    /**
+     * 是否处理完成
+     *  0:未处理
+     *  1:已处理
+     */
+    public static final Integer TASK_IS_HANDLE_UNTREATED = 0;
+    public static final Integer TASK_IS_HANDLE_FINISHED = 1;
+
+
+
+
+}

+ 47 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/RetreatConstant.java

@@ -0,0 +1,47 @@
+package com.kyl.constant;
+
+/**
+ * @author WanJl
+ */
+public class RetreatConstant {
+
+    /**
+     * 法务部门编号
+     */
+    public static final String LEGAL_DEPT_CODE = "100001007000000";
+
+    /**
+     * 护理部 部门编号
+     */
+    public static final String NURSING_DEPT_CODE = "100001005000000";
+
+    /**
+     * 财务部门编号
+     */
+    public static final String SETTLEMENT_DEPT_CODE = "100001002000000";
+
+    /**
+     * 院长办公室部门编号
+     */
+    public static final String DEAN_OFFICE_DEPT_CODE = "100001001000000";
+
+
+    /**
+     * 退住业务-审核步骤编号
+     * 0:发起申请
+     * 1:护理组长审批
+     * 2:法务解除合同
+     * 3:结算员调整账单
+     * 4:结算组长审批
+     * 5:副院长审批
+     * 6:结算员结清费用
+     */
+    public static final Integer ACCRADITATION_STEP_NO_ZERO = 0;
+    public static final Integer ACCRADITATION_STEP_NO_ONE = 1;
+    public static final Integer ACCRADITATION_STEP_NO_TWO = 2;
+    public static final Integer ACCRADITATION_STEP_NO_THREE = 3;
+    public static final Integer ACCRADITATION_STEP_NO_FOUR = 4;
+    public static final Integer ACCRADITATION_STEP_NO_FIVE = 5;
+    public static final Integer ACCRADITATION_STEP_NO_SIX = 6;
+
+}

+ 17 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/SecurityConstant.java

@@ -0,0 +1,17 @@
+package com.kyl.constant;
+
+/**
+ * @ClassName SecurityConstant.java
+ */
+public class SecurityConstant {
+
+    //用户前端token
+    public static final String USER_TOKEN = "authorization";
+
+    //本人
+    public static final String DATA_SCOPE_1 = "1";
+
+    //自定义
+    public static final String DATA_SCOPE_0 = "0";
+
+}

+ 60 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/SuperConstant.java

@@ -0,0 +1,60 @@
+
+package com.kyl.constant;
+
+
+/**
+ *  静态变量
+ */
+public class SuperConstant {
+
+	/**
+	 * 常量是
+	 */
+	public static final String DATA_STATE_0 = "0";
+
+	/**
+	 * 常量否
+	 */
+	public static final String DATA_STATE_1 = "1";
+
+
+	/**
+	 * 树形根节点父Id
+	 */
+	public static final String ROOT_PARENT_ID = "100001000000000";
+
+	/**
+	 * 树形根节点父Id
+	 */
+	public static final String ROOT_DEPT_PARENT_ID = "100000000000000";
+
+	/**
+	 * 常量目录
+	 */
+	public static final String CATALOGUE = "c";
+
+	/**
+	 * 常量菜单
+	 */
+	public static final String MENU = "m";
+
+	/**
+	 * 常量菜单
+	 */
+	public static final String BUTTON = "r";
+
+	/**
+	 * 常量平台
+	 */
+	public static final String SYSTEM = "s";
+
+	/**
+	 *  前端显示布局
+	 */
+	public static final String COMPONENT_LAYOUT="Layout";
+
+	/**
+	 * 常量是
+	 */
+    public static final String DEFAULT_0 = "0";
+}

+ 48 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/constant/UserCacheConstant.java

@@ -0,0 +1,48 @@
+package com.kyl.constant;
+
+/**
+* 用户表缓存常量
+*/
+public class UserCacheConstant {
+
+    /**
+     * 缓存父包
+     */
+    public static final String PREFIX= "user:";
+
+    /**
+     * 缓存父包
+     */
+    public static final String BASIC= PREFIX+"basic";
+
+    /**
+     * 分布式锁前缀
+     */
+    public static final String LOCK_PREFIX = PREFIX+"lock:";
+
+    /**
+     * page分页
+     */
+    public static final String PAGE= PREFIX+"page";
+
+    /**
+     * list下拉框
+     */
+    public static final String LIST= PREFIX+"list";
+
+    /**
+     * 用户登录
+     */
+    public static final String LOGIN= PREFIX+"login";
+
+    /**
+     * 建立用户与会话唯一标识之间的关系,用于判断剔除
+     */
+    public static final String USER_TOKEN = PREFIX+"user-token:";
+
+    /**
+     * 建立会话唯一标识与jwtToken之间的关系,用于令牌续期
+     */
+    public static final String JWT_TOKEN = PREFIX+"jwt-token:";
+
+}

+ 27 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/enums/BasicEnum.java

@@ -0,0 +1,27 @@
+package com.kyl.enums;
+
+import com.kyl.base.IBasicEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ *  基础枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum BasicEnum implements IBasicEnum {
+
+    SUCCEED(200,"操作成功"),
+    SECURITY_ACCESSDENIED_FAIL(401,"权限不足!"),
+    SYSYTEM_FAIL(1503,"系统运行异常"),
+    VALID_EXCEPTION(1504,"参数校验异常");
+
+    /**
+     * 编码
+     */
+    public int code;
+    /**
+     * 信息
+     */
+    public String msg;
+}

+ 73 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/exception/BaseException.java

@@ -0,0 +1,73 @@
+package com.kyl.exception;
+
+
+import lombok.Data;
+
+/**
+ * BaseException
+ * @author WanJl
+ **/
+@Data
+public class BaseException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 所属模块
+     */
+    private String module;
+
+    /**
+     * 错误码
+     */
+    private String code;
+
+    /**
+     * 错误码对应的参数
+     */
+    private Object[] args;
+
+    /**
+     * 错误消息
+     */
+    private String defaultMessage;
+
+    public BaseException(String module, String code, Object[] args, String defaultMessage) {
+        this.module = module;
+        this.code = code;
+        this.args = args;
+        this.defaultMessage = defaultMessage;
+    }
+
+    public BaseException(String module, String code, Object[] args) {
+        this(module, code, args, null);
+    }
+
+    public BaseException(String module, String defaultMessage) {
+        this(module, null, null, defaultMessage);
+    }
+
+    public BaseException(String code, Object[] args) {
+        this(null, code, args, null);
+    }
+
+    public BaseException(String defaultMessage) {
+        this(null, "500", null, defaultMessage);
+    }
+
+
+    public String getModule() {
+        return module;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public Object[] getArgs() {
+        return args;
+    }
+
+    public String getDefaultMessage() {
+        return defaultMessage;
+    }
+}

+ 139 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/exception/GlobalExceptionHandler.java

@@ -0,0 +1,139 @@
+package com.kyl.exception;
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.multipart.MaxUploadSizeExceededException;
+
+import java.io.FileNotFoundException;
+import java.nio.file.AccessDeniedException;
+
+@RestControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+    /**
+     * 处理自定义异常BaseException。
+     * 返回自定义异常中的错误代码和错误消息。
+     *
+     * @param exception 自定义异常
+     * @return 响应数据,包含错误代码和错误消息
+     */
+    @ExceptionHandler(BaseException.class)
+    public ResponseEntity<Object> handleBaseException(BaseException exception) {
+        log.error("自定义异常: code={}, msg={}", exception.getCode(), exception.getDefaultMessage(), exception);
+        return ResponseEntity.ok(MapUtil.<String, Object>builder()
+                .put("code", exception.getCode())
+                .put("msg", exception.getDefaultMessage())
+                .build());
+    }
+
+    /**
+     * 处理文件上传超过最大限制异常。
+     * 返回HTTP响应状态码500,包含错误代码和错误消息。
+     *
+     * @param exception 文件上传异常
+     * @return 响应数据,包含错误代码和错误消息
+     */
+    @ExceptionHandler(MaxUploadSizeExceededException.class)
+    public ResponseEntity<Object> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException exception) {
+        log.error("文件上传超过最大限制 -> ", exception);
+
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+                .body(MapUtil.<String, Object>builder()
+                        .put("code", HttpStatus.INTERNAL_SERVER_ERROR.value())
+                        .put("msg", "上传图片大小不能超过5M,格式需为jpg、png、gif")
+                        .build());
+    }
+
+    /**
+     * 处理其他未知异常。
+     * 返回HTTP响应状态码500,包含错误代码和异常堆栈信息。
+     *
+     * @param exception 未知异常
+     * @return 响应数据,包含错误代码和异常堆栈信息
+     */
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<Object> handleUnknownException(Exception exception) {
+        log.error("其他未知异常 -> ", exception);
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+                .body(MapUtil.<String, Object>builder()
+                        .put("code", HttpStatus.INTERNAL_SERVER_ERROR.value())
+                        .put("msg", ExceptionUtil.stacktraceToString(exception))
+                        .build());
+    }
+
+    /**
+     * 处理FileNotFoundException异常。
+     * 返回HTTP响应状态码400,包含错误代码和错误消息。
+     *
+     * @param exception 文件未找到异常
+     * @return 响应数据,包含错误代码和错误消息
+     */
+    @ExceptionHandler(FileNotFoundException.class)
+    public ResponseEntity<Object> handleFileNotFoundException(FileNotFoundException exception) {
+        log.error("文件不存在 -> ", exception);
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+                .body(MapUtil.<String, Object>builder()
+                        .put("code", HttpStatus.BAD_REQUEST.value())
+                        .put("msg", exception.getMessage())
+                        .build());
+    }
+
+    /**
+     * 处理没有权限访问接口异常。
+     * 返回HTTP响应状态码401,包含错误代码和错误消息。
+     *
+     * @param exception 权限访问异常
+     * @return 响应数据,包含错误代码和错误消息
+     */
+    @ExceptionHandler(AccessDeniedException.class)
+    public ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException exception) {
+        log.error("没有权限访问接口异常 -> ", exception);
+        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+                .body(MapUtil.<String, Object>builder()
+                        .put("code", HttpStatus.UNAUTHORIZED.value())
+                        .put("msg", "没有权限访问接口")
+                        .build());
+    }
+
+    /**
+     * 处理运行时异常。
+     * 返回HTTP响应状态码500,包含错误代码和错误消息。
+     *
+     * @param exception 运行时异常
+     * @return 响应数据,包含错误代码和错误消息
+     */
+    @ExceptionHandler(RuntimeException.class)
+    public ResponseEntity<Object> handleRuntimeException(RuntimeException exception) {
+        log.error("运行时异常 -> ", exception);
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+                .body(MapUtil.<String, Object>builder()
+                        .put("code", HttpStatus.INTERNAL_SERVER_ERROR.value())
+                        .put("msg", exception.getMessage())
+                        .build());
+    }
+
+    /**
+     * 处理key重复异常。
+     * 返回HTTP响应状态码200,包含错误代码和错误消息。
+     *
+     * @param exception key重复异常
+     * @return 响应数据,包含错误代码和错误消息
+     */
+    @ExceptionHandler(DuplicateKeyException.class)
+    public ResponseEntity<Object> handleDuplicateKeyException(DuplicateKeyException exception) {
+        log.error("数据重复异常 -> ", exception);
+        return ResponseEntity.status(HttpStatus.OK)
+                .body(MapUtil.<String, Object>builder()
+                        .put("code", HttpStatus.INTERNAL_SERVER_ERROR.value())
+                        .put("msg", "操作失败,数据重复")
+                        .build());
+    }
+}

+ 83 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/exception/ProjectException.java

@@ -0,0 +1,83 @@
+package com.kyl.exception;
+
+
+import com.kyl.base.IBasicEnum;
+
+/**
+ * 自定义异常
+ */
+public class ProjectException extends RuntimeException {
+
+    //错误编码
+    private int code;
+
+    //提示信息
+    private String message;
+
+    //异常接口
+    private IBasicEnum basicEnumIntface;
+
+    public ProjectException() {
+
+    }
+    public ProjectException(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public ProjectException(IBasicEnum errorCode) {
+        setBasicMsg(errorCode);
+    }
+
+
+    public ProjectException(IBasicEnum errorCode, String throwMsg) {
+        super(throwMsg);
+        setBasicMsg(errorCode);
+    }
+
+    public ProjectException(IBasicEnum errorCode, Throwable throwable) {
+        super(throwable);
+        setBasicMsg(errorCode);
+    }
+
+
+    private void setBasicMsg(IBasicEnum basicEnumIntface) {
+        this.code = basicEnumIntface.getCode();
+        this.message = basicEnumIntface.getMsg();
+        this.basicEnumIntface = basicEnumIntface;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public IBasicEnum getBasicEnumIntface() {
+        return basicEnumIntface;
+    }
+
+    public void setBasicEnumIntface(IBasicEnum basicEnumIntface) {
+        this.basicEnumIntface = basicEnumIntface;
+    }
+
+    @Override
+    public String toString() {
+        return "ProjectException{" +
+                "code='" + code + '\'' +
+                ", message='" + message + '\'' +
+                ", basicEnumIntface=" + basicEnumIntface +
+                '}';
+    }
+}

+ 45 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/AjaxResultBuild.java

@@ -0,0 +1,45 @@
+package com.kyl.utils;
+
+import com.kyl.base.IBasicEnum;
+import com.kyl.base.ResponseResult;
+import com.kyl.enums.BasicEnum;
+
+import java.util.Date;
+
+/**
+ * 构造AjaxResult工具
+ */
+public class AjaxResultBuild {
+
+    public static <T> ResponseResult<T> build(IBasicEnum basicEnumIntface, T t){
+
+         //构建对象
+        return ResponseResult.<T>builder()
+            .code(basicEnumIntface.getCode())
+            .msg(basicEnumIntface.getMsg())
+            .operationTime(new Date())
+            .data(t)
+            .build();
+    }
+
+    /**
+     * 公共成功响应方法
+     * @param t
+     * @param <T>
+     * @return
+     */
+    public static <T> ResponseResult<T> successBuild(T t){
+        return AjaxResultBuild.build(BasicEnum.SUCCEED,t);
+    }
+
+    /**
+     * 公共失败响应方法
+     * @param t
+     * @param <T>
+     * @return
+     */
+    public static <T> ResponseResult<T> failedBuild(T t){
+        return AjaxResultBuild.build(BasicEnum.SYSYTEM_FAIL,t);
+    }
+
+}

+ 69 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/AliOSSUtil.java

@@ -0,0 +1,69 @@
+package com.kyl.utils;
+
+import com.aliyun.oss.ClientException;
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.OSSException;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.ByteArrayInputStream;
+
+@Data
+@AllArgsConstructor
+@Slf4j
+public class AliOSSUtil {
+
+    private String endpoint;
+    private String accessKeyId;
+    private String accessKeySecret;
+    private String bucketName;
+
+    /**
+     * 文件上传
+     *
+     * @param bytes
+     * @param objectName
+     * @return
+     */
+    public String upload(byte[] bytes, String objectName) {
+
+        // 创建OSSClient实例。
+        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
+
+        try {
+            // 创建PutObject请求。
+            ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
+        } catch (OSSException oe) {
+            log.error("Caught an OSSException, which means your request made it to OSS, "
+                    + "but was rejected with an error response for some reason.");
+            log.error("Error Message:" + oe.getErrorMessage());
+            log.error("Error Code:" + oe.getErrorCode());
+            log.error("Request ID:" + oe.getRequestId());
+            log.error("Host ID:" + oe.getHostId());
+        } catch (ClientException ce) {
+            log.error("Caught an ClientException, which means the client encountered "
+                    + "a serious internal problem while trying to communicate with OSS, "
+                    + "such as not being able to access the network.");
+            log.error("Error Message:" + ce.getMessage());
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+
+        //文件访问路径规则 https://BucketName.Endpoint/ObjectName
+        StringBuilder stringBuilder = new StringBuilder("https://");
+        stringBuilder
+                .append(bucketName)
+                .append(".")
+                .append(endpoint)
+                .append("/")
+                .append(objectName);
+
+        log.info("文件上传到:{}", stringBuilder.toString());
+
+        return stringBuilder.toString();
+    }
+}

+ 104 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/BeanConv.java

@@ -0,0 +1,104 @@
+package com.kyl.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import ma.glasnost.orika.MapperFacade;
+import ma.glasnost.orika.MapperFactory;
+import ma.glasnost.orika.MappingContext;
+import ma.glasnost.orika.converter.BidirectionalConverter;
+import ma.glasnost.orika.converter.ConverterFactory;
+import ma.glasnost.orika.impl.DefaultMapperFactory;
+import ma.glasnost.orika.metadata.Type;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+
+/***
+ * 对象转换工具,当对象成员变量属性:名称及类型相同时候会自动填充其值
+ */
+@Slf4j
+public class BeanConv {
+
+    private static MapperFacade mapper;
+
+    private static MapperFacade notNullMapper;
+
+    static {
+        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
+        ConverterFactory converterFactory = mapperFactory.getConverterFactory();
+        converterFactory.registerConverter(new LocalDateTimeConverter());
+        converterFactory.registerConverter(new LocalDateConverter());
+        converterFactory.registerConverter(new LocalTimeConverter());
+        mapper = mapperFactory.getMapperFacade();
+        MapperFactory notNullMapperFactory = new DefaultMapperFactory.Builder().mapNulls(false).build();
+        notNullMapper = notNullMapperFactory.getMapperFacade();
+    }
+
+    private static class LocalDateTimeConverter extends BidirectionalConverter<LocalDateTime, LocalDateTime> {
+
+        @Override
+        public LocalDateTime convertTo(LocalDateTime localDateTime, Type<LocalDateTime> type, MappingContext mappingContext) {
+            return LocalDateTime.from(localDateTime);
+        }
+
+        @Override
+        public LocalDateTime convertFrom(LocalDateTime localDateTime, Type<LocalDateTime> type, MappingContext mappingContext) {
+            return LocalDateTime.from(localDateTime);
+        }
+    }
+    private static class LocalDateConverter extends BidirectionalConverter<LocalDate, LocalDate> {
+        @Override
+        public LocalDate convertTo(LocalDate localDate, Type<LocalDate> type, MappingContext mappingContext) {
+            return LocalDate.from(localDate);
+        }
+
+        @Override
+        public LocalDate convertFrom(LocalDate localDate, Type<LocalDate> type, MappingContext mappingContext) {
+            return LocalDate.from(localDate);
+        }
+    }
+    private static class LocalTimeConverter extends BidirectionalConverter<LocalTime, LocalTime> {
+
+        @Override
+        public LocalTime convertTo(LocalTime localTime, Type<LocalTime> type, MappingContext mappingContext) {
+            return LocalTime.from(localTime);
+        }
+
+        @Override
+        public LocalTime convertFrom(LocalTime localTime, Type<LocalTime> type, MappingContext mappingContext) {
+            return LocalTime.from(localTime);
+        }
+    }
+
+
+    /***
+     *  深度复制对象
+     *
+     * @param source 源对象
+     * @param destinationClass 目标类型
+     * @return
+     */
+    public static <T> T toBean(Object source, Class<T> destinationClass) {
+        if (EmptyUtil.isNullOrEmpty(source)){
+            return null;
+        }
+        return mapper.map(source, destinationClass);
+    }
+
+    /***
+     *  复制List
+     *
+     * @param sourceList 源list对象
+     * @param destinationClass 目标类型
+     * @return
+     */
+    public static <T> List<T> toBeanList(List<?> sourceList, Class<T> destinationClass) {
+        if (EmptyUtil.isNullOrEmpty(sourceList)){
+            return new ArrayList<>();
+        }
+        return mapper.mapAsList(sourceList,destinationClass);
+    }
+
+}

+ 52 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/ClientIpUtil.java

@@ -0,0 +1,52 @@
+package com.kyl.utils;
+
+import com.google.common.net.InetAddresses;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 请求人的ip
+ * @author WanJl
+ */
+@Slf4j
+public class ClientIpUtil {
+
+    private static final String HN_CND_SRC_IP = "cdn-src-ip";
+    private static final String HN_X_REAL_IP = "x-real-ip";
+    private static final String HN_X_FORWARDED_FOR = "x-forwarded-for";
+    private static final String SR_IP = ",";
+    private static final String IP_UNKNOWN = "127.0.0.1";
+
+
+    public static String clientIp(HttpServletRequest request) {
+        String ip = null;
+        String cdnSrcIp = RequestUtil.headerValue(request, "cdn-src-ip");
+        String realIp = RequestUtil.headerValue(request, "x-real-ip");
+        String forwardedFor = RequestUtil.headerValue(request, "x-forwarded-for");
+        String remoteAddr = request.getRemoteAddr();
+        log.info("cdnSrcIp:{}, realIp:{}, forwardedFor:{}, remoteAddr:{}", cdnSrcIp, realIp, forwardedFor, remoteAddr);
+        if (StringUtils.isNotBlank(cdnSrcIp)) {
+            ip = ip(cdnSrcIp);
+        } else if (StringUtils.isNotBlank(realIp)) {
+            ip = ip(realIp);
+        } else if (StringUtils.isNotBlank(forwardedFor)) {
+            ip = ip(forwardedFor);
+        } else if (StringUtils.isNotBlank(remoteAddr)) {
+            ip = remoteAddr;
+        }
+
+        ip = ipValid(ip) ? ip : "127.0.0.1";
+        log.debug("client_ip:{}", ip);
+        return ip;
+    }
+
+    private static String ip(String ips) {
+        return StringUtils.split(ips, ",")[0];
+    }
+
+    private static boolean ipValid(String ip) {
+        return StringUtils.isNotBlank(ip) && InetAddresses.isInetAddress(ip);
+    }
+}

+ 60 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/CodeUtil.java

@@ -0,0 +1,60 @@
+package com.kyl.utils;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.time.LocalDateTime;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author WanJl
+ */
+public class CodeUtil {
+
+    /**
+     * code生成
+     * 样例:TZ 20230630 1010 0001
+     *
+     * @param prefix        code前缀  比如:退住(TZ)、合同(HT)
+     * @param redisTemplate code后四位根据redis自增生成
+     * @param expire        生成自增后的失效时间,
+     * @return  code码
+     */
+    public static String generateCode(String prefix, RedisTemplate<String,String> redisTemplate, long expire) {
+        String dateStr = LocalDateTimeUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss");
+        String code = prefix + dateStr;
+        Long increment = redisTemplate.boundValueOps(code).increment();
+        //设置过期时间
+        if(expire == 0) {
+            expire = 5;
+        }
+        redisTemplate.expire(code, expire, TimeUnit.SECONDS);
+        //最终生成的code吗
+        return code += increment;
+    }
+
+    /**
+     * 生成具有给定前缀和时间戳的编码。
+     *
+     * @param prefix        编码前缀,例如 "TZ" 表示退住或 "HT" 表示合同。
+     * @param redisTemplate 用于生成代码末尾四位数字的 Redis 模板。
+     * @param expireSeconds 生成的编码的过期时间(秒)。
+     * @return 生成的编码。
+     */
+    public static String generateCode2(String prefix, RedisTemplate<String, String> redisTemplate, long expireSeconds) {
+        String dateStr = LocalDateTimeUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss");
+        String code = prefix + dateStr;
+
+        // 从 Redis 中递增并获取编码的值,使其唯一。
+        Long increment = redisTemplate.boundValueOps(code).increment();
+
+        // 设置编码在 Redis 中的过期时间。
+        if (expireSeconds <= 0) {
+            expireSeconds = 5; // 如果未提供,默认过期时间为 5 秒。
+        }
+        redisTemplate.expire(code, expireSeconds, TimeUnit.SECONDS);
+
+        // 最终带有递增值的编码。
+        return code + increment;
+    }
+}

+ 18 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/ConvertHandler.java

@@ -0,0 +1,18 @@
+package com.kyl.utils;
+
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ConvertHandler
+ * @description 特殊类型转换器
+ * @create 2024/12/25
+ */
+public interface ConvertHandler<O, T> {
+    /**
+     * 特殊对象类型转换
+     * @param originObject 源对象
+     * @param targetObject 目标对象
+     */
+    void map(O originObject, T targetObject);
+}

+ 124 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/EmptyUtil.java

@@ -0,0 +1,124 @@
+package com.kyl.utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 判断对象是否为空的工具类
+ */
+public abstract class EmptyUtil {
+
+	/***
+	 * 对string字符串是否为空判断
+	 *
+	 * @param str 被判定字符串
+	 * @return
+	 */
+	public static boolean isNullOrEmpty(String str) {
+		if (str == null || "".equals(str.trim()) || "null".equalsIgnoreCase(str.trim()) || "undefined".equalsIgnoreCase(str.trim())) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/***
+	 *  对于StringBuffer类型的非空判断
+	 *
+	 * @param str 被判定StringBuffer
+	 * @return
+	 */
+	public static boolean isNullOrEmpty(StringBuffer str) {
+		return (str == null || str.length() == 0);
+	}
+
+	/***
+	 *  对于string数组类型的非空判断
+	 *
+	 * @param str 被判定字符串数组
+	 * @return
+	 */
+	public static boolean isNullOrEmpty(String[] str) {
+		if (str == null || str.length == 0) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/***
+	 *  对于Object类型的非空判断
+	 *
+	 * @param obj 被判定对象
+	 * @return
+	 */
+	public static boolean isNullOrEmpty(Object obj) {
+		if (obj == null || "".equals(obj)) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/***
+	 *  对于Object数组类型的非空判断
+	 *
+	 * @param obj 被判定对象数组
+	 * @return
+	 */
+	public static boolean isNullOrEmpty(Object[] obj) {
+		if (obj == null || obj.length == 0) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/***
+	 *  对于Collection类型的非空判断
+	 *
+	 * @param collection 被判定Collection类型对象
+	 * @return
+	 */
+	public static boolean isNullOrEmpty(Collection collection) {
+		if (collection == null || collection.isEmpty()) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * @方法名:对于Map类型的非空判断
+	 * @功能说明:对于Map类型的非空判断
+	 * @return boolean true-为空,false-不为空
+	 * @throws
+	 */
+	@SuppressWarnings("rawtypes")
+	public static boolean isNullOrEmpty( Map map) {
+		if (map == null || map.isEmpty()) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *
+	 * @方法名:removeNullUnit
+	 * @功能说明: 删除集合中的空元素
+	 * @return
+	 */
+	public static <T> List<T> removeNullUnit(List<T> xllxList) {
+		List<T> need = new ArrayList<T>();
+		for (int i = 0; i < xllxList.size(); i++) {
+			if (!isNullOrEmpty(xllxList.get(i))) {
+				need.add(xllxList.get(i));
+			}
+		}
+		return need;
+	}
+
+}

+ 81 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/ExceptionsUtil.java

@@ -0,0 +1,81 @@
+package com.kyl.utils;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ *  异常转换工具
+ */
+public class ExceptionsUtil {
+
+	/**
+	 *
+	 * <b>方法名:</b>:unchecked<br>
+	 * <b>功能说明:</b>:将CheckedException转换为UncheckedException.<br>
+	 */
+	public static RuntimeException unchecked(Exception e) {
+		if (e instanceof RuntimeException) {
+			return (RuntimeException) e;
+		} else {
+			return new RuntimeException(e);
+		}
+	}
+
+	/**
+	 *
+	 * <b>方法名:</b>:getStackTraceAsString<br>
+	 * <b>功能说明:</b>:将ErrorStack转化为String<br>
+	 */
+	public static String getStackTraceAsString(Exception e) {
+		StringWriter stringWriter = new StringWriter();
+		e.printStackTrace(new PrintWriter(stringWriter));
+		return stringWriter.toString();
+	}
+
+	/**
+	 *
+	 * <b>方法名:</b>:getErrorMessageWithNestedException<br>
+	 * <b>功能说明:</b>:获取组合本异常信息与底层异常信息的异常描述, 适用于本异常为统一包装异常类,底层异常才是根本原因的情况<br>
+	 */
+	public static String getErrorMessageWithNestedException(Exception e) {
+		Throwable nestedException = e.getCause();
+		return new StringBuilder().append(e.getMessage()).append(" nested exception is ")
+				.append(nestedException.getClass().getName()).append(":").append(nestedException.getMessage())
+				.toString();
+	}
+
+	/**
+	 *
+	 * <b>方法名:</b>:isCausedBy<br>
+	 * <b>功能说明:</b>:判断异常是否由某些底层的异常引起<br>
+	 */
+	public static boolean isCausedBy(Exception ex, @SuppressWarnings("unchecked") Class<? extends Exception>... causeExceptionClasses) {
+		Throwable cause = ex;
+		while (cause != null) {
+			for (Class<? extends Exception> causeClass : causeExceptionClasses) {
+				if (causeClass.isInstance(cause)) {
+					return true;
+				}
+			}
+			cause = cause.getCause();
+		}
+		return false;
+	}
+
+	/**
+	 *
+	 * <b>方法名:</b>:convertReflectionExceptionToUnchecked<br>
+	 * <b>功能说明:</b>:将反射时的checked exception转换为unchecked exception.<br>
+	 */
+	public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
+		if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException || e instanceof NoSuchMethodException) {
+			return new IllegalArgumentException("Reflection Exception.", e);
+		} else if (e instanceof InvocationTargetException) {
+			return new RuntimeException("Reflection Exception.", ((InvocationTargetException) e).getTargetException());
+		} else if (e instanceof RuntimeException) {
+			return (RuntimeException) e;
+		}
+		return new RuntimeException("Unexpected Checked Exception.", e);
+	}
+}

+ 92 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/HttpStatus.java

@@ -0,0 +1,92 @@
+package com.kyl.utils;
+
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title HttpStatus
+ * @description 状态码
+ * @create 2024/12/25
+ */
+public class HttpStatus {
+    /**
+     * 操作成功
+     */
+    public static final int SUCCESS = 200;
+
+    /**
+     * 对象创建成功
+     */
+    public static final int CREATED = 201;
+
+    /**
+     * 请求已经被接受
+     */
+    public static final int ACCEPTED = 202;
+
+    /**
+     * 操作已经执行成功,但是没有返回数据
+     */
+    public static final int NO_CONTENT = 204;
+
+    /**
+     * 资源已被移除
+     */
+    public static final int MOVED_PERM = 301;
+
+    /**
+     * 重定向
+     */
+    public static final int SEE_OTHER = 303;
+
+    /**
+     * 资源没有被修改
+     */
+    public static final int NOT_MODIFIED = 304;
+
+    /**
+     * 参数列表错误(缺少,格式不匹配)
+     */
+    public static final int BAD_REQUEST = 400;
+
+    /**
+     * 未授权
+     */
+    public static final int UNAUTHORIZED = 401;
+
+    /**
+     * 访问受限,授权过期
+     */
+    public static final int FORBIDDEN = 403;
+
+    /**
+     * 资源,服务未找到
+     */
+    public static final int NOT_FOUND = 404;
+
+    /**
+     * 不允许的http方法
+     */
+    public static final int BAD_METHOD = 405;
+
+    /**
+     * 资源冲突,或者资源被锁
+     */
+    public static final int CONFLICT = 409;
+
+    /**
+     * 不支持的数据,媒体类型
+     */
+    public static final int UNSUPPORTED_TYPE = 415;
+
+    /**
+     * 系统内部错误
+     */
+    public static final int ERROR = 500;
+
+    /**
+     * 接口未实现
+     */
+    public static final int NOT_IMPLEMENTED = 501;
+}
+

+ 58 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/JwtUtil.java

@@ -0,0 +1,58 @@
+package com.kyl.utils;
+
+import cn.hutool.core.date.DateField;
+import cn.hutool.core.date.DateUtil;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Date;
+import java.util.Map;
+
+public class JwtUtil {
+    /**
+     * 生成jwt
+     * 使用Hs256算法, 私匙使用固定秘钥
+     *
+     * @param secretKey jwt秘钥(Base64编码格式,使用前自动解码)
+     * @param dateOffset jwt过期时间(小时)
+     * @param claims    设置的信息
+     * @return
+     */
+    public static String createJWT(String secretKey , int dateOffset, Map<String, Object> claims) {
+        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
+
+        // Base64解码密钥(配置中存储的是Base64编码的32字节密钥)
+        byte[] keyBytes = Base64.getDecoder().decode(secretKey);
+
+        JwtBuilder builder = Jwts.builder()
+                .setClaims(claims)
+                .signWith(signatureAlgorithm, keyBytes)
+                .setExpiration(DateUtil.offset(new Date(), DateField.HOUR_OF_DAY, dateOffset));
+
+        return builder.compact();
+    }
+
+    /**
+     * Token解密
+     *
+     * @param secretKey jwt秘钥(Base64编码格式)
+     * @param token     加密后的token
+     * @return
+     */
+    public static Claims parseJWT(String secretKey, String token) {
+        try {
+            byte[] keyBytes = Base64.getDecoder().decode(secretKey);
+            Claims claims = Jwts.parser()
+                    .setSigningKey(keyBytes)
+                    .parseClaimsJws(token).getBody();
+            return claims;
+        } catch (Exception e) {
+            throw new RuntimeException("没有权限,请登录");
+        }
+    }
+
+}

+ 74 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/NoProcessing.java

@@ -0,0 +1,74 @@
+package com.kyl.utils;
+
+/**
+ *  工具类补全
+ */
+public class NoProcessing {
+
+    /***
+     *  处理补全编号,所有编号都是15位,共5层,每层关系如下:
+     * 第一层:100000000000000
+     * 第二层:100001000000000
+     * 第三层:100001001000000
+     * 第四层:100001001001000
+     * 第五层:100001001001001
+     * @param input 如果为001000000000000处理完成后则变为001
+     * @return
+ * @return: java.lang.String
+     */
+    public static String processString(String input) {
+        int step = input.length() / 3;
+        for (int i =0;i<step;i++ ){
+            String targetString = input.substring(input.length()-3,input.length());
+            if ("000".equals(targetString)){
+                input = input.substring(0,input.length()-3);
+            }else {
+                break;
+            }
+        }
+        return input;
+    }
+
+    public static void main(String[] args) {
+//        String input = "100001001001000";
+        String input = "100000000000000";
+        String processedString = createNo(input,false);
+        System.out.println(processedString);
+    }
+
+    /***
+     *  生产层级编号
+     * @param input 输入编号
+     * @param peerNode 是否下属节点
+     * @return
+     * @return: java.lang.String
+     */
+    public static String createNo(String input,boolean peerNode) {
+        int step = input.length() / 3;
+        int supplement = 0;
+        for (int i =0;i<step;i++ ){
+            String targetString = input.substring(input.length()-3,input.length());
+            if ("000".equals(targetString)){
+                input = input.substring(0,input.length()-3);
+                supplement++;
+            }else {
+                break;
+            }
+        }
+        if (peerNode){
+            input = String.valueOf(Long.valueOf(input) + 1L);
+            for (int i =0;i<supplement;i++ ){
+                input = input+"000";
+            }
+        }else {
+            input = String.valueOf(Long.valueOf(input+"001"));
+            for (int i =0;i<supplement-1;i++ ){
+                input = input+"000";
+            }
+        }
+        return input;
+    }
+
+
+
+}

+ 30 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/ObjectUtil.java

@@ -0,0 +1,30 @@
+package com.kyl.utils;
+
+import cn.hutool.core.util.ArrayUtil;
+
+/**
+ * 扩展hutool中的方法
+ */
+public class ObjectUtil extends cn.hutool.core.util.ObjectUtil {
+
+    /**
+     * 给定对象是否与提供的中任一对象相同,相同则返回{@code true},没有相同的返回{@code false}<br>
+     * 如果参与比对的对象列表为空,返回{@code false}
+     *
+     * @param obj1 给定需要检查的对象
+     * @param objs 需要参与比对的对象列表
+     * @return 是否相同
+     */
+    public static boolean equalsAny(Object obj1, Object... objs) {
+        if (ArrayUtil.isEmpty(objs)) {
+            return false;
+        }
+        for (Object obj : objs) {
+            if (cn.hutool.core.util.ObjectUtil.equal(obj1, obj)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

+ 56 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/RegisterBeanHandler.java

@@ -0,0 +1,56 @@
+package com.kyl.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.stereotype.Component;
+
+/**
+ * IOC工厂处理工具
+ */
+@Slf4j
+@Component
+public class RegisterBeanHandler {
+
+    ConfigurableApplicationContext configurableApplicationContext;
+
+    @Autowired
+    public RegisterBeanHandler(ConfigurableApplicationContext configurableApplicationContext) {
+        this.configurableApplicationContext = configurableApplicationContext;
+    }
+
+    /**
+     * 注册bean
+     * @param beanName bean的id
+     * @param bean 实现类
+     */
+    public <T> void registerBean(String beanName, T bean) {
+        // 将bean对象注册到bean工厂
+        configurableApplicationContext.getBeanFactory().registerSingleton(beanName, bean);
+    }
+
+    /**
+     * 移除bean
+     * @param beanName bean的id
+     */
+    public void unregisterBean(String beanName){
+        ((DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory()).removeBeanDefinition(beanName);
+    }
+
+    /**
+     * 获得bean
+     * @param beanName bean的id
+     */
+    public <T> T getBean(String beanName, Class<T> t) {
+        return configurableApplicationContext.getBean(beanName,t);
+    }
+
+    /**
+     * bean是否存在
+     * @param beanName bean的id
+     */
+    public boolean containsBean(String beanName) {
+        return configurableApplicationContext.containsBean(beanName);
+    }
+}

+ 23 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/RequestUtil.java

@@ -0,0 +1,23 @@
+package com.kyl.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Slf4j
+public class RequestUtil {
+
+    private static final String HN_REFERER = "referer";
+
+    public static String headerValue(HttpServletRequest request, String name) {
+        return request.getHeader(name);
+    }
+
+    public static String parameterValue(HttpServletRequest request, String name) {
+        return request.getParameter(name);
+    }
+
+    public static String referer(HttpServletRequest request) {
+        return request.getHeader("referer");
+    }
+}

+ 126 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/SnowflakeIdWorker.java

@@ -0,0 +1,126 @@
+package com.kyl.utils;
+
+/**
+ *
+ */
+public class SnowflakeIdWorker {
+    // ==============================Fields===========================================
+    /** 开始时间截 (2020-08-28) */
+    private final long twepoch = 1598598185157L;
+
+    /** 机器id所占的位数 */
+    private final long workerIdBits = 5L;
+
+    /** 数据标识id所占的位数 */
+    private final long datacenterIdBits = 5L;
+
+    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
+    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
+
+    /** 支持的最大数据标识id,结果是31 */
+    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
+
+    /** 序列在id中占的位数 */
+    private final long sequenceBits = 12L;
+
+    /** 机器ID向左移12位 */
+    private final long workerIdShift = sequenceBits;
+
+    /** 数据标识id向左移17位(12+5) */
+    private final long datacenterIdShift = sequenceBits + workerIdBits;
+
+    /** 时间截向左移22位(5+5+12) */
+    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
+
+    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
+    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
+
+    /** 工作机器ID(0~31) */
+    private long workerId =1;
+
+    /** 数据中心ID(0~31) */
+    private long datacenterId;
+
+    /** 毫秒内序列(0~4095) */
+    private long sequence = 0L;
+
+    /** 上次生成ID的时间截 */
+    private long lastTimestamp = -1L;
+
+    //==============================Constructors=====================================
+    /**
+     * 构造函数
+     * @param workerId 工作ID (0~31)
+     * @param datacenterId 数据中心ID (0~31)
+     */
+    public SnowflakeIdWorker(long workerId, long datacenterId) {
+        if (workerId > maxWorkerId || workerId < 0) {
+            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
+        }
+        if (datacenterId > maxDatacenterId || datacenterId < 0) {
+            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
+        }
+        this.workerId = workerId;
+        this.datacenterId = datacenterId;
+    }
+
+    // ==============================Methods==========================================
+    /**
+     * 获得下一个ID (该方法是线程安全的)
+     * @return SnowflakeId
+     */
+    public synchronized long nextId() {
+        long timestamp = timeGen();
+
+        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
+        if (timestamp < lastTimestamp) {
+            throw new RuntimeException(
+                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
+        }
+
+        //分表节点数
+        sequence = (int)(Math.random()*3);
+
+        //上次生成ID的时间截
+        lastTimestamp = timestamp;
+
+        //移位并通过或运算拼到一起组成64位的ID
+         long id = ((timestamp - twepoch) << timestampLeftShift) //
+                | (datacenterId << datacenterIdShift) //
+                | (workerId << workerIdShift) //
+                | sequence;
+        return id;
+    }
+
+    /**
+     * 阻塞到下一个毫秒,直到获得新的时间戳
+     * @param lastTimestamp 上次生成ID的时间截
+     * @return 当前时间戳
+     */
+    protected long tilNextMillis(long lastTimestamp) {
+        long timestamp = timeGen();
+        while (timestamp <= lastTimestamp) {
+            timestamp = timeGen();
+        }
+        return timestamp;
+    }
+
+    /**
+     * 返回以毫秒为单位的当前时间
+     * @return 当前时间(毫秒)
+     */
+    protected long timeGen() {
+        return System.currentTimeMillis();
+    }
+
+    //==============================Test=============================================
+    /** 测试 */
+    public static void main(String[] args) throws InterruptedException {
+        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 1);
+        for (int i = 0; i < 1000; i++) {
+            long id = idWorker.nextId();
+            Thread.sleep(1);
+            System.out.println(id);
+        }
+    }
+}

+ 385 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/StringUtils.java

@@ -0,0 +1,385 @@
+package com.kyl.utils;
+
+
+import org.apache.commons.lang3.RandomStringUtils;
+
+import java.util.*;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title StringUtils
+ * @description 字符串工具类
+ * @create 2024/12/25
+ */
+public class StringUtils extends org.apache.commons.lang3.StringUtils {
+
+    /**
+     * 空字符串
+     */
+    private static final String NULLSTR = "";
+
+    /**
+     * 下划线
+     */
+    private static final char SEPARATOR = '_';
+
+    /**
+     * 获取参数不为空值
+     *
+     * @param value defaultValue 要判断的value
+     * @return value 返回值
+     */
+    public static <T> T nvl(T value, T defaultValue) {
+        return value != null ? value : defaultValue;
+    }
+
+    /**
+     * * 判断一个Collection是否为空, 包含List,Set,Queue
+     *
+     * @param coll 要判断的Collection
+     * @return true:为空 false:非空
+     */
+    public static boolean isEmpty(Collection<?> coll) {
+        return isNull(coll) || coll.isEmpty();
+    }
+
+    /**
+     * * 判断一个Collection是否非空,包含List,Set,Queue
+     *
+     * @param coll 要判断的Collection
+     * @return true:非空 false:空
+     */
+    public static boolean isNotEmpty(Collection<?> coll) {
+        return !isEmpty(coll);
+    }
+
+    /**
+     * * 判断一个对象数组是否为空
+     *
+     * @param objects 要判断的对象数组
+     *                * @return true:为空 false:非空
+     */
+    public static boolean isEmpty(Object[] objects) {
+        return isNull(objects) || (objects.length == 0);
+    }
+
+    /**
+     * * 判断一个对象数组是否非空
+     *
+     * @param objects 要判断的对象数组
+     * @return true:非空 false:空
+     */
+    public static boolean isNotEmpty(Object[] objects) {
+        return !isEmpty(objects);
+    }
+
+    /**
+     * * 判断一个Map是否为空
+     *
+     * @param map 要判断的Map
+     * @return true:为空 false:非空
+     */
+    public static boolean isEmpty(Map<?, ?> map) {
+        return isNull(map) || map.isEmpty();
+    }
+
+    /**
+     * * 判断一个Map是否为空
+     *
+     * @param map 要判断的Map
+     * @return true:非空 false:空
+     */
+    public static boolean isNotEmpty(Map<?, ?> map) {
+        return !isEmpty(map);
+    }
+
+    /**
+     * * 判断一个字符串是否为空串
+     *
+     * @param str String
+     * @return true:为空 false:非空
+     */
+    public static boolean isEmpty(String str) {
+        return isNull(str) || NULLSTR.equals(str.trim());
+    }
+
+    /**
+     * * 判断一个字符串是否为非空串
+     *
+     * @param str String
+     * @return true:非空串 false:空串
+     */
+    public static boolean isNotEmpty(String str) {
+        return !isEmpty(str);
+    }
+
+    /**
+     * * 判断一个对象是否为空
+     *
+     * @param object Object
+     * @return true:为空 false:非空
+     */
+    public static boolean isNull(Object object) {
+        return object == null;
+    }
+
+    /**
+     * * 判断一个对象是否非空
+     *
+     * @param object Object
+     * @return true:非空 false:空
+     */
+    public static boolean isNotNull(Object object) {
+        return !isNull(object);
+    }
+
+    /**
+     * * 判断一个对象是否是数组类型(Java基本型别的数组)
+     *
+     * @param object 对象
+     * @return true:是数组 false:不是数组
+     */
+    public static boolean isArray(Object object) {
+        return isNotNull(object) && object.getClass().isArray();
+    }
+
+    /**
+     * 去空格
+     */
+    public static String trim(String str) {
+        return (str == null ? "" : str.trim());
+    }
+
+    /**
+     * 截取字符串
+     *
+     * @param str   字符串
+     * @param start 开始
+     * @return 结果
+     */
+    public static String substring(final String str, int start) {
+        if (str == null) {
+            return NULLSTR;
+        }
+
+        if (start < 0) {
+            start = str.length() + start;
+        }
+
+        if (start < 0) {
+            start = 0;
+        }
+        if (start > str.length()) {
+            return NULLSTR;
+        }
+
+        return str.substring(start);
+    }
+
+    /**
+     * 截取字符串
+     *
+     * @param str   字符串
+     * @param start 开始
+     * @param end   结束
+     * @return 结果
+     */
+    public static String substring(final String str, int start, int end) {
+        if (str == null) {
+            return NULLSTR;
+        }
+
+        if (end < 0) {
+            end = str.length() + end;
+        }
+        if (start < 0) {
+            start = str.length() + start;
+        }
+
+        if (end > str.length()) {
+            end = str.length();
+        }
+
+        if (start > end) {
+            return NULLSTR;
+        }
+
+        if (start < 0) {
+            start = 0;
+        }
+        if (end < 0) {
+            end = 0;
+        }
+
+        return str.substring(start, end);
+    }
+
+
+    /**
+     * 字符串转set
+     *
+     * @param str 字符串
+     * @param sep 分隔符
+     * @return set集合
+     */
+    public static final Set<String> str2Set(String str, String sep) {
+        return new HashSet<String>(str2List(str, sep, true, false));
+    }
+
+    /**
+     * 字符串转list
+     *
+     * @param str         字符串
+     * @param sep         分隔符
+     * @param filterBlank 过滤纯空白
+     * @param trim        去掉首尾空白
+     * @return list集合
+     */
+    public static final List<String> str2List(String str, String sep, boolean filterBlank, boolean trim) {
+        List<String> list = new ArrayList<String>();
+        if (StringUtils.isEmpty(str)) {
+            return list;
+        }
+
+        // 过滤空白字符串
+        if (filterBlank && StringUtils.isBlank(str)) {
+            return list;
+        }
+        String[] split = str.split(sep);
+        for (String string : split) {
+            if (filterBlank && StringUtils.isBlank(string)) {
+                continue;
+            }
+            if (trim) {
+                string = string.trim();
+            }
+            list.add(string);
+        }
+
+        return list;
+    }
+
+    /**
+     * 下划线转驼峰命名
+     */
+    public static String toUnderScoreCase(String str) {
+        if (str == null) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        // 前置字符是否大写
+        boolean preCharIsUpperCase = true;
+        // 当前字符是否大写
+        boolean curreCharIsUpperCase = true;
+        // 下一字符是否大写
+        boolean nexteCharIsUpperCase = true;
+        for (int i = 0; i < str.length(); i++) {
+            char c = str.charAt(i);
+            if (i > 0) {
+                preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
+            } else {
+                preCharIsUpperCase = false;
+            }
+
+            curreCharIsUpperCase = Character.isUpperCase(c);
+
+            if (i < (str.length() - 1)) {
+                nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
+            }
+
+            if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) {
+                sb.append(SEPARATOR);
+            } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) {
+                sb.append(SEPARATOR);
+            }
+            sb.append(Character.toLowerCase(c));
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * 是否包含字符串
+     *
+     * @param str  验证字符串
+     * @param strs 字符串组
+     * @return 包含返回true
+     */
+    public static boolean inStringIgnoreCase(String str, String... strs) {
+        if (str != null && strs != null) {
+            for (String s : strs) {
+                if (str.equalsIgnoreCase(trim(s))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
+     *
+     * @param name 转换前的下划线大写方式命名的字符串
+     * @return 转换后的驼峰式命名的字符串
+     */
+    public static String convertToCamelCase(String name) {
+        StringBuilder result = new StringBuilder();
+        // 快速检查
+        if (name == null || name.isEmpty()) {
+            // 没必要转换
+            return "";
+        } else if (!name.contains("_")) {
+            // 不含下划线,仅将首字母大写
+            return name.substring(0, 1).toUpperCase() + name.substring(1);
+        }
+        // 用下划线将原始字符串分割
+        String[] camels = name.split("_");
+        for (String camel : camels) {
+            // 跳过原始字符串中开头、结尾的下换线或双重下划线
+            if (camel.isEmpty()) {
+                continue;
+            }
+            // 首字母大写
+            result.append(camel.substring(0, 1).toUpperCase());
+            result.append(camel.substring(1).toLowerCase());
+        }
+        return result.toString();
+    }
+
+    /**
+     * 驼峰式命名法 例如:user_name->userName
+     */
+    public static String toCamelCase(String s) {
+        if (s == null) {
+            return null;
+        }
+        s = s.toLowerCase();
+        StringBuilder sb = new StringBuilder(s.length());
+        boolean upperCase = false;
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+
+            if (c == SEPARATOR) {
+                upperCase = true;
+            } else if (upperCase) {
+                sb.append(Character.toUpperCase(c));
+                upperCase = false;
+            } else {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T cast(Object obj) {
+        return (T) obj;
+    }
+
+    public static String getRandom(int weight) {
+        return RandomStringUtils.random(weight,
+                new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 's',
+                        't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9'});
+    }
+}

+ 439 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/UUID.java

@@ -0,0 +1,439 @@
+package com.kyl.utils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+
+
+/**
+ * 提供通用唯一识别码(universally unique identifier)(UUID)实现
+ * @author WanJl
+ **/
+public final class UUID implements java.io.Serializable, Comparable<UUID> {
+    private static final long serialVersionUID = -1185015143654744140L;
+
+    /**
+     * SecureRandom 的单例
+     */
+    private static class Holder {
+        static final SecureRandom numberGenerator = getSecureRandom();
+    }
+
+    /**
+     * 此UUID的最高64有效位
+     */
+    private final long mostSigBits;
+
+    /**
+     * 此UUID的最低64有效位
+     */
+    private final long leastSigBits;
+
+    /**
+     * 私有构造
+     *
+     * @param data 数据
+     */
+    private UUID(byte[] data) {
+        long msb = 0;
+        long lsb = 0;
+        assert data.length == 16 : "data must be 16 bytes in length";
+        for (int i = 0; i < 8; i++) {
+            msb = (msb << 8) | (data[i] & 0xff);
+        }
+        for (int i = 8; i < 16; i++) {
+            lsb = (lsb << 8) | (data[i] & 0xff);
+        }
+        this.mostSigBits = msb;
+        this.leastSigBits = lsb;
+    }
+
+    /**
+     * 使用指定的数据构造新的 UUID。
+     *
+     * @param mostSigBits  用于 {@code UUID} 的最高有效 64 位
+     * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位
+     */
+    public UUID(long mostSigBits, long leastSigBits) {
+        this.mostSigBits = mostSigBits;
+        this.leastSigBits = leastSigBits;
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。
+     *
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID fastUUID() {
+        return randomUUID(false);
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
+     *
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID randomUUID() {
+        return randomUUID(true);
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
+     *
+     * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID randomUUID(boolean isSecure) {
+        final Random ng = isSecure ? Holder.numberGenerator : getRandom();
+
+        byte[] randomBytes = new byte[16];
+        ng.nextBytes(randomBytes);
+        randomBytes[6] &= 0x0f; /* clear version */
+        randomBytes[6] |= 0x40; /* set to version 4 */
+        randomBytes[8] &= 0x3f; /* clear variant */
+        randomBytes[8] |= 0x80; /* set to IETF variant */
+        return new UUID(randomBytes);
+    }
+
+    /**
+     * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。
+     *
+     * @param name 用于构造 UUID 的字节数组。
+     * @return 根据指定数组生成的 {@code UUID}
+     */
+    public static UUID nameUUIDFromBytes(byte[] name) {
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new InternalError("MD5 not supported");
+        }
+        byte[] md5Bytes = md.digest(name);
+        md5Bytes[6] &= 0x0f; /* clear version */
+        md5Bytes[6] |= 0x30; /* set to version 3 */
+        md5Bytes[8] &= 0x3f; /* clear variant */
+        md5Bytes[8] |= 0x80; /* set to IETF variant */
+        return new UUID(md5Bytes);
+    }
+
+    /**
+     * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。
+     *
+     * @param name 指定 {@code UUID} 字符串
+     * @return 具有指定值的 {@code UUID}
+     * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常
+     */
+    public static UUID fromString(String name) {
+        String[] components = name.split("-");
+        if (components.length != 5) {
+            throw new IllegalArgumentException("Invalid UUID string: " + name);
+        }
+        for (int i = 0; i < 5; i++) {
+            components[i] = "0x" + components[i];
+        }
+
+        long mostSigBits = Long.decode(components[0]).longValue();
+        mostSigBits <<= 16;
+        mostSigBits |= Long.decode(components[1]).longValue();
+        mostSigBits <<= 16;
+        mostSigBits |= Long.decode(components[2]).longValue();
+
+        long leastSigBits = Long.decode(components[3]).longValue();
+        leastSigBits <<= 48;
+        leastSigBits |= Long.decode(components[4]).longValue();
+
+        return new UUID(mostSigBits, leastSigBits);
+    }
+
+    /**
+     * 返回此 UUID 的 128 位值中的最低有效 64 位。
+     *
+     * @return 此 UUID 的 128 位值中的最低有效 64 位。
+     */
+    public long getLeastSignificantBits() {
+        return leastSigBits;
+    }
+
+    /**
+     * 返回此 UUID 的 128 位值中的最高有效 64 位。
+     *
+     * @return 此 UUID 的 128 位值中最高有效 64 位。
+     */
+    public long getMostSignificantBits() {
+        return mostSigBits;
+    }
+
+    /**
+     * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。
+     * <p>
+     * 版本号具有以下含意:
+     * <ul>
+     * <li>1 基于时间的 UUID
+     * <li>2 DCE 安全 UUID
+     * <li>3 基于名称的 UUID
+     * <li>4 随机生成的 UUID
+     * </ul>
+     *
+     * @return 此 {@code UUID} 的版本号
+     */
+    public int version() {
+        // Version is bits masked by 0x000000000000F000 in MS long
+        return (int) ((mostSigBits >> 12) & 0x0f);
+    }
+
+    /**
+     * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。
+     * <p>
+     * 变体号具有以下含意:
+     * <ul>
+     * <li>0 为 NCS 向后兼容保留
+     * <li>2 <a href="http://www.ietf.org/rfc/rfc4122.txt">IETF&nbsp;RFC&nbsp;4122</a>(Leach-Salz), 用于此类
+     * <li>6 保留,微软向后兼容
+     * <li>7 保留供以后定义使用
+     * </ul>
+     *
+     * @return 此 {@code UUID} 相关联的变体号
+     */
+    public int variant() {
+        // This field is composed of a varying number of bits.
+        // 0 - - Reserved for NCS backward compatibility
+        // 1 0 - The IETF aka Leach-Salz variant (used by this class)
+        // 1 1 0 Reserved, Microsoft backward compatibility
+        // 1 1 1 Reserved for future definition.
+        return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63));
+    }
+
+    /**
+     * 与此 UUID 相关联的时间戳值。
+     *
+     * <p>
+     * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。<br>
+     * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。
+     *
+     * <p>
+     * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
+     * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
+     *
+     * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。
+     */
+    public long timestamp() throws UnsupportedOperationException {
+        checkTimeBase();
+        return (mostSigBits & 0x0FFFL) << 48//
+                | ((mostSigBits >> 16) & 0x0FFFFL) << 32//
+                | mostSigBits >>> 32;
+    }
+
+    /**
+     * 与此 UUID 相关联的时钟序列值。
+     *
+     * <p>
+     * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。
+     * <p>
+     * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出
+     * UnsupportedOperationException。
+     *
+     * @return 此 {@code UUID} 的时钟序列
+     * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
+     */
+    public int clockSequence() throws UnsupportedOperationException {
+        checkTimeBase();
+        return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48);
+    }
+
+    /**
+     * 与此 UUID 相关的节点值。
+     *
+     * <p>
+     * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。
+     * <p>
+     * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
+     * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
+     *
+     * @return 此 {@code UUID} 的节点值
+     * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
+     */
+    public long node() throws UnsupportedOperationException {
+        checkTimeBase();
+        return leastSigBits & 0x0000FFFFFFFFFFFFL;
+    }
+
+    /**
+     * 返回此{@code UUID} 的字符串表现形式。
+     *
+     * <p>
+     * UUID 的字符串表示形式由此 BNF 描述:
+     *
+     * <pre>
+     * {@code
+     * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
+     * time_low               = 4*<hexOctet>
+     * time_mid               = 2*<hexOctet>
+     * time_high_and_version  = 2*<hexOctet>
+     * variant_and_sequence   = 2*<hexOctet>
+     * node                   = 6*<hexOctet>
+     * hexOctet               = <hexDigit><hexDigit>
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * </pre>
+     *
+     * </blockquote>
+     *
+     * @return 此{@code UUID} 的字符串表现形式
+     * @see #toString(boolean)
+     */
+    @Override
+    public String toString() {
+        return toString(false);
+    }
+
+    /**
+     * 返回此{@code UUID} 的字符串表现形式。
+     *
+     * <p>
+     * UUID 的字符串表示形式由此 BNF 描述:
+     *
+     * <pre>
+     * {@code
+     * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
+     * time_low               = 4*<hexOctet>
+     * time_mid               = 2*<hexOctet>
+     * time_high_and_version  = 2*<hexOctet>
+     * variant_and_sequence   = 2*<hexOctet>
+     * node                   = 6*<hexOctet>
+     * hexOctet               = <hexDigit><hexDigit>
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * </pre>
+     *
+     * </blockquote>
+     *
+     * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串
+     * @return 此{@code UUID} 的字符串表现形式
+     */
+    public String toString(boolean isSimple) {
+        final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36);
+        // time_low
+        builder.append(digits(mostSigBits >> 32, 8));
+        if (false == isSimple) {
+            builder.append('-');
+        }
+        // time_mid
+        builder.append(digits(mostSigBits >> 16, 4));
+        if (false == isSimple) {
+            builder.append('-');
+        }
+        // time_high_and_version
+        builder.append(digits(mostSigBits, 4));
+        if (false == isSimple) {
+            builder.append('-');
+        }
+        // variant_and_sequence
+        builder.append(digits(leastSigBits >> 48, 4));
+        if (false == isSimple) {
+            builder.append('-');
+        }
+        // node
+        builder.append(digits(leastSigBits, 12));
+
+        return builder.toString();
+    }
+
+    /**
+     * 返回此 UUID 的哈希码。
+     *
+     * @return UUID 的哈希码值。
+     */
+    @Override
+    public int hashCode() {
+        long hilo = mostSigBits ^ leastSigBits;
+        return ((int) (hilo >> 32)) ^ (int) hilo;
+    }
+
+    /**
+     * 将此对象与指定对象比较。
+     * <p>
+     * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。
+     *
+     * @param obj 要与之比较的对象
+     * @return 如果对象相同,则返回 {@code true};否则返回 {@code false}
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if ((null == obj) || (obj.getClass() != UUID.class)) {
+            return false;
+        }
+        UUID id = (UUID) obj;
+        return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits);
+    }
+
+    // Comparison Operations
+
+    /**
+     * 将此 UUID 与指定的 UUID 比较。
+     *
+     * <p>
+     * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。
+     *
+     * @param val 与此 UUID 比较的 UUID
+     * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。
+     */
+    @Override
+    public int compareTo(UUID val) {
+        // The ordering is intentionally set up so that the UUIDs
+        // can simply be numerically compared as two numbers
+        return (this.mostSigBits < val.mostSigBits ? -1 : //
+                (this.mostSigBits > val.mostSigBits ? 1 : //
+                        (this.leastSigBits < val.leastSigBits ? -1 : //
+                                (this.leastSigBits > val.leastSigBits ? 1 : //
+                                        0))));
+    }
+
+    // -------------------------------------------------------------------------------------------------------------------
+    // Private method start
+
+    /**
+     * 返回指定数字对应的hex值
+     *
+     * @param val    值
+     * @param digits 位
+     * @return 值
+     */
+    private static String digits(long val, int digits) {
+        long hi = 1L << (digits * 4);
+        return Long.toHexString(hi | (val & (hi - 1))).substring(1);
+    }
+
+    /**
+     * 检查是否为time-based版本UUID
+     */
+    private void checkTimeBase() {
+        if (version() != 1) {
+            throw new UnsupportedOperationException("Not a time-based UUID");
+        }
+    }
+
+    /**
+     * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG)
+     *
+     * @return {@link SecureRandom}
+     */
+    public static SecureRandom getSecureRandom() {
+        try {
+            return SecureRandom.getInstance("SHA1PRNG");
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 获取随机数生成器对象<br>
+     * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。
+     *
+     * @return {@link ThreadLocalRandom}
+     */
+    public static ThreadLocalRandom getRandom() {
+        return ThreadLocalRandom.current();
+    }
+}

+ 128 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/utils/UserThreadLocal.java

@@ -0,0 +1,128 @@
+package com.kyl.utils;
+
+import cn.hutool.json.JSONUtil;
+import com.kyl.base.BaseVo;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * subjectContent.java
+ *  用户主体对象
+ */
+@Slf4j
+public class UserThreadLocal {
+
+
+    /***
+     *  创建线程局部userVO变量
+     */
+    public static ThreadLocal<String> subjectThreadLocal = new ThreadLocal<String>() {
+        @Override
+        protected String initialValue() {
+            return null;
+        }
+    };
+
+
+    // 提供线程局部变量set方法
+    public static void setSubject(String subject) {
+
+        subjectThreadLocal.set(subject);
+    }
+    // 提供线程局部变量get方法
+    public static String getSubject() {
+
+        return subjectThreadLocal.get();
+    }
+
+    //清空当前线程,防止内存溢出
+    public static void removeSubject() {
+
+        subjectThreadLocal.remove();
+        clearCurrentUserCache();
+    }
+
+    // 缓存当前登录用户的反序列化对象,避免重复解析
+    private static final ThreadLocal<Object> CURRENT_USER_CACHE = new ThreadLocal<>();
+
+    /**
+     * 获取当前登录用户(反序列化为指定类型)
+     * 多次调用会从缓存中返回,避免重复 JSON 解析
+     *
+     * @param clazz 目标类型 Class
+     * @param <T>   目标类型
+     * @return 当前登录用户对象,如果未登录返回 null
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getCurrentUser(Class<T> clazz) {
+        String subject = subjectThreadLocal.get();
+        if (subject == null || subject.isEmpty()) {
+            return null;
+        }
+        T cached = (T) CURRENT_USER_CACHE.get();
+        if (cached != null && cached.getClass() == clazz) {
+            return cached;
+        }
+        T user = JSONUtil.toBean(subject, clazz);
+        CURRENT_USER_CACHE.set(user);
+        return user;
+    }
+
+    /**
+     * 清除当前登录用户缓存(在 removeSubject 时一并清除)
+     */
+    public static void clearCurrentUserCache() {
+        CURRENT_USER_CACHE.remove();
+    }
+
+    private static final ThreadLocal<Long> LOCAL = new ThreadLocal<>();
+
+    private UserThreadLocal() {
+
+    }
+
+    /**
+     * 将authUserInfo放到ThreadLocal中
+     *
+     * @param authUserInfo {@link Long}
+     */
+    public static void set(Long authUserInfo) {
+        LOCAL.set(authUserInfo);
+    }
+
+    /**
+     * 从ThreadLocal中获取authUserInfo
+     */
+    public static Long get() {
+        return LOCAL.get();
+    }
+
+    /**
+     * 从当前线程中删除authUserInfo
+     */
+    public static void remove() {
+        LOCAL.remove();
+    }
+
+    /**
+     * 从当前线程中获取前端用户id
+     * @return 用户id
+     */
+    public static Long getUserId() {
+        return LOCAL.get();
+    }
+
+    /**
+     * 从当前线程中获取前端后端id
+     * @return 用户id
+     */
+    public static Long getMgtUserId() {
+        String subject = subjectThreadLocal.get();
+        if (ObjectUtil.isEmpty(subject)) {
+            return null;
+        }
+        BaseVo baseVo = JSONUtil.toBean(subject, BaseVo.class);
+        return baseVo.getId() ;
+    }
+
+
+}

+ 12 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/DataScopeDto.java

@@ -0,0 +1,12 @@
+package com.kyl.vo;
+
+import lombok.Data;
+
+/**
+ * @author WanJl
+ */
+@Data
+public class DataScopeDto {
+
+    private String dataScope;
+}

+ 29 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/DataSecurity.java

@@ -0,0 +1,29 @@
+package com.kyl.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ *  数据权限对象
+ */
+@Data
+@NoArgsConstructor
+public class DataSecurity implements Serializable {
+
+    @ApiModelProperty(value = "仅本人数据权限")
+    private Boolean youselfData;
+
+    @ApiModelProperty(value = "数据权限对应部门编号集合")
+    private List<String> deptNos;
+
+    @Builder
+    public DataSecurity(Boolean youselfData, List<String> deptNos) {
+        this.youselfData = youselfData;
+        this.deptNos = deptNos;
+    }
+}

+ 55 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/DeptVo.java

@@ -0,0 +1,55 @@
+package com.kyl.vo;
+
+
+import com.kyl.base.BaseVo;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 部门表
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DeptVo extends BaseVo {
+
+    @ApiModelProperty(value = "父部门编号")
+    private String parentDeptNo;
+
+    @ApiModelProperty(value = "部门编号")
+    private String deptNo;
+
+    @ApiModelProperty(value = "部门名称")
+    private String deptName;
+
+    @ApiModelProperty(value = "排序")
+    private Integer sortNo;
+
+    @ApiModelProperty(value = "负责人Id")
+    private Long leaderId;
+
+    @ApiModelProperty(value = "负责人姓名")
+    private String leaderName;
+
+    @ApiModelProperty(value = "角色查询部门:部门对应角色id")
+    private String roleId;
+
+    @ApiModelProperty(value = "层级")
+    private Integer level = 4;
+
+    @Builder
+    public DeptVo(Long id, String dataState, String parentDeptNo, String deptNo, String deptName, Integer sortNo, Long leaderId, String leaderName, String roleId, Integer level) {
+        super(id, dataState);
+        this.parentDeptNo = parentDeptNo;
+        this.deptNo = deptNo;
+        this.deptName = deptName;
+        this.sortNo = sortNo;
+        this.leaderId = leaderId;
+        this.leaderName = leaderName;
+        this.roleId = roleId;
+        this.level = level;
+    }
+}

+ 33 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/MenuMetaVo.java

@@ -0,0 +1,33 @@
+package com.kyl.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 菜单meta属性
+ */
+@Data
+@NoArgsConstructor
+public class MenuMetaVo implements Serializable {
+
+    @ApiModelProperty(value = "标题")
+    private String title;
+
+    @ApiModelProperty(value = "图标")
+    private String icon;
+
+    @ApiModelProperty(value = "角色")
+    private List<String> roles;
+
+    @Builder
+    public MenuMetaVo(String title, String icon, List<String> roles) {
+        this.title = title;
+        this.icon = icon;
+        this.roles = roles;
+    }
+}

+ 51 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/MenuVo.java

@@ -0,0 +1,51 @@
+package com.kyl.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 动态菜单VO对象
+ */
+@Data
+@NoArgsConstructor
+public class MenuVo implements Serializable {
+
+	// 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
+	@ApiModelProperty(value = "路由名字")
+	private  String name;
+
+	@ApiModelProperty(value = "请求路径")
+	private String path;
+
+	@ApiModelProperty(value = "模块跳转目标")
+	private String component;
+
+	@ApiModelProperty(value = "高亮子菜单")
+	private String redirect;
+
+	// 当设置 true 的时候该路由不会在侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
+	@ApiModelProperty(value = "是否显示")
+	private Boolean hidden;
+
+	@ApiModelProperty(value = "子菜单")
+	private List<MenuVo> children;
+
+	@ApiModelProperty(value = "meta属性")
+	private MenuMetaVo meta;
+
+	@Builder
+	public MenuVo(String name, String path, String component, String redirect, Boolean hidden, List<MenuVo> children, MenuMetaVo meta) {
+		this.name = name;
+		this.path = path;
+		this.component = component;
+		this.redirect = redirect;
+		this.hidden = hidden;
+		this.children = children;
+		this.meta = meta;
+	}
+}

+ 48 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/PostVo.java

@@ -0,0 +1,48 @@
+package com.kyl.vo;
+
+import com.kyl.base.BaseVo;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 岗位表
+ */
+@Data
+@NoArgsConstructor
+public class PostVo extends BaseVo {
+
+    @ApiModelProperty(value = "部门编号")
+    private String deptNo;
+
+    @ApiModelProperty(value = "岗位编码:父部门编号+001【3位】")
+    private String postNo;
+
+    @ApiModelProperty(value = "岗位名称")
+    private String postName;
+
+    @ApiModelProperty(value = "显示顺序")
+    private Integer sortNo;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "列表选择:选中职位Ids")
+    private String[] checkedIds;
+
+    @ApiModelProperty(value = "职位对应部门")
+    private DeptVo deptVo;
+
+    @Builder
+    public PostVo(Long id, String dataState, String deptNo, String postNo, String postName, Integer sortNo, String remark, String[] checkedIds, DeptVo deptVo) {
+        super(id, dataState);
+        this.deptNo = deptNo;
+        this.postNo = postNo;
+        this.postName = postName;
+        this.sortNo = sortNo;
+        this.remark = remark;
+        this.checkedIds = checkedIds;
+        this.deptVo = deptVo;
+    }
+}

+ 74 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/ResourceVo.java

@@ -0,0 +1,74 @@
+package com.kyl.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.kyl.base.BaseVo;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 权限表
+ */
+@Data
+@NoArgsConstructor
+public class ResourceVo extends BaseVo {
+
+    @ApiModelProperty(value = "资源编号")
+    private String resourceNo;
+
+    @ApiModelProperty(value = "父资源编号")
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private String parentResourceNo;
+
+    @ApiModelProperty(value = "资源名称")
+    private String resourceName;
+
+    @ApiModelProperty(value = "资源类型:s平台 c目录 m菜单 r按钮")
+    private String resourceType;
+
+    @ApiModelProperty(value = "请求地址")
+    private String requestPath;
+
+    @ApiModelProperty(value = "权限标识")
+    private String label;
+
+    @ApiModelProperty(value = "排序")
+    private Integer sortNo;
+
+    @ApiModelProperty(value = "图标")
+    private String icon;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "列表选择:选中职位Ids")
+    private String[] checkedIds;
+
+    @ApiModelProperty(value = "TREE结构:选中资源编号")
+    private String[] checkedResourceNos;
+
+    @ApiModelProperty(value = "角色查询资源:资源对应角色id")
+    private String roleId;
+
+    @ApiModelProperty(value = "层级")
+    private Integer level = 4;
+
+    @Builder
+    public ResourceVo(Long id, String dataState, String resourceNo, String parentResourceNo, String resourceName, String resourceType, String requestPath, String label, Integer sortNo, String icon, String remark, String[] checkedIds, String[] checkedResourceNos, String roleId, Integer level) {
+        super(id, dataState);
+        this.resourceNo = resourceNo;
+        this.parentResourceNo = parentResourceNo;
+        this.resourceName = resourceName;
+        this.resourceType = resourceType;
+        this.requestPath = requestPath;
+        this.label = label;
+        this.sortNo = sortNo;
+        this.icon = icon;
+        this.remark = remark;
+        this.checkedIds = checkedIds;
+        this.checkedResourceNos = checkedResourceNos;
+        this.roleId = roleId;
+        this.level = level;
+    }
+}

+ 59 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/RoleVo.java

@@ -0,0 +1,59 @@
+package com.kyl.vo;
+
+import com.kyl.base.BaseVo;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 角色表
+ */
+@Data
+@NoArgsConstructor
+public class RoleVo extends BaseVo {
+
+    @ApiModelProperty(value = "角色名称")
+    private String roleName;
+
+    @ApiModelProperty(value = "角色标识")
+    private String label;
+
+    @ApiModelProperty(value = "排序")
+    private Integer sortNo;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "列表选择:选中角色Ids")
+    private String[] checkedIds;
+
+    @ApiModelProperty(value = "TREE结构:选中资源No")
+    private String[] checkedResourceNos;
+
+    @ApiModelProperty(value = "TREE结构:选中部门No")
+    private String[] checkedDeptNos;
+
+    @ApiModelProperty(value = "人员查询部门:当前人员Id")
+    private String userId;
+
+    @ApiModelProperty(value = "数据范围(0自定义  1本人 2本部门及以下 3本部门 4全部)")
+    private String dataScope;
+
+    private String dataState;
+
+    @Builder
+    public RoleVo(Long id, String dataState, String roleName, String label, Integer sortNo, String remark, String[] checkedIds, String[] checkedResourceNos, String[] checkedDeptNos, String userId, String dataScope, String dataState1) {
+        super(id, dataState);
+        this.roleName = roleName;
+        this.label = label;
+        this.sortNo = sortNo;
+        this.remark = remark;
+        this.checkedIds = checkedIds;
+        this.checkedResourceNos = checkedResourceNos;
+        this.checkedDeptNos = checkedDeptNos;
+        this.userId = userId;
+        this.dataScope = dataScope;
+        this.dataState = dataState1;
+    }
+}

+ 34 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/TreeItemVo.java

@@ -0,0 +1,34 @@
+package com.kyl.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 资源树结构体
+ */
+@Data
+@NoArgsConstructor
+public class TreeItemVo implements Serializable {
+
+    @ApiModelProperty(value = "节点ID")
+    private String id;
+
+    @ApiModelProperty(value = "显示内容")
+    private String label;
+
+    @ApiModelProperty(value = "显示内容")
+    public List<TreeItemVo> children = new ArrayList<>();
+
+    @Builder
+    public TreeItemVo(String id, String label, List<TreeItemVo> children) {
+        this.id = id;
+        this.label = label;
+        this.children = children;
+    }
+}

+ 25 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/TreeVo.java

@@ -0,0 +1,25 @@
+package com.kyl.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 资源树显示类
+ */
+@Data
+@NoArgsConstructor
+public class TreeVo implements Serializable {
+
+	@ApiModelProperty(value = "tree数据")
+	private List<TreeItemVo> items;
+
+	@Builder
+	public TreeVo(List<TreeItemVo> items) {
+		this.items = items;
+	}
+}

+ 58 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/UserAddVo.java

@@ -0,0 +1,58 @@
+package com.kyl.vo;
+
+import com.kyl.base.BaseVo;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Set;
+
+/**
+ * 用户表
+ */
+@Data
+@NoArgsConstructor
+public class UserAddVo extends BaseVo {
+
+    @ApiModelProperty(value = "用户账号")
+    private String username;
+
+    @ApiModelProperty(value = "密码")
+    private String password;
+
+    @ApiModelProperty(value = "用户邮箱")
+    private String email;
+
+    @ApiModelProperty(value = "真实姓名")
+    private String realName;
+
+    @ApiModelProperty(value = "手机号码")
+    private String mobile;
+
+    @ApiModelProperty(value = "用户性别(0男 1女 2未知)")
+    private String sex;
+
+    @ApiModelProperty(value = "查询用户:用户角色Ids")
+    private Set<String>  roleVoIds;
+
+    @ApiModelProperty(value = "部门编号【当前】")
+    private String deptNo;
+
+    @ApiModelProperty(value = "职位编号【当前】")
+    private String postNo;
+
+    @Builder
+    public UserAddVo(Long id, String dataState, String username, String password, String email, String realName, String mobile, String sex, Set<String> roleVoIds, String deptNo, String postNo) {
+        super(id, dataState);
+        this.username = username;
+        this.password = password;
+        this.email = email;
+        this.realName = realName;
+        this.mobile = mobile;
+        this.sex = sex;
+        this.roleVoIds = roleVoIds;
+        this.deptNo = deptNo;
+        this.postNo = postNo;
+    }
+}

+ 24 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/UserRoleVo.java

@@ -0,0 +1,24 @@
+package com.kyl.vo;
+
+import lombok.Data;
+
+/**
+ * @author WanJl
+ */
+@Data
+public class UserRoleVo {
+
+    /**
+     * 用户真名
+     */
+    private String userName;
+    /**
+     * 角色名称
+     */
+    private String roleName;
+
+    /**
+     * 用户id
+     */
+    private Long id;
+}

+ 110 - 0
kyl-sanatorium/kyl-common/src/main/java/com/kyl/vo/UserVo.java

@@ -0,0 +1,110 @@
+package com.kyl.vo;
+
+import com.kyl.base.BaseVo;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 用户表
+ */
+@Data
+@NoArgsConstructor
+public class UserVo extends BaseVo {
+
+    @ApiModelProperty(value = "用户账号")
+    private String username;
+
+    @ApiModelProperty(value = "密码")
+    private String password;
+
+    @ApiModelProperty(value = "用户类型(0:系统用户,1:客户)")
+    private String userType;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "用户职位")
+    private String post;
+
+    @ApiModelProperty(value = "用户部门")
+    private String dept;
+
+    @ApiModelProperty(value = "用户邮箱")
+    private String email;
+
+    @ApiModelProperty(value = "真实姓名")
+    private String realName;
+
+    @ApiModelProperty(value = "手机号码")
+    private String mobile;
+
+    @ApiModelProperty(value = "用户性别(0男 1女 2未知)")
+    private String sex;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "选中节点")
+    private String[] checkedIds;
+
+    @ApiModelProperty(value = "三方openId")
+    private String openId;
+
+    @ApiModelProperty(value = "查询用户:用户角色Ids")
+    private Set<String>  roleVoIds;
+
+    @ApiModelProperty(value = "构建令牌:用户角色标识")
+    private Set<String> roleLabels;
+
+    @ApiModelProperty(value = "角色列表")
+    private List<RoleVo> roleList;
+
+    @ApiModelProperty(value = "构建令牌:用户权限路径")
+    private Set<String> resourceRequestPaths;
+
+    @ApiModelProperty(value = "部门编号【当前】")
+    private String deptNo;
+
+    @ApiModelProperty(value = "职位编号【当前】")
+    private String postNo;
+
+    @ApiModelProperty(value = "角色Id【当前】")
+    private Long roleId;
+
+    @ApiModelProperty(value = "用户令牌")
+    private String userToken;
+
+    private String dataState;
+
+    @Builder
+    public UserVo(Long id, String dataState, String username, String password, String userType, String nickName, String post, String dept, String email, String realName, String mobile, String sex, String remark, String[] checkedIds, String openId, Set<String> roleVoIds, Set<String> roleLabels, List<RoleVo> roleList, Set<String> resourceRequestPaths, String deptNo, String postNo, Long roleId, String userToken, String dataState1) {
+        super(id, dataState);
+        this.username = username;
+        this.password = password;
+        this.userType = userType;
+        this.nickName = nickName;
+        this.post = post;
+        this.dept = dept;
+        this.email = email;
+        this.realName = realName;
+        this.mobile = mobile;
+        this.sex = sex;
+        this.remark = remark;
+        this.checkedIds = checkedIds;
+        this.openId = openId;
+        this.roleVoIds = roleVoIds;
+        this.roleLabels = roleLabels;
+        this.roleList = roleList;
+        this.resourceRequestPaths = resourceRequestPaths;
+        this.deptNo = deptNo;
+        this.postNo = postNo;
+        this.roleId = roleId;
+        this.userToken = userToken;
+        this.dataState = dataState1;
+    }
+}

+ 119 - 0
kyl-sanatorium/kyl-framework/pom.xml

@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 定义项目对象模型(POM),是Maven构建工具的基础 -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!-- 定义父项目,用于继承公共配置 -->
+    <parent>
+        <groupId>com.kyl</groupId>
+        <artifactId>kyl-sanatorium</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <!-- 当前项目的唯一标识符 -->
+    <artifactId>kyl-framework</artifactId>
+
+    <!-- 定义项目属性 -->
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <!-- 定义项目依赖 -->
+    <dependencies>
+        <!--MySQL支持-->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <!--springboot的测试支持-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
+        <!-- 自定义公共依赖 -->
+        <dependency>
+            <groupId>com.kyl</groupId>
+            <artifactId>kyl-common</artifactId>
+        </dependency>
+        <!--Spring Boot Web模块-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!--Spring Boot Actuator模块,用于监控和管理应用-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <!--Spring Boot自动配置模块-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <!--Spring Boot配置处理器-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+        <!--Druid数据源的Spring Boot启动器-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+        </dependency>
+        <!--MyBatis-Plus的Spring Boot启动器-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        <!--Redis-->
+        <!--Redisson缓存客户端-->
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+        </dependency>
+        <!--ZXing二维码生成核心库-->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+        </dependency>
+
+        <!--XXL-JOB分布式任务调度框架核心库-->
+        <dependency>
+            <groupId>com.xuxueli</groupId>
+            <artifactId>xxl-job-core</artifactId>
+        </dependency>
+
+        <!--Apache Commons Codec库,用于加密和解密-->
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
+
+        <!--JAXB API,用于XML绑定-->
+        <dependency>
+            <groupId>javax.xml.bind</groupId>
+            <artifactId>jaxb-api</artifactId>
+        </dependency>
+
+        <!--Spring Boot缓存支持-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 53 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/CustomRedisConfig.java

@@ -0,0 +1,53 @@
+package com.kyl.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.time.Duration;
+
+/**
+ * Redis缓存配置(来自kyl项目优化版)
+ * 支持Java8时间类型序列化
+ */
+@Configuration
+@EnableCaching
+public class CustomRedisConfig {
+
+    @Bean
+    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        objectMapper.activateDefaultTyping(
+                objectMapper.getPolymorphicTypeValidator(),
+                ObjectMapper.DefaultTyping.NON_FINAL
+        );
+        objectMapper.registerModule(new JavaTimeModule());
+
+        GenericJackson2JsonRedisSerializer jsonSerializer =
+                new GenericJackson2JsonRedisSerializer(objectMapper);
+
+        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofMinutes(10))
+                .disableCachingNullValues()
+                .serializeKeysWith(RedisSerializationContext.SerializationPair
+                        .fromSerializer(new StringRedisSerializer()))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair
+                        .fromSerializer(jsonSerializer));
+
+        return RedisCacheManager.builder(redisConnectionFactory)
+                .cacheDefaults(cacheConfig)
+                .build();
+    }
+}

+ 21 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/MybatisConfig.java

@@ -0,0 +1,21 @@
+package com.kyl.config;
+
+import com.kyl.intercept.AutoFillInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ *  webMvc高级配置
+ */
+@Configuration
+public class MybatisConfig {
+
+    /***
+     *  自动填充拦截器
+     */
+    @Bean
+    public AutoFillInterceptor autoFillInterceptor(){
+        return new AutoFillInterceptor();
+    }
+
+}

+ 86 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/OSSAliyunFileStorageService.java

@@ -0,0 +1,86 @@
+package com.kyl.config;
+
+import com.aliyun.oss.ClientException;
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSException;
+import com.aliyun.oss.model.CannedAccessControlList;
+import com.aliyun.oss.model.DeleteObjectsRequest;
+import com.aliyun.oss.model.PutObjectRequest;
+import com.aliyun.oss.model.PutObjectResult;
+import com.google.common.collect.Lists;
+import com.kyl.properties.AliOssConfigProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.InputStream;
+import java.util.List;
+
+@Slf4j
+@Component
+public class OSSAliyunFileStorageService {
+
+    @Autowired
+    OSS ossClient;
+
+    @Autowired
+    AliOssConfigProperties aliOssConfigProperties;
+
+    /**
+     * 上传文件
+     * @param objectName  文件名
+     * @param inputStream  文件流对象
+     * @return
+     */
+    public String store(String objectName, InputStream inputStream) {
+        //文件读取路径
+        String url = null;
+        // 判断文件
+        if (inputStream == null) {
+            log.error("上传文件:objectName{}文件流为空", objectName);
+            return url;
+        }
+        log.info("OSS文件上传开始:{}", objectName);
+        try {
+            String bucketName = aliOssConfigProperties.getBucketName();
+            // 上传文件
+            PutObjectRequest request = new PutObjectRequest(bucketName, objectName, inputStream);
+
+            PutObjectResult result = ossClient.putObject(request);
+            // 设置权限(公开读)
+            ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
+            if (result != null) {
+                log.info("OSS文件上传成功:{}", objectName);
+
+            }
+        } catch (OSSException oe) {
+            log.error("OSS文件上传错误:{}", oe);
+        } catch (ClientException ce) {
+            log.error("OSS文件上传客户端错误:{}", ce);
+        }
+        //文件访问路径规则 https://BucketName.Endpoint/ObjectName
+        StringBuilder stringBuilder = new StringBuilder("https://");
+        stringBuilder
+                .append(aliOssConfigProperties.getBucketName())
+                .append(".")
+                .append(aliOssConfigProperties.getEndpoint())
+                .append("/")
+                .append(objectName);
+        return stringBuilder.toString();
+    }
+
+    /**
+     * 根据url删除文件
+     * @param pathUrl   url地址(全路径)
+     */
+    public void delete(String pathUrl) {
+        String prefix = "https://"+aliOssConfigProperties.getBucketName()+"."+ aliOssConfigProperties.getEndpoint()+"/";
+        String key = pathUrl.replace(prefix, "");
+        List<String> keys = Lists.newArrayList();
+        keys.add(key);
+        // 删除Objects
+        ossClient.deleteObjects(new DeleteObjectsRequest(aliOssConfigProperties.getBucketName()).withKeys(keys));
+
+    }
+
+}

+ 48 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/OssAliyunAutoConfig.java

@@ -0,0 +1,48 @@
+package com.kyl.config;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.CannedAccessControlList;
+import com.aliyun.oss.model.CreateBucketRequest;
+import com.aliyun.oss.model.SetBucketLoggingRequest;
+import com.kyl.properties.AliOssConfigProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Slf4j
+@Configuration
+public class OssAliyunAutoConfig {
+
+    @Autowired
+    AliOssConfigProperties aliOssConfigProperties;
+
+    @Bean
+    public OSS ossClient(){
+        log.info("-----------------开始创建OSSClient--------------------");
+        OSS ossClient = new OSSClientBuilder().build(aliOssConfigProperties.getEndpoint(),
+                aliOssConfigProperties.getAccessKeyId(), aliOssConfigProperties.getAccessKeySecret());
+        //判断容器是否存在,不存在就创建
+        if (!ossClient.doesBucketExist(aliOssConfigProperties.getBucketName())) {
+            ossClient.createBucket(aliOssConfigProperties.getBucketName());
+            CreateBucketRequest createBucketRequest = new CreateBucketRequest(aliOssConfigProperties.getBucketName());
+            //设置问公共可读
+            createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
+            ossClient.createBucket(createBucketRequest);
+        }
+
+        //添加客户端访问日志
+        SetBucketLoggingRequest request = new SetBucketLoggingRequest(aliOssConfigProperties.getBucketName());
+        // 设置存放日志文件的存储空间。
+        request.setTargetBucket(aliOssConfigProperties.getBucketName());
+        // 设置日志文件存放的目录。
+        request.setTargetPrefix(aliOssConfigProperties.getBucketName());
+        ossClient.setBucketLogging(request);
+
+        log.info("-----------------结束创建OSSClient--------------------");
+        return ossClient;
+    }
+
+
+}

+ 62 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/SecurityConfig.java

@@ -0,0 +1,62 @@
+package com.kyl.config;
+
+import com.kyl.properties.SecurityConfigProperties;
+import com.kyl.security.JwtAuthorizationManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+
+import java.util.List;
+
+/**
+ *  权限核心配置类
+ */
+@Configuration
+@EnableConfigurationProperties(SecurityConfigProperties.class)
+public class SecurityConfig  {
+
+    @Autowired
+    SecurityConfigProperties securityConfigProperties;
+
+    @Autowired
+    JwtAuthorizationManager jwtAuthorizationManager;
+
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+
+        //忽略地址
+        List<String> ignoreUrl = securityConfigProperties.getIgnoreUrl();
+        http.authorizeHttpRequests()
+                .antMatchers( ignoreUrl.toArray( new String[ignoreUrl.size() ] ) )
+                .permitAll()
+                .anyRequest().access(jwtAuthorizationManager);
+
+        http.csrf().disable();
+        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS );//关闭session
+        http.headers().cacheControl().disable();//关闭缓存
+
+        return http.build();
+    }
+
+    @Bean
+    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+        return  authenticationConfiguration.getAuthenticationManager();
+    }
+
+    /**
+     * BCrypt密码编码
+     * @return
+     */
+    @Bean
+    public BCryptPasswordEncoder bcryptPasswordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+}

+ 93 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/SwaggerConfig.java

@@ -0,0 +1,93 @@
+package com.kyl.config;
+
+import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
+import com.kyl.properties.SwaggerConfigProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
+import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
+import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
+import org.springframework.boot.actuate.endpoint.web.*;
+import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
+import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
+import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.env.Environment;
+import org.springframework.util.StringUtils;
+import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@Configuration
+@EnableConfigurationProperties(SwaggerConfigProperties.class)
+@EnableKnife4j
+@Import(BeanValidatorPluginsConfiguration.class)
+public class SwaggerConfig {
+
+    @Autowired
+    SwaggerConfigProperties swaggerConfigProperties;
+
+    @Bean(value = "defaultApi2")
+    @ConditionalOnClass(SwaggerConfigProperties.class)
+    public Docket defaultApi2() {
+        // 构建API文档  文档类型为swagger2
+        return new Docket(DocumentationType.SWAGGER_2)
+            .select()
+            // 配置 api扫描路径
+            .apis(RequestHandlerSelectors.basePackage(swaggerConfigProperties.getSwaggerPath()))
+            // 指定路径的设置  any代表所有路径
+            .paths(PathSelectors.any())
+            // api的基本信息
+            .build().apiInfo(new ApiInfoBuilder()
+                // api文档名称
+                .title(swaggerConfigProperties.getTitle())
+                // api文档描述
+                .description(swaggerConfigProperties.getDescription())
+                // api文档版本
+                .version("1.0") // 版本
+                // api作者信息
+                .contact(new Contact(
+                    swaggerConfigProperties.getContactName(),
+                    swaggerConfigProperties.getContactUrl(),
+                    swaggerConfigProperties.getContactEmail()))
+                .build());
+    }
+
+    /**
+     * 增加如下配置可解决Spring Boot 6.x 与Swagger 3.0.0 不兼容问题
+     **/
+    @Bean
+    public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
+                                                                         ServletEndpointsSupplier servletEndpointsSupplier,
+                                                                         ControllerEndpointsSupplier controllerEndpointsSupplier,
+                                                                         EndpointMediaTypes endpointMediaTypes,
+                                                                         CorsEndpointProperties corsProperties,
+                                                                         WebEndpointProperties webEndpointProperties,
+                                                                         Environment environment) {
+        List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
+        Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
+        allEndpoints.addAll(webEndpoints);
+        allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
+        allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
+        String basePath = webEndpointProperties.getBasePath();
+        EndpointMapping endpointMapping = new EndpointMapping(basePath);
+        boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
+        return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(),
+                new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
+    }
+    private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
+        return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
+    }
+}

+ 110 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/WebMvcConfig.java

@@ -0,0 +1,110 @@
+package com.kyl.config;
+
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+import com.kyl.intercept.UserInterceptor;
+import com.kyl.intercept.UserTokenIntercept;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.math.BigInteger;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ *  webMvc高级配置
+ */
+@Configuration
+@ComponentScan("springfox.documentation.swagger.web")
+public class WebMvcConfig implements WebMvcConfigurer {
+
+   @Autowired
+   UserTokenIntercept userTokenIntercept;
+
+    @Autowired
+   UserInterceptor userInterceptor;
+
+    //拦截的时候过滤掉swagger相关路径和登录相关接口
+    private static final String[] EXCLUDE_PATH_PATTERNS = new String[]{"/swagger-ui.html",
+            "/webjars/**",
+            "/swagger-resources",
+            "/v2/api-docs",
+            // 登录接口
+            "/customer/user/login",
+            // 房型列表接口
+            "/customer/roomTypes",
+            "/customer/orders/project/**",
+            "/user/refresh/**"};
+
+    /**
+     *  拦截器
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        //userToken拦截
+        registry.addInterceptor(userTokenIntercept).excludePathPatterns(ADMIN_EXCLUDE_PATH_PATTERNS);
+        // 小程序端接口鉴权拦截器
+        registry.addInterceptor(userInterceptor).excludePathPatterns(EXCLUDE_PATH_PATTERNS).addPathPatterns("/customer/**");
+    }
+
+    //拦截的时候过滤掉swagger相关路径和登录相关接口
+    private static final String[] ADMIN_EXCLUDE_PATH_PATTERNS = new String[]{"/swagger-ui.html",
+            "/webjars/**",
+            "/swagger-resources",
+            "/v2/api-docs",
+            "/customer/**",
+            "/security/**",
+            "/common/**",
+            "/user/refresh/**"};
+
+    /**
+     * 资源路径 映射
+     */
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        //支持webjars
+        registry.addResourceHandler("/webjars/**")
+                .addResourceLocations("classpath:/META-INF/resources/webjars/");
+        //支持swagger
+        registry.addResourceHandler("swagger-ui.html")
+                .addResourceLocations("classpath:/META-INF/resources/");
+        //支持小刀
+        registry.addResourceHandler("doc.html")
+                .addResourceLocations("classpath:/META-INF/resources/");
+    }
+
+    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
+    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
+
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
+        return builder -> {
+            // 序列化
+            builder.serializerByType(Long.class, ToStringSerializer.instance);
+            builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
+            builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
+            builder.serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
+            builder.serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
+
+            // 反序列化
+            builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
+            builder.deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
+            builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
+
+        };
+    }
+}

+ 39 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/config/XxlJobConfig.java

@@ -0,0 +1,39 @@
+package com.kyl.config;
+
+import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ *
+ * @author xuxueli 2017-04-28
+ */
+@Configuration
+public class XxlJobConfig {
+    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
+
+    @Value("${xxl.job.admin.addresses}")
+    private String adminAddresses;
+
+    @Value("${xxl.job.executor.appname}")
+    private String appname;
+
+    @Value("${xxl.job.executor.port}")
+    private int port;
+
+
+    @Bean
+    public XxlJobSpringExecutor xxlJobExecutor() {
+        logger.info(">>>>>>>>>>> xxl-job config init.");
+        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
+        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
+        xxlJobSpringExecutor.setAppname(appname);
+        xxlJobSpringExecutor.setPort(port);
+        return xxlJobSpringExecutor;
+    }
+
+
+}

+ 134 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/intercept/AutoFillInterceptor.java

@@ -0,0 +1,134 @@
+package com.kyl.intercept;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import com.kyl.utils.EmptyUtil;
+import com.kyl.utils.UserThreadLocal;
+import org.apache.ibatis.binding.MapperMethod;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.plugin.*;
+import org.apache.ibatis.reflection.MetaObject;
+import org.apache.ibatis.reflection.SystemMetaObject;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Properties;
+
+@Component
+@Intercepts({
+        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
+})
+public class AutoFillInterceptor implements Interceptor {
+
+    private static final String CREATE_BY = "createBy";
+    private static final String UPDATE_BY = "updateBy";
+
+    private static final String CREATE_TIME = "createTime";
+    private static final String UPDATE_TIME = "updateTime";
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+        Object[] args = invocation.getArgs();
+        // 获取用于描述SQL语句的映射信息
+        MappedStatement ms = (MappedStatement) args[0];
+        SqlCommandType sqlCommandType = ms.getSqlCommandType();
+
+        // 获取sql参数实体 ParamMap
+        Object parameter = args[1];
+
+        if (parameter != null && sqlCommandType != null) {
+            // 获取用户ID
+            Long userId = loadUserId();
+
+            if (SqlCommandType.INSERT.equals(sqlCommandType)) {
+                // 插入操作
+                if (parameter instanceof MapperMethod.ParamMap) {
+                    // 批量插入的情况
+                    MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameter;
+                    ArrayList list = (ArrayList) paramMap.get("list");
+                    list.forEach(v -> {
+                        // 设置创建人和创建时间字段值
+                        setFieldValByName(CREATE_BY, userId, v);
+                        setFieldValByName(CREATE_TIME, LocalDateTime.now(), v);
+                        setFieldValByName(UPDATE_TIME, LocalDateTime.now(), v);
+                    });
+                    paramMap.put("list", list);
+                } else {
+                    // 单条插入的情况
+                    // 设置创建人和创建时间字段值
+                    setFieldValByName(CREATE_BY, userId, parameter);
+                    setFieldValByName(CREATE_TIME, LocalDateTime.now(), parameter);
+                    setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);
+                }
+            } else if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
+                // 更新操作
+                // 设置更新人和更新时间字段值
+                setFieldValByName(UPDATE_BY, userId, parameter);
+                setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);
+            }
+        }
+
+        // 继续执行原始方法
+        return invocation.proceed();
+    }
+
+    /**
+     * 通过反射设置实体的字段值
+     *
+     * @param fieldName 字段名
+     * @param fieldVal  字段值
+     * @param parameter 实体对象
+     */
+    private void setFieldValByName(String fieldName, Object fieldVal, Object parameter) {
+        MetaObject metaObject = SystemMetaObject.forObject(parameter);
+        if (fieldName.equals(CREATE_BY)) {
+            Object value = metaObject.getValue(fieldName);
+            if (ObjectUtil.isNotEmpty(value)) {
+                return;
+            }
+        }
+
+        if (metaObject.hasSetter(fieldName)) {
+            metaObject.setValue(fieldName, fieldVal);
+        }
+    }
+
+    @Override
+    public Object plugin(Object target) {
+        if (target instanceof Executor) {
+            // 对目标对象进行包装,返回代理对象
+            return Plugin.wrap(target, this);
+        }
+        // 非 Executor 类型的对象,直接返回原始对象
+        return target;
+    }
+
+    @Override
+    public void setProperties(Properties properties) {
+        // 读取配置文件中的属性,此处没有使用
+    }
+
+    /**
+     * 获取当前用户的ID,用于填充创建人和更新人字段的值
+     *
+     * @return 当前用户ID
+     */
+    public static Long loadUserId() {
+        // 从 ThreadLocal 中获取用户ID
+        Long userId = UserThreadLocal.getUserId();
+        // 如果 ThreadLocal 中不存在用户ID,则从管理用户ID中获取
+        if (ObjectUtil.isNotEmpty(userId)) {
+            return userId;
+        }
+        userId = UserThreadLocal.getMgtUserId();
+        // 如果管理用户ID也不存在,则默认返回ID为1的用户
+        if (!EmptyUtil.isNullOrEmpty(userId)) {
+            return userId;
+        }
+        return 1L;
+    }
+}
+

+ 76 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/intercept/UserInterceptor.java

@@ -0,0 +1,76 @@
+package com.kyl.intercept;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.kyl.constant.Constants;
+import com.kyl.exception.BaseException;
+import com.kyl.properties.JwtTokenManagerProperties;
+import com.kyl.utils.JwtUtil;
+import com.kyl.utils.UserThreadLocal;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+/**
+ * 小程序端token校验
+ * 统一对请求的合法性进行校验,需要进行2个方面的校验,
+ * 一、请求头中是否携带了authorization
+ * 二、请求头中是否存在userId,如果不存在则说明是非法请求,响应401状态码
+ * 如果是合法请求,将userId存储到ThreadLocal中
+ */
+@Slf4j
+@Component
+@EnableConfigurationProperties(JwtTokenManagerProperties.class)
+public class UserInterceptor implements HandlerInterceptor {
+
+    @Value("${token.header:authorization}")
+    private String header;
+
+    @Autowired
+    private JwtTokenManagerProperties jwtTokenManagerProperties;
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+
+        //如果不是映射到方法就放行,比如跨域验证请求、静态资源等不需要身份校验的请求
+        if (!(handler instanceof HandlerMethod)) {
+            return true;
+        }
+
+        //获取header的参数
+        String token = request.getHeader(header);
+
+        log.info("开始解析 customer user token {}", token);
+        if (ObjectUtil.isEmpty(token)) {
+            //token失效
+            throw new BaseException("小程序登录","401",null,"没有权限,请登录");
+        }
+        Map<String, Object> claims = JwtUtil.parseJWT(jwtTokenManagerProperties.getBase64EncodedSecretKey(), token);
+        if (ObjectUtil.isEmpty(claims)) {
+            //token失效
+            throw new BaseException("小程序登录","401",null,"没有权限,请登录");
+        }
+
+        Long userId = MapUtil.get(claims, Constants.JWT_USERID, Long.class);
+        if (ObjectUtil.isEmpty(userId)) {
+            throw new BaseException("小程序登录","401",null,"没有权限,请登录");
+        }
+
+        UserThreadLocal.set(userId);
+
+        return true;
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+        UserThreadLocal.remove();
+    }
+}

+ 56 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/intercept/UserTokenIntercept.java

@@ -0,0 +1,56 @@
+package com.kyl.intercept;
+
+import com.kyl.constant.SecurityConstant;
+import com.kyl.constant.UserCacheConstant;
+import com.kyl.properties.JwtTokenManagerProperties;
+import com.kyl.utils.EmptyUtil;
+import com.kyl.utils.JwtUtil;
+import com.kyl.utils.UserThreadLocal;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *  多租户放到SubjectContent上下文中
+ */
+@Component
+public class UserTokenIntercept implements HandlerInterceptor {
+
+    @Autowired
+    private StringRedisTemplate redisTemplate;
+
+    @Autowired
+    private JwtTokenManagerProperties jwtTokenManagerProperties;
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+
+        if (!(handler instanceof HandlerMethod)) {
+            return true;
+        }
+        //从头部中拿到当前userToken
+        String userToken = request.getHeader(SecurityConstant.USER_TOKEN);
+        if (!EmptyUtil.isNullOrEmpty(userToken)) {
+            String jwtTokenKey = UserCacheConstant.JWT_TOKEN + userToken;
+            String jwtToken = redisTemplate.opsForValue().get(jwtTokenKey);
+            if (!EmptyUtil.isNullOrEmpty(jwtToken)) {
+                Object userObj = JwtUtil.parseJWT(jwtTokenManagerProperties.getBase64EncodedSecretKey(), jwtToken).get("currentUser");
+                String currentUser = String.valueOf(userObj);
+                //放入当前线程中:用户当前的web直接获得user使用
+                UserThreadLocal.setSubject(currentUser);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+        //移除当前线程中的参数
+        UserThreadLocal.removeSubject();
+    }
+}

+ 48 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/properties/AliIoTConfigProperties.java

@@ -0,0 +1,48 @@
+package com.kyl.properties;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author WanJl
+ */
+
+@Setter
+@Getter
+@NoArgsConstructor
+@ToString
+@Configuration
+@ConfigurationProperties(prefix = "kyl.aliyun")
+public class AliIoTConfigProperties {
+
+    /**
+     * 访问Key
+     */
+    private String accessKeyId;
+    /**
+     * 访问秘钥
+     */
+    private String accessKeySecret;
+    /**
+     * 区域id
+     */
+    private String regionId;
+    /**
+     * 实例id
+     */
+    private String iotInstanceId;
+    /**
+     * 域名
+     */
+    private String host;
+
+    /**
+     * 消费组
+     */
+    private String consumerGroupId;
+
+}

+ 43 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/properties/AliOssConfigProperties.java

@@ -0,0 +1,43 @@
+package com.kyl.properties;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @ClassName AliOssConfigProperties.java
+ * 
+ */
+@Setter
+@Getter
+@NoArgsConstructor
+@ToString
+@Configuration
+@ConfigurationProperties(prefix = "kyl.framework.oss")
+public class AliOssConfigProperties {
+
+    /**
+     * 域名站点
+     */
+    private String endpoint ;
+
+    /**
+     * 秘钥Id
+     */
+    private String accessKeyId ;
+
+    /**
+     * 秘钥
+     */
+    private String accessKeySecret ;
+
+    /**
+     * 桶名称
+     */
+    private String bucketName ;
+
+}
+

+ 32 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/properties/JwtTokenManagerProperties.java

@@ -0,0 +1,32 @@
+package com.kyl.properties;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.Serializable;
+
+/**
+ * jw配置文件
+ */
+@Setter
+@Getter
+@NoArgsConstructor
+@ToString
+@Configuration
+@ConfigurationProperties(prefix = "kyl.framework.jwt")
+public class JwtTokenManagerProperties implements Serializable {
+
+    /**
+     *  签名密码
+     */
+    private String base64EncodedSecretKey;
+
+    /**
+     *  有效时间
+     */
+    private Integer ttl;
+}

+ 33 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/properties/SecurityConfigProperties.java

@@ -0,0 +1,33 @@
+package com.kyl.properties;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *  忽略配置及跨域
+ */
+@Slf4j
+@Data
+@ConfigurationProperties(prefix = "kyl.framework.security")
+@Configuration
+public class SecurityConfigProperties {
+
+    String defaultPassword;
+
+    List<String> ignoreUrl = new ArrayList<>();
+
+    List<String> origins = new ArrayList<>();
+
+    String loginPage;
+
+    //令牌有效时间
+    Integer accessTokenValiditySeconds = 3*24*3600;
+
+    //刷新令牌有效时间
+    Integer refreshTokenValiditySeconds= 7*24*3600;
+}

+ 32 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/properties/SwaggerConfigProperties.java

@@ -0,0 +1,32 @@
+package com.kyl.properties;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.io.Serializable;
+
+/**
+ *  SwaggerConfigProperties配置类
+ */
+@Setter
+@Getter
+@NoArgsConstructor
+@ToString
+@ConfigurationProperties(prefix = "kyl.framework.swagger")
+public class SwaggerConfigProperties implements Serializable {
+
+    public String swaggerPath;
+
+    public String title;
+
+    public String description;
+
+    public String contactName;
+
+    public String contactUrl;
+
+    public String contactEmail;
+}

+ 114 - 0
kyl-sanatorium/kyl-framework/src/main/java/com/kyl/security/JwtAuthorizationManager.java

@@ -0,0 +1,114 @@
+package com.kyl.security;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import com.kyl.constant.SecurityConstant;
+import com.kyl.constant.UserCacheConstant;
+import com.kyl.properties.JwtTokenManagerProperties;
+import com.kyl.utils.EmptyUtil;
+import com.kyl.utils.JwtUtil;
+import com.kyl.vo.UserVo;
+import io.jsonwebtoken.Claims;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ *  授权管理器
+ */
+@Slf4j
+@Component
+public class JwtAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
+
+    private AntPathMatcher antPathMatcher = new AntPathMatcher();
+
+    @Autowired
+    private StringRedisTemplate redisTemplate;
+
+    @Autowired
+    private JwtTokenManagerProperties jwtTokenManagerProperties;
+
+
+    @Override
+    public AuthorizationDecision check(Supplier<Authentication> authentication,
+                                       RequestAuthorizationContext requestAuthorizationContext) {
+        //用户当前请求路径
+        String method = requestAuthorizationContext.getRequest().getMethod();
+        String requestURI = requestAuthorizationContext.getRequest().getRequestURI();
+        String targetUrl = (method+requestURI);
+
+        //获得请求中的认证后传递过来的userToken
+        String userToken = requestAuthorizationContext.getRequest().getHeader(SecurityConstant.USER_TOKEN);
+
+        //如果userToken为空,则当前请求不合法
+        if (EmptyUtil.isNullOrEmpty(userToken)){
+            return new AuthorizationDecision(false);
+        }
+
+        //通过userToken获取jwtToken
+        String jwtTokenKey = UserCacheConstant.JWT_TOKEN+userToken;
+        //key:uuid
+        String jwtToken = redisTemplate.opsForValue().get(jwtTokenKey);
+
+        //如果jwtToken为空,则当前请求不合法
+        if (EmptyUtil.isNullOrEmpty(jwtToken)){
+            return new AuthorizationDecision(false);
+        }
+
+        //校验jwtToken是否合法
+        Claims cla = JwtUtil.parseJWT(jwtTokenManagerProperties.getBase64EncodedSecretKey(), jwtToken);
+        if (ObjectUtil.isEmpty(cla)) {
+            //token失效
+            return new AuthorizationDecision(false);
+        }
+
+        //如果校验jwtToken通过,则获得userVo对象
+        UserVo userVo = JSONUtil.toBean(String.valueOf(cla.get("currentUser")), UserVo.class);
+
+        //用户剔除校验:redis中最新的userToken与出入的userToken不符合,则认为当前用户被后续用户剔除
+        //key:username  value:uuid
+        String currentUserToken = redisTemplate.opsForValue().get(UserCacheConstant.USER_TOKEN + userVo.getUsername());
+        if (!userToken.equals(currentUserToken)){
+            return new AuthorizationDecision(false);
+        }
+
+        //如果当前UserToken存活时间少于10分钟,则进行续期
+        Long remainTimeToLive = redisTemplate.opsForValue().getOperations().getExpire(jwtTokenKey);
+        if (remainTimeToLive.longValue()<= 600){
+            //jwt生成的token也会过期,所以需要重新生成jwttoken
+            Map<String, Object> claims = new HashMap<>();
+            String userVoJsonString = JSONUtil.toJsonStr(userVo);
+            claims.put("currentUser", userVoJsonString);
+
+            //jwtToken令牌颁布
+            String newJwtToken = JwtUtil.createJWT(jwtTokenManagerProperties.getBase64EncodedSecretKey(), jwtTokenManagerProperties.getTtl(), claims);
+            long ttl = Long.valueOf(jwtTokenManagerProperties.getTtl()) / 1000;
+
+            redisTemplate.opsForValue().set(jwtTokenKey, newJwtToken, ttl, TimeUnit.SECONDS);
+            redisTemplate.expire(UserCacheConstant.USER_TOKEN + userVo.getUsername(), ttl, TimeUnit.SECONDS);
+        }
+
+        //当前用户资源是否包含当前URL
+        /*for (String resourceRequestPath : userVo.getResourceRequestPaths()) {
+            boolean isMatch = antPathMatcher.match(resourceRequestPath, targetUrl);
+            if (isMatch){
+                log.info("用户:{}拥有targetUrl权限:{}==========",userVo.getUsername(),targetUrl);
+                return new AuthorizationDecision(true);
+            }
+        }*/
+
+        return new AuthorizationDecision(true);
+    }
+
+}

+ 49 - 0
kyl-sanatorium/kyl-pay/pom.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.kyl</groupId>
+        <artifactId>kyl-sanatorium</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>kyl-pay</artifactId>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>com.github.wechatpay-apiv3</groupId>
+            <artifactId>wechatpay-apache-httpclient</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.kyl</groupId>
+            <artifactId>kyl-framework</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 62 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/Config.java

@@ -0,0 +1,62 @@
+package com.kyl.client;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @ClassName Config.java
+ *  微信的配置文件
+ */
+@Slf4j
+@Data
+public class Config {
+
+    //appId
+    String appid;
+
+    //商户号
+    String mchId;
+
+    //私钥字符串
+    String privateKey;
+
+    //商户证书序列号
+    String mchSerialNo;
+
+    //V3密钥
+    String apiV3Key;
+
+    //请求地址
+    String domain;
+
+    //回调地址
+    String notifyUrl;
+
+    /***
+     *  构建客户端
+     * @param mchId  商户号
+     * @param privateKey 私钥字符串
+     * @param mchSerialNo 商户证书序列号
+     * @param apiV3Key V3密钥
+     * @return
+     */
+    @Builder
+    public Config(String appid,
+                  String mchId,
+                  String privateKey,
+                  String mchSerialNo,
+                  String apiV3Key,
+                  String domain,String notifyUrl) {
+        this.appid = appid;
+        this.mchId = mchId;
+        this.privateKey = privateKey;
+        this.mchSerialNo = mchSerialNo;
+        this.apiV3Key = apiV3Key;
+        this.domain=domain;
+        this.notifyUrl = notifyUrl;
+    }
+
+    public Config() {
+    }
+}

+ 35 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/Factory.java

@@ -0,0 +1,35 @@
+package com.kyl.client;
+
+import com.kyl.client.operators.Common;
+import com.kyl.client.operators.Wap;
+import lombok.Data;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * @ClassName Factory.java
+ *  封装对于微信支付工程类
+ */
+@Data
+@Log4j2
+public class Factory {
+
+    Config config;
+
+    public void setOptions(Config config) {
+        this.config = config;
+    }
+
+    //基础服务
+    public Common Common(){
+        return new Common(config);
+    }
+
+
+
+    //手机网页支付
+    public Wap Wap(){
+        return new Wap(config);
+    }
+
+
+}

+ 151 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/WechatPayHttpClient.java

@@ -0,0 +1,151 @@
+package com.kyl.client;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
+import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
+import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
+import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
+import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
+import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
+import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
+import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
+import com.kyl.utils.EmptyUtil;
+import com.kyl.utils.ExceptionsUtil;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+
+/**
+ * @ClassName WechatPayHttpClient.java
+ * 支付宝支付远程调用对象
+ */
+@Slf4j
+@Data
+@NoArgsConstructor
+public class WechatPayHttpClient {
+
+    public static final ObjectMapper JSON_MAPPER = new ObjectMapper();
+
+    //商户号
+    String mchId;
+
+    //私钥字符串
+    String privateKey;
+
+    //商户证书序列号
+    String mchSerialNo;
+
+    //V3密钥
+    String apiV3Key;
+
+    //请求地址
+    String domain;
+
+    @Builder
+    public WechatPayHttpClient(String mchId,
+                               String privateKey,
+                               String mchSerialNo,
+                               String apiV3Key,
+                               String domain) {
+        this.mchId = mchId;
+        this.privateKey = privateKey;
+        this.mchSerialNo = mchSerialNo;
+        this.apiV3Key = apiV3Key;
+        this.domain = domain;
+    }
+
+
+    /***
+     *  构建CloseableHttpClient远程请求对象
+     * @return: org.apache.http.impl.client.CloseableHttpClient
+     */
+    private CloseableHttpClient createHttpClient() throws UnsupportedEncodingException {
+        // 加载商户私钥(privateKey:私钥字符串)
+        PrivateKey merchantPrivateKey = PemUtil
+                .loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
+        // 获取证书管理器实例
+        CertificatesManager certificatesManager = CertificatesManager.getInstance();
+        // 向证书管理器增加需要自动更新平台证书的商户信息
+        try {
+            certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
+                    new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
+        } catch (IOException e) {
+            log.error("流处理异常:{}", ExceptionsUtil.getStackTraceAsString(e));
+            return null;
+        } catch (GeneralSecurityException e) {
+            log.error("微信支付权限异常:{}", ExceptionsUtil.getStackTraceAsString(e));
+            return null;
+        } catch (HttpCodeException e) {
+            log.error("微信支付请求异常:{}", ExceptionsUtil.getStackTraceAsString(e));
+            return null;
+        }
+        Verifier verifier = null;
+        try {
+            verifier = certificatesManager.getVerifier(mchId);
+        } catch (NotFoundException e) {
+            log.error("微信支付验证者创建异常:{}", ExceptionsUtil.getStackTraceAsString(e));
+            return null;
+        }
+        // 初始化httpClient
+        return com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder.create()
+                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
+                .withValidator(new WechatPay2Validator(verifier))
+                .build();
+    }
+
+    /***
+     *  支持post请求的远程调用
+     * @param params 携带请求参数
+     * @return 返回字符串
+     */
+    public String doPost(ObjectNode params) throws IOException {
+        HttpPost httpPost = new HttpPost("https://" + domain);
+        httpPost.addHeader("Accept", "application/json");
+        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.writeValue(bos, params);
+
+        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
+        CloseableHttpResponse response = this.createHttpClient().execute(httpPost);
+        log.info("关闭交易单:网关返回 -->" + JSON_MAPPER.writeValueAsString(response));
+        if (!EmptyUtil.isNullOrEmpty(response)) {
+            return EntityUtils.toString(response.getEntity());
+        }
+        return null;
+    }
+
+    /***
+     *  支持get请求的远程调用
+     * @param param 在路径中请求的参数
+     * @return
+     * @return: 返回字符串
+     */
+    public String doGet(String param) throws URISyntaxException, IOException {
+        URIBuilder uriBuilder = new URIBuilder("https://" + domain + param);
+        HttpGet httpGet = new HttpGet(uriBuilder.build());
+        httpGet.addHeader("Accept", "application/json");
+        CloseableHttpResponse response = this.createHttpClient().execute(httpGet);
+        return EntityUtils.toString(response.getEntity());
+    }
+
+}

+ 149 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/operators/Common.java

@@ -0,0 +1,149 @@
+package com.kyl.client.operators;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.kyl.client.WechatPayHttpClient;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.kyl.client.Config;
+import com.kyl.client.WechatPayHttpClient;
+import com.kyl.client.response.CloseResponse;
+import com.kyl.client.response.QueryResponse;
+import com.kyl.client.response.RefundResponse;
+import com.kyl.utils.EmptyUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+/**
+ * @ClassName Common.java
+ *  基础交易API封装
+ */
+@Slf4j
+public class Common {
+
+    private Config config;
+
+    public Common(Config config) {
+        this.config=config;
+    }
+
+    /***
+     *  查询交易结果
+     * @param outTradeNo 发起支付传递的交易单号
+     * @return
+     */
+    public QueryResponse query(String outTradeNo) throws Exception {
+        //请求地址
+        String uri ="/v3/pay/transactions/out-trade-no";
+        WechatPayHttpClient httpClient = WechatPayHttpClient.builder()
+            .mchId(config.getMchId())
+            .mchSerialNo(config.getMchSerialNo())
+            .apiV3Key(config.getApiV3Key())
+            .privateKey(config.getPrivateKey())
+            .domain(config.getDomain()+uri)
+            .build();
+        String uriParams ="/"+outTradeNo+"?mchid="+config.getMchId();
+        String body =  httpClient.doGet(uriParams);
+        QueryResponse queryResponse = WechatPayHttpClient.JSON_MAPPER.readValue(body, QueryResponse.class);
+        if (!EmptyUtil.isNullOrEmpty(queryResponse.getTradeState())){
+            queryResponse.setCode("200");
+            queryResponse.setMessage("网关请求成功");
+        }
+        return queryResponse;
+    }
+
+    /***
+     *  统一收单交易退款接口查询
+     * @param outRefundNo 商户系统内部的退款单号
+     * @return
+     */
+    public RefundResponse queryRefund(String outRefundNo) throws Exception {
+        //请求地址
+        String uri ="/v3/refund/domestic/refunds";
+        WechatPayHttpClient httpClient = WechatPayHttpClient.builder()
+            .mchId(config.getMchId())
+            .mchSerialNo(config.getMchSerialNo())
+            .apiV3Key(config.getApiV3Key())
+            .privateKey(config.getPrivateKey())
+            .domain(config.getDomain()+uri)
+            .build();
+        String uriParams ="/"+outRefundNo;
+        String body =  httpClient.doGet(uriParams);
+        RefundResponse refundResponse = WechatPayHttpClient.JSON_MAPPER.readValue(body, RefundResponse.class);
+        if (!EmptyUtil.isNullOrEmpty(refundResponse.getStatus())){
+            refundResponse.setCode("200");
+            refundResponse.setMessage("网关请求成功");
+        }
+        return refundResponse;
+    }
+
+    /***
+     *  统一收单交易退款接口
+     * @param outTradeNo 发起支付传递的交易单号
+     * @param amount 退款金额
+     * @param outRefundNo 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔
+     * @param total 原订单金额
+     * @return
+     */
+    public RefundResponse refund(String outTradeNo, String amount,
+                                 String outRefundNo, String total) throws Exception {
+        //请求地址
+        String uri ="/v3/refund/domestic/refunds";
+        WechatPayHttpClient httpClient = WechatPayHttpClient.builder()
+            .mchId(config.getMchId())
+            .mchSerialNo(config.getMchSerialNo())
+            .apiV3Key(config.getApiV3Key())
+            .privateKey(config.getPrivateKey())
+            .domain(config.getDomain()+uri)
+            .build();
+        ObjectMapper objectMapper = new ObjectMapper();
+        ObjectNode bodyParams = objectMapper.createObjectNode();
+        BigDecimal bigDecimal = new BigDecimal(amount);
+        BigDecimal multiply = bigDecimal.multiply(new BigDecimal(100));
+        BigDecimal bigDecimalTotal = new BigDecimal(total);
+        BigDecimal multiplyTotal = bigDecimalTotal.multiply(new BigDecimal(100));
+        bodyParams.put("out_refund_no", outRefundNo)
+            .put("out_trade_no", outTradeNo);
+        bodyParams.putObject("amount")
+            .put("refund", multiply.intValue())
+            .put("total", multiplyTotal.intValue())
+            .put("currency", "CNY");
+        String body =  httpClient.doPost(bodyParams);
+        RefundResponse refundResponse = WechatPayHttpClient.JSON_MAPPER.readValue(body, RefundResponse.class);
+        if (!EmptyUtil.isNullOrEmpty(refundResponse.getStatus())){
+            refundResponse.setCode("200");
+            refundResponse.setMessage("网关请求成功");
+        }
+        return refundResponse;
+    }
+
+    /***
+     *  统一关闭订单
+     * 1、商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
+     * 2、系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
+     * @param outTradeNo 外部交易单号
+     * @return
+     */
+    public CloseResponse close(String outTradeNo) throws Exception {
+        //请求地址
+        String uri ="/v3/pay/transactions/out-trade-no/"+outTradeNo+"/close";
+        WechatPayHttpClient httpClient = WechatPayHttpClient.builder()
+            .mchId(config.getMchId())
+            .mchSerialNo(config.getMchSerialNo())
+            .apiV3Key(config.getApiV3Key())
+            .privateKey(config.getPrivateKey())
+            .domain(config.getDomain()+uri)
+            .build();
+        ObjectMapper objectMapper = new ObjectMapper();
+        ObjectNode bodyParams = objectMapper.createObjectNode();
+        bodyParams.put("mchid", config.getMchId());
+        try {
+            httpClient.doPost(bodyParams);
+        } catch (Exception e){}
+
+        CloseResponse closeResponse = new CloseResponse();
+        closeResponse.setCode("200");
+        closeResponse.setMessage("网关请求成功");
+        return closeResponse;
+    }
+}

+ 68 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/operators/Wap.java

@@ -0,0 +1,68 @@
+package com.kyl.client.operators;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.kyl.client.WechatPayHttpClient;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.kyl.client.Config;
+import com.kyl.client.WechatPayHttpClient;
+import com.kyl.client.response.WapPayResponse;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+/**
+ * @ClassName Wap.java
+ *  手机网页支付
+ */
+@Slf4j
+public class Wap {
+
+    private Config config;
+
+    public Wap(Config config) {
+        this.config=config;
+    }
+
+    /***
+     *  生成交易表单,渲染后自动跳转支付宝或者微信引导用户完成支付
+     * @param outTradeNo  外部订单【业务系统中间的交易单号】
+     * @param amount    交易的金额【单位:分】
+     * @param description   商品的描述
+     * @param openId    用户在当前公众号下的OpenID
+     * @return TradingVo对象中的placeOrderJson:阿里云form表单字符串、微信prepay_id标识
+     */
+    public WapPayResponse pay(String outTradeNo, String amount,
+                              String description, String openId) throws Exception {
+        //请求地址
+        String uri ="/v3/pay/transactions/jsapi";
+        //发起对应的请求
+        WechatPayHttpClient httpClient = WechatPayHttpClient.builder()
+                .mchId(config.getMchId())
+                .mchSerialNo(config.getMchSerialNo())
+                .apiV3Key(config.getApiV3Key())
+                .privateKey(config.getPrivateKey())
+                .domain(config.getDomain()+uri)
+                .build();
+        ObjectMapper objectMapper = new ObjectMapper();
+        ObjectNode bodyParams = objectMapper.createObjectNode();
+        BigDecimal bigDecimal = new BigDecimal(amount);
+        BigDecimal multiply = bigDecimal.multiply(new BigDecimal(100));
+        bodyParams.put("mchid",config.getMchId())
+                .put("appid",config.getAppid() )
+                .put("description", description)
+                .put("notify_url", config.getNotifyUrl())
+                .put("out_trade_no", outTradeNo);
+        //交易金额
+        bodyParams.putObject("amount")
+                .put("total", multiply.intValue());
+        //付款者OpenId信息
+        bodyParams.putObject("payer").put("openid",openId);
+        String body =  httpClient.doPost(bodyParams);
+        WapPayResponse wapPayResponse = WechatPayHttpClient.JSON_MAPPER.readValue(body, WapPayResponse.class);
+        wapPayResponse.setCode("200");
+        return wapPayResponse;
+    }
+
+
+}

+ 19 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/AmountResponse.java

@@ -0,0 +1,19 @@
+package com.kyl.client.response;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @ClassName AmountResponse.java
+ *  金额信息
+ */
+@Data
+@NoArgsConstructor
+public class AmountResponse {
+
+    //订单总金额【分】
+    private Integer total;
+
+    //退款总金额【分】
+    private Integer refund;
+}

+ 19 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/AppPreCreateResponse.java

@@ -0,0 +1,19 @@
+package com.kyl.client.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @ClassName AppPreCreateResponse.java
+ */
+@Data
+@NoArgsConstructor
+public class AppPreCreateResponse {
+
+    //请求返回编码
+    private String code;
+
+    @JsonProperty("prepay_id")
+    private String prepayId;
+}

+ 23 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/BasicResponse.java

@@ -0,0 +1,23 @@
+package com.kyl.client.response;
+
+import lombok.Data;
+
+/**
+ * @ClassName BasicResponse.java
+ *  返回基础对象
+ */
+@Data
+public class BasicResponse {
+
+    //网关请求返回编码
+    public String code;
+
+    //网关请求返回信息
+    public String message;
+
+    //业务请求返回编码
+    public String subCode;
+
+    //业务请求返回信息
+    public String subMessage;
+}

+ 14 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/CloseResponse.java

@@ -0,0 +1,14 @@
+package com.kyl.client.response;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @ClassName CloseResponse.java
+ *  关闭订单返回结果
+ */
+@Data
+@NoArgsConstructor
+public class CloseResponse extends BasicResponse {
+
+}

+ 18 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/PreCreateResponse.java

@@ -0,0 +1,18 @@
+package com.kyl.client.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @ClassName PreCreateResponse.java
+ */
+@Data
+@NoArgsConstructor
+public class PreCreateResponse extends BasicResponse{
+
+    //二维码请求地址
+    @JsonProperty("code_url")
+    private String codeUrl;
+
+}

+ 27 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/QueryResponse.java

@@ -0,0 +1,27 @@
+package com.kyl.client.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @ClassName QueryResponse.java
+ *  统一收单线下交易查询返回
+ */
+@Data
+@NoArgsConstructor
+public class QueryResponse extends BasicResponse{
+
+    //商户订单号
+    @JsonProperty("out_trade_no")
+    private String outTradeNo;
+
+    //交易状态:SUCCESS:支付成功,REFUND:转入退款,NOTPAY:未支付,CLOSED:已关闭,REVOKED:已撤销(仅付款码支付会返回)
+    //USERPAYING:用户支付中(仅付款码支付会返回),PAYERROR:支付失败(仅付款码支付会返回)
+    @JsonProperty("trade_state")
+    private String tradeState;
+
+    //交易状态描述
+    @JsonProperty("trade_state_desc")
+    private String tradeStateDesc;
+}

+ 32 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/RefundResponse.java

@@ -0,0 +1,32 @@
+package com.kyl.client.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @ClassName RefundResponse.java
+ *  退款返回
+ */
+@Data
+@NoArgsConstructor
+public class RefundResponse extends BasicResponse {
+
+    //商户退款单号
+    @JsonProperty("out_refund_no")
+    private String outRefundNo;
+
+    //商户订单号
+    @JsonProperty("out_trade_no")
+    private String outTradeNo;
+
+    //退款状态
+    //SUCCESS:退款成功
+    //CLOSED:退款关闭
+    //PROCESSING:退款处理中
+    //ABNORMAL:退款异常
+    private String status;
+
+    //金额信息
+    private AmountResponse amount;
+}

+ 17 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/client/response/WapPayResponse.java

@@ -0,0 +1,17 @@
+package com.kyl.client.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @ClassName PagePayResponse.java
+ */
+@Data
+@NoArgsConstructor
+public class WapPayResponse extends BasicResponse {
+
+    @JsonProperty("prepay_id")
+    private String prepayId;
+
+}

+ 34 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/config/WechatPayConfig.java

@@ -0,0 +1,34 @@
+package com.kyl.config;
+
+import com.kyl.client.Config;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @ClassName WeChatPayConfig.java
+ *  微信配置类
+ */
+@Configuration
+@EnableConfigurationProperties(WechatPayProperties.class)
+public class WechatPayConfig {
+
+    @Autowired
+    WechatPayProperties wechatPayProperties;
+
+    /***
+     *  获得配置
+     * @return
+     */
+    public Config config(){
+        return Config.builder()
+            .appid(wechatPayProperties.getAppid())
+            .domain(wechatPayProperties.getDomain())
+            .mchId(wechatPayProperties.getMchId())
+            .mchSerialNo(wechatPayProperties.getMchSerialNo())
+            .apiV3Key(wechatPayProperties.getApiV3Key())
+            .privateKey(wechatPayProperties.getPrivateKey())
+            .notifyUrl(wechatPayProperties.getNotifyUrl())
+            .build();
+    }
+}

+ 33 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/config/WechatPayProperties.java

@@ -0,0 +1,33 @@
+package com.kyl.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @ClassName WechatPayProperties.java
+ */
+@Data
+@ConfigurationProperties(prefix = "kyl.framework.trade.wechatpay")
+public class WechatPayProperties {
+
+    //appId
+    String appid;
+
+    //商户号
+    String mchId;
+
+    //私钥字符串
+    String privateKey;
+
+    //商户证书序列号
+    String mchSerialNo;
+
+    //V3密钥
+    String apiV3Key;
+
+    //请求地址
+    String domain;
+
+    //回调地址
+    String notifyUrl;
+}

+ 20 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/constant/SuperConstant.java

@@ -0,0 +1,20 @@
+
+package com.kyl.constant;
+
+
+/**
+ *  静态变量
+ */
+public class SuperConstant {
+
+	/**
+	 * 常量是
+	 */
+	public static final String YES = "YES";
+
+	/**
+	 * 常量否
+	 */
+	public static final String NO = "NO";
+
+}

+ 32 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/constant/TradingCacheConstant.java

@@ -0,0 +1,32 @@
+package com.kyl.constant;
+
+/**
+ * @ClassName TradingCacheConstant.java
+ *  交易缓存维护
+ */
+public class TradingCacheConstant {
+
+    //默认redis等待时间
+    public static final int REDIS_WAIT_TIME = 10;
+
+    //默认redis自动释放时间
+    public static final int REDIS_LEASETIME = 4;
+
+    //安全组前缀
+    public static final String PREFIX = "trading:";
+
+    //分布式锁前缀
+    public static final String LOCK_PREFIX = PREFIX+"lock:";
+
+    //创建交易加锁
+    public static final String CREATE_PAY = LOCK_PREFIX+ "create_pay";
+
+    //创建退款加锁
+    public static final String REFUND_PAY = LOCK_PREFIX+ "refund_pay";
+
+    //创建退款加锁
+    public static final String PAY_CHANNEL_LIST = PREFIX+"pay_channel_list&ttl=-1";
+
+    //page分页
+    public static final String PAGE= PREFIX+"page";
+}

+ 80 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/constant/TradingConstant.java

@@ -0,0 +1,80 @@
+package com.kyl.constant;
+
+/**
+ * @ClassName TardingConstant.java
+ *  交易常量类
+ */
+public class TradingConstant {
+
+    //【阿里云退款返回状态】
+    //REFUND_SUCCESS:成功
+    public static final String REFUND_SUCCESS= "REFUND_SUCCESS";
+
+    //【阿里云返回付款状态】
+    //TRADE_CLOSED:未付款交易超时关闭,或支付完成后全额退款
+    public static final String ALI_TRADE_CLOSED ="TRADE_CLOSED";
+    //TRADE_SUCCESS:交易支付成功
+    public static final String ALI_TRADE_SUCCESS="TRADE_SUCCESS";
+    //TRADE_FINISHED:交易结束不可退款
+    public static final String ALI_TRADE_FINISHED ="TRADE_FINISHED";
+
+
+    //【微信退款返回状态】
+    //SUCCESS:退款成功
+    public static final String WECHAT_REFUND_SUCCESS ="SUCCESS";
+    //CLOSED:退款关闭
+    public static final String WECHAT_REFUND_CLOSED="CLOSED";
+    //PROCESSING:退款处理中
+    public static final String WECHAT_REFUND_PROCESSING ="PROCESSING";
+    //ABNORMAL:退款异常
+    public static final String WECHAT_REFUND_ABNORMAL ="ABNORMAL";
+
+    //【微信返回付款状态】
+    //SUCCESS:支付成功
+    public static final String WECHAT_TRADE_SUCCESS ="SUCCESS";
+    //REFUND:转入退款
+    public static final String WECHAT_TRADE_REFUND ="REFUND";
+    //NOTPAY:未支付
+    public static final String WECHAT_TRADE_NOTPAY ="NOTPAY";
+    //CLOSED:已关闭
+    public static final String WECHAT_TRADE_CLOSED ="CLOSED";
+    //REVOKED:已撤销(仅付款码支付会返回)
+    public static final String WECHAT_TRADE_REVOKED ="REVOKED";
+    //USERPAYING:用户支付中(仅付款码支付会返回)
+    public static final String WECHAT_TRADE_USERPAYING ="USERPAYING";
+    //PAYERROR:支付失败(仅付款码支付会返回)
+    public static final String WECHAT_TRADE_PAYERROR ="PAYERROR";
+
+    //【平台:交易渠道】
+    //阿里支付
+    public static final String TRADING_CHANNEL_ALI_PAY = "ALI_PAY";
+    //微信支付
+    public static final String TRADING_CHANNEL_WECHAT_PAY = "WECHAT_PAY";
+    //现金
+    public static final String TRADING_CHANNEL_CASH_PAY = "CASH_PAY";
+    //免单挂账【信用渠道】
+    public static final String TRADING_CHANNEL_CREDIT_PAY = "CREDIT_PAY";
+
+
+    //【平台:交易状态】
+    //WAIT_BUYER_PAY:待支付(交易创建,等待买家付款)
+    public static final Integer TRADE_WAIT_BUYER_PAY_1 =1;
+    //SUCCESS:支付成功
+    public static final Integer TRADE_SUCCESS_4 =4;
+    //CLOSED:已关闭(未付款交易超时关闭,或支付失败)
+    public static final Integer TRADE_CLOSED_5 =5;
+
+    //【平台:退款状态】
+    //失败
+    public static final Integer REFUND_STATUS_FAIL_0= 0;
+    //成功
+    public static final Integer REFUND_STATUS_SUCCESS_2 = 2;
+    //请求中
+    public static final Integer REFUND_STATUS_SENDING_1= 1;
+    //请求关闭
+    public static final Integer REFUND_STATUS_CLOSED_3= 3;
+
+    public static final String ALI_SUCCESS_CODE= "10000";
+
+    public static final String ALI_SUCCESS_MSG= "Success";
+}

+ 106 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/controller/CommonPayFeignController.java

@@ -0,0 +1,106 @@
+
+package com.kyl.controller;
+
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.kyl.base.PageResponse;
+import com.kyl.base.ResponseResult;
+import com.kyl.constant.TradingCacheConstant;
+import com.kyl.enums.TradingEnum;
+import com.kyl.exception.ProjectException;
+import com.kyl.handler.CommonPayHandler;
+import com.kyl.service.RefundRecordService;
+import com.kyl.utils.ExceptionsUtil;
+import com.kyl.vo.RefundRecordVo;
+import com.kyl.vo.TradingVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @ClassName CommonPayController.java
+ *  基础支付控制器
+ */
+@RequestMapping("trade-common-feign")
+@RestController
+@Api(tags = "支付基础服务")
+@Slf4j
+public class CommonPayFeignController {
+
+    @Autowired
+    CommonPayHandler wechatCommonPayHandler;
+
+    @Autowired
+    RedissonClient redissonClient;
+
+    @Resource
+    private RefundRecordService refundRecordService;
+
+    /***
+     *  申请退款接口
+     * 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,
+     * 将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。
+     * @param tradingVo 交易单
+     * @return
+     */
+    @PostMapping("refund")
+    @ApiOperation(value = "申请退款",notes = "申请退款")
+    @ApiImplicitParam(name = "tradingVo",value = "交易单",required = true)
+    @ApiOperationSupport(includeParameters ={"tradingVo.tradingOrderNo",
+            "tradingVo.operTionRefund","tradingVo.tradingChannel"})
+    public ResponseResult refundTrading(@RequestBody TradingVo tradingVo){
+        //1、对交易订单加锁
+        Long productOrderNo = tradingVo.getProductOrderNo();
+        String key = TradingCacheConstant.REFUND_PAY + productOrderNo;
+        RLock lock = redissonClient.getFairLock(key);
+        try {
+            boolean flag = lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS);
+            if (flag){
+                tradingVo.setCreateType(2);
+                return ResponseResult.success(wechatCommonPayHandler.refundTrading(tradingVo));
+            }else {
+                throw new ProjectException(TradingEnum.REFUND_FAIL);
+            }
+        } catch (Exception e) {
+            log.error("统一收单交易退款接口异常:{}", ExceptionsUtil.getStackTraceAsString(e));
+            throw new ProjectException(TradingEnum.REFUND_FAIL);
+        }finally {
+            lock.unlock();
+        }
+    }
+
+    /***
+     *  申请退款查询接口
+     * 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,
+     * 将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。
+     * @param refundRecordNo 退款交易单
+     * @return
+     */
+    @GetMapping("query-refund")
+    @ApiOperation(value = "根据退款单号进行退款结果查询",notes = "根据退款单号进行退款结果查询")
+    public ResponseResult queryRefundDownLineTrading(@RequestParam Long refundRecordNo){
+        return ResponseResult.success(refundRecordService.findByRefundNo(refundRecordNo));
+    }
+
+
+    /***
+     *  根据退款编号,订单编号 申请人 申请时间 状态 分页查询退款记录
+     * @param refundRecordVo 退款交易单
+     * @return
+     */
+    @PostMapping("query-refund-record")
+    @ApiOperation(value = "分页查询退款记录",notes = "分页查询退款记录")
+    @ApiImplicitParam(name = "refundRecordVo",value = "退款交易单",required = true)
+    public ResponseResult<PageResponse<RefundRecordVo>> queryRefundRecord(@RequestBody RefundRecordVo refundRecordVo){
+        return ResponseResult.success(refundRecordService.queryRefundRecord(refundRecordVo));
+    }
+
+}
+

+ 75 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/entity/RefundRecord.java

@@ -0,0 +1,75 @@
+package com.kyl.entity;
+
+import com.kyl.base.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+/**
+ * 退款记录表
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("refund_record")
+public class RefundRecord extends BaseEntity {
+
+    private static final long serialVersionUID = -3998253241655800061L;
+
+    @ApiModelProperty(value = "交易系统订单号【对于三方来说:商户订单】")
+    private Long tradingOrderNo;
+
+    @ApiModelProperty(value = "业务系统订单号")
+    private Long productOrderNo;
+
+    @ApiModelProperty(value = "本次退款订单号")
+    private Long refundNo;
+
+    @ApiModelProperty(value = "商户号")
+    private Long enterpriseId;
+
+    @ApiModelProperty(value = "退款渠道【支付宝、微信、现金】")
+    private String tradingChannel;
+
+    @ApiModelProperty(value = "退款状态")
+    private Integer refundStatus;
+
+    @ApiModelProperty(value = "返回编码")
+    private String refundCode;
+
+    @ApiModelProperty(value = "返回信息")
+    private String refundMsg;
+
+    @ApiModelProperty(value = "备注【订单门店,桌台信息】")
+    private String memo;
+
+    @ApiModelProperty(value = "本次退款金额")
+    private BigDecimal refundAmount;
+
+    @ApiModelProperty(value = "原订单金额")
+    private BigDecimal total;
+
+    private String dataState;
+
+    private Integer createType;
+
+    @Builder
+    public RefundRecord(Long id, String dataState, Long tradingOrderNo, Long productOrderNo, Long refundNo, Long enterpriseId, String tradingChannel, Integer refundStatus, String refundCode, String refundMsg, String memo, BigDecimal refundAmount, BigDecimal total) {
+        super(id);
+        this.tradingOrderNo = tradingOrderNo;
+        this.productOrderNo = productOrderNo;
+        this.refundNo = refundNo;
+        this.enterpriseId = enterpriseId;
+        this.tradingChannel = tradingChannel;
+        this.refundStatus = refundStatus;
+        this.refundCode = refundCode;
+        this.refundMsg = refundMsg;
+        this.memo = memo;
+        this.refundAmount = refundAmount;
+        this.total = total;
+        this.dataState = dataState;
+    }
+}

+ 100 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/entity/Trading.java

@@ -0,0 +1,100 @@
+package com.kyl.entity;
+
+
+import com.kyl.base.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+/**
+ * 交易订单表
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("trading")
+public class Trading extends BaseEntity {
+
+    private static final long serialVersionUID = -3427581867070559590L;
+
+    @ApiModelProperty(value = "openId标识")
+    private String openId;
+
+    @ApiModelProperty(value = "业务系统订单号")
+    private Long productOrderNo;
+
+    @ApiModelProperty(value = "交易系统订单号【对于三方来说:商户订单】")
+    private Long tradingOrderNo;
+
+    @ApiModelProperty(value = "支付渠道【支付宝、微信、现金、免单挂账】")
+    private String tradingChannel;
+
+    @ApiModelProperty(value = "交易类型【1账单,2订单】")
+    private String tradingType;
+
+    @ApiModelProperty(value = "交易单状态【DFK待付款,FKZ付款中,QXDD取消订单,YJS已结算,MD免单,GZ挂账】")
+    private Integer tradingState;
+
+    @ApiModelProperty(value = "收款人姓名")
+    private String payeeName;
+
+    @ApiModelProperty(value = "收款人账户ID")
+    private Long payeeId;
+
+    @ApiModelProperty(value = "付款人姓名")
+    private String payerName;
+
+    @ApiModelProperty(value = "付款人Id")
+    private Long payerId;
+
+    @ApiModelProperty(value = "交易金额")
+    private BigDecimal tradingAmount;
+
+    @ApiModelProperty(value = "退款金额【付款后】")
+    private BigDecimal refund;
+
+    @ApiModelProperty(value = "是否有退款:YES,NO")
+    private String isRefund;
+
+    @ApiModelProperty(value = "第三方交易返回编码【最终确认交易结果】")
+    private String resultCode;
+
+    @ApiModelProperty(value = "第三方交易返回提示消息【最终确认交易信息】")
+    private String resultMsg;
+
+    @ApiModelProperty(value = "第三方交易返回信息json【分析交易最终信息】")
+    private String resultJson;
+
+    @ApiModelProperty(value = "统一下单返回编码")
+    private String placeOrderCode;
+
+    @ApiModelProperty(value = "统一下单返回信息")
+    private String placeOrderMsg;
+
+    @ApiModelProperty(value = "统一下单返回信息json【用于生产二维码、Android ios唤醒支付等】")
+    private String placeOrderJson;
+
+    @ApiModelProperty(value = "商户号")
+    private Long enterpriseId;
+
+    @ApiModelProperty(value = "备注【订单门店,桌台信息】")
+    private String memo;
+
+    @ApiModelProperty(value = "二维码base64数据")
+    private String qrCode;
+
+    @ApiModelProperty(value = "是否有效")
+    protected String enableFlag;
+
+    String phone;
+
+    private String memberCreator;
+
+
+}

+ 34 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/enums/RefundStatusEnum.java

@@ -0,0 +1,34 @@
+package com.kyl.enums;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * 退款状态枚举
+ *
+ * @author WanJl
+ * @version 1.0
+ */
+public enum RefundStatusEnum {
+
+    SENDING(1, "退款中"),
+    SUCCESS(2, "成功"),
+    FAIL(3, "失败");
+
+    @JsonValue
+    private Integer code;
+    private String value;
+
+    RefundStatusEnum(Integer code, String value) {
+        this.code = code;
+        this.value = value;
+    }
+
+
+    public Integer getCode() {
+        return this.code;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+}

+ 63 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/enums/TradingEnum.java

@@ -0,0 +1,63 @@
+package com.kyl.enums;
+
+import com.kyl.base.IBasicEnum;
+
+/**
+ * 交易枚举
+ */
+public enum TradingEnum implements IBasicEnum {
+
+    SUCCEED(1001, 200, "操作成功"),
+    ERROR(1002, "操作失败"),
+    CHECK_TRADING_FAIL(1003, "交易单校验失败"),
+    TRY_LOCK_TRADING_FAIL(1004, "交易单加锁失败"),
+    PAYING_TRADING_FAIL(1005, "交易单支付失败"),
+    TRADING_STATE_SUCCEED(1006, "交易单已完成"),
+    TRADING_STATE_PAYING(1007, "交易单交易中"),
+    CONFIG_EMPTY(1008, "支付配置为空"),
+    CONFIG_ERROR(1009, "支付配置错误"),
+    NATIVE_PAY_FAIL(1010, "统一下单交易失败"),
+    NATIVE_QRCODE_FAIL(1011, "生成二维码失败"),
+    TRAD_QUERY_REFUND_FAIL(1030, "统一下单交易退款失败"),
+    REFUND_FAIL(1012, "统一下单交易退款失败"),
+    SAVE_OR_UPDATE_FAIL(1013, "交易单保存或修改失败"),
+    TRADING_TYPE_FAIL(1014, "未定义的交易类型"),
+    NATIVE_QUERY_FAIL(1015, "查询统一下单交易失败"),
+    NATIVE_REFUND_FAIL(1016, "统一下单退款交易失败"),
+    NATIVE_QUERY_REFUND_FAIL(1017, "统一下单查询退款失败"),
+    CASH_PAY_FAIL(1018, "现金交易失败"),
+    CASH_REFUND_FAIL(1019, "统一下单退款交易失败"),
+    CREDIT_PAY_FAIL(1020, "信用交易失败"),
+    LIST_TRADE_STATE_FAIL(1021, "按交易状态查询交易单失败"),
+    NOT_FOUND(1022, "交易单不存在"),
+    CLOSE_FAIL(1023, "关闭交易单失败"),
+    BASIC_REFUND_OUT_FAIL(1024, "退款金额超过订单总金额"),
+    REFUND_NOT_FOUND(1025, "退款记录不存在"),
+    REFUND_ALREADY_COMPLETED(1026, "退款记录已经完成"),
+    BASIC_REFUND_COUNT_OUT_FAIL(1027, "退款次数超出限制,最多20次"),
+    TRADING_QUERY_PARAM_ERROR(1028, "查询交易单错误,订单号或交易单号至少传递一个"),
+    REFUND_QUERY_PARAM_ERROR(1029, "查询退款单错误,订单号或交易单号至少传递一个");
+
+    private int code;
+    private String msg;
+
+    TradingEnum(Integer code, String msg) {
+        this.code = code;
+        this.msg = msg;
+
+    }
+
+    TradingEnum(int code, Integer status, String value) {
+        this.code = code;
+        this.msg = msg;
+    }
+    @Override
+    public int getCode() {
+        return code;
+    }
+    @Override
+    public String getMsg() {
+        return this.msg;
+    }
+
+}

+ 39 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/enums/TradingStateEnum.java

@@ -0,0 +1,39 @@
+package com.kyl.enums;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * 交易单状态枚举
+ *
+ * @author WanJl
+ * @version 1.0
+ */
+public enum TradingStateEnum {
+
+    DFK(1, "待付款"),
+    FKZ(2, "付款中"),
+    FKSB(3, "付款失败"),
+    YJS(4, "已结算(已付款)"),
+    QXDD(5, "取消订单"),
+    MD(6, "免单"),
+    GZ(7, "挂账");
+
+    @JsonValue
+    private Integer code;
+    private String value;
+
+    TradingStateEnum(Integer code, String value) {
+        this.code = code;
+        this.value = value;
+    }
+
+
+    public Integer getCode() {
+        return this.code;
+    }
+
+
+    public String getValue() {
+        return this.value;
+    }
+}

+ 71 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/handler/BeforePayHandler.java

@@ -0,0 +1,71 @@
+package com.kyl.handler;
+
+import com.kyl.exception.ProjectException;
+import com.kyl.vo.RefundRecordVo;
+import com.kyl.vo.TradingVo;
+
+/**
+ * @ClassName IdempotentHandler.java
+ *  交易前置处理接口
+ */
+
+public interface BeforePayHandler {
+
+
+    /***
+     *  CreateTrading交易幂等性
+     * @param tradingVo 交易订单
+     * @return
+     */
+    void idempotentCreateTrading(TradingVo tradingVo) throws ProjectException;
+
+    /***
+     *  CreateTrading交易单参数校验
+     * @param tradingVo 交易订单
+     * @return
+     */
+    Boolean checkeCreateTrading(TradingVo tradingVo);
+
+    /***
+     *  QueryTrading交易单参数校验
+     * @param tradingVo 交易订单
+     * @return
+     */
+    Boolean checkeQueryTrading(TradingVo tradingVo);
+
+    /***
+     *  RefundTrading退款交易幂等性
+     * @param tradingVo 交易订单
+     * @return
+     */
+    void idempotentRefundTrading(TradingVo tradingVo);
+
+    /***
+     *  RefundTrading退款交易单参数校验
+     * @param tradingVo 交易订单
+     * @return
+     */
+    Boolean checkeRefundTrading(TradingVo tradingVo);
+
+
+    /***
+     *  QueryRefundTrading交易单参数校验
+     * @param refundRecordVo 退款记录
+     * @return
+     */
+    Boolean checkeQueryRefundTrading(RefundRecordVo refundRecordVo);
+
+    /***
+     *  closeTradin交易单参数校验c
+     * @param tradingVo 交易订单
+     * @return
+     */
+    Boolean checkeCloseTrading(TradingVo tradingVo);
+
+    /***
+     *  DownLoadBill下载订单交易
+     * @param tradingVo 交易订单
+     * @return
+     */
+    Boolean checkeDownLoadBill(TradingVo tradingVo);
+}

+ 55 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/handler/CommonPayHandler.java

@@ -0,0 +1,55 @@
+package com.kyl.handler;
+
+import com.kyl.vo.RefundRecordVo;
+import com.kyl.vo.TradingVo;
+
+/**
+ * @ClassName CommonPayHandler.java
+ *  基础交易处理接口
+ */
+public interface CommonPayHandler {
+
+    /***
+     *  统一收单线下交易查询
+     * 该接口提供所有支付订单的查询,商户可以通过该接口主动查询订单状态,完成下一步的业务逻辑。
+     * @param tradingVo 交易单
+     * @return
+     */
+    TradingVo queryTrading(TradingVo tradingVo);
+
+    /***
+     *  统一收单交易退款接口
+     * 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,
+     * 将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。
+     * @param tradingVo 交易单
+     * @return
+     */
+    TradingVo refundTrading(TradingVo tradingVo);
+
+    /***
+     *  统一收单交易退款查询接口
+     * 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,
+     * 将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。
+     * @param refundRecordVo 退款交易单
+     * @return
+     */
+    RefundRecordVo queryRefundTrading(RefundRecordVo refundRecordVo) ;
+
+    /***
+     *  统一关闭订单
+     * 1、商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
+     * 2、系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
+     * @param tradingVo 交易单
+     * @return
+     */
+    TradingVo closeTrading(TradingVo tradingVo);
+
+    /***
+     *  为方便商户快速查账,支持商户通过本接口获取商户离线账单下载地址
+     * @param tradingVo 退款交易单
+     * @return
+     */
+    TradingVo downLoadBill(TradingVo tradingVo);
+
+
+}

+ 17 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/handler/WapPayHandler.java

@@ -0,0 +1,17 @@
+package com.kyl.handler;
+
+import com.kyl.vo.TradingVo;
+
+/**
+ * @ClassName WapPayHandler.java
+ *  手机网页支付处理
+ */
+public interface WapPayHandler extends CommonPayHandler {
+
+    /***
+     *  生成交易表单,渲染后自动跳转支付宝或者微信引导用户完成支付
+     * @param tradingVo 订单单
+     * @return TradingVo对象中的placeOrderJson:阿里云form表单字符串、微信prepay_id标识
+     */
+    TradingVo wapTrading(TradingVo tradingVo);
+}

+ 198 - 0
kyl-sanatorium/kyl-pay/src/main/java/com/kyl/handler/basic/BasicBeforePayHandler.java

@@ -0,0 +1,198 @@
+package com.kyl.handler.basic;
+
+import com.kyl.constant.TradingConstant;
+import com.kyl.entity.RefundRecord;
+import com.kyl.entity.Trading;
+import com.kyl.enums.TradingEnum;
+import com.kyl.exception.ProjectException;
+import com.kyl.handler.BeforePayHandler;
+import com.kyl.service.RefundRecordService;
+import com.kyl.service.TradingService;
+import com.kyl.utils.EmptyUtil;
+import com.kyl.utils.UUID;
+import com.kyl.vo.RefundRecordVo;
+import com.kyl.vo.TradingVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @ClassName BeforePayHandlerImpl.java
+ *  阿里交易前置处理接口实现
+ */
+@Component("beforePayHandler")
+public class BasicBeforePayHandler implements BeforePayHandler {
+
+    @Autowired
+    TradingService tradingService;
+
+    @Autowired
+    RefundRecordService refundRecordService;
+
+
+    @Override
+    public void idempotentCreateTrading(TradingVo tradingVo) throws ProjectException {
+        //查询当前订单对应的最近交易单
+        Trading trading = tradingService.findTradByProductOrderNo(tradingVo.getProductOrderNo(), tradingVo.getTradingType());
+        //交易单为空:之前未对此订单做过支付
+        if (EmptyUtil.isNullOrEmpty(trading)) {
+            tradingVo.setTradingOrderNo(UUID.getSecureRandom().nextLong());
+            return;
+        }
+        //交易单不为空:判定交易单状态
+        if (trading.getTradingState().equals(TradingConstant.TRADE_SUCCESS_4)){
+            throw new ProjectException(TradingEnum.TRADING_STATE_SUCCEED);
+        }else if (trading.getTradingState().equals(TradingConstant.TRADE_WAIT_BUYER_PAY_1)){
+            throw new ProjectException(TradingEnum.TRADING_STATE_PAYING);
+        }else if (trading.getTradingState().equals(TradingConstant.TRADE_CLOSED_5)){
+            tradingVo.setTradingOrderNo(UUID.getSecureRandom().nextLong());
+        }else{
+            throw new ProjectException(TradingEnum.PAYING_TRADING_FAIL);
+        }
+    }
+
+    @Override
+    public void idempotentRefundTrading(TradingVo tradingVo) {
+        //查询交易单是否为以结算订单
+        Trading trading = tradingService.findTradByTradingOrderNo(tradingVo.getTradingOrderNo());
+        //交易单不存在,或者不为已结算状态:抛出退款失败异常
+        if (EmptyUtil.isNullOrEmpty(trading)|| !TradingConstant.TRADE_SUCCESS_4.equals(trading.getTradingState())){
+            throw new ProjectException(TradingEnum.REFUND_FAIL);
+        }else {
+            tradingVo.setTradingOrderNo(trading.getTradingOrderNo());
+            tradingVo.setId(trading.getId());
+            tradingVo.setTradingAmount(tradingVo.getOperTionRefund());
+        }
+        //查询是否有退款中的退款记录
+        RefundRecord refundRecord = refundRecordService
+                .findRefundRecordByProductOrderNoAndSending(tradingVo.getProductOrderNo());
+        if (!EmptyUtil.isNullOrEmpty(refundRecord)){
+            throw new ProjectException(TradingEnum.REFUND_FAIL);
+        }
+    }
+
+    @Override
+    public Boolean checkeCreateTrading(TradingVo tradingVo) {
+        Boolean flag =null;
+        //订单为空
+        if (EmptyUtil.isNullOrEmpty(tradingVo)) {
+            flag = false;
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getOpenId())){
+            flag = false;
+        //订单号为空
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getProductOrderNo())){
+            flag = false;
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getTradingAmount())){
+            flag = false;
+        }else {
+            flag = true;
+        }
+        return flag;
+    }
+
+    @Override
+    public Boolean checkeQueryTrading(TradingVo tradingVo) {
+        Boolean flag =null;
+        //订单为空
+        if (EmptyUtil.isNullOrEmpty(tradingVo)) {
+            flag = false;
+        //企业号为空
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getEnterpriseId())){
+            flag = false;
+         //交易号为空
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getTradingOrderNo())){
+            flag = false;
+        }else {
+            flag = true;
+        }
+        return flag;
+    }
+
+    @Override
+    public Boolean checkeRefundTrading(TradingVo tradingVo) {
+
+        Boolean flag =null;
+        //订单为空
+        if (EmptyUtil.isNullOrEmpty(tradingVo)) {
+            flag = false;
+//        //企业号为空
+//        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getEnterpriseId())){
+//            flag = false;
+        //交易号为空
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getTradingOrderNo())){
+            flag = false;
+         //退款请求号为空
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getOutRequestNo())){
+            flag = false;
+        //当前退款金额为空
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getOperTionRefund())){
+            flag = false;
+        //退款总金额不可超剩余实付总金额
+        }else if (tradingVo.getOperTionRefund().compareTo(tradingVo.getTradingAmount()) > 0){
+            flag = false;
+        }else {
+            flag = true;
+        }
+
+        return flag;
+    }
+
+    @Override
+    public Boolean checkeQueryRefundTrading(RefundRecordVo refundRecordVo) {
+        Boolean flag =null;
+        //订单为空
+        if (EmptyUtil.isNullOrEmpty(refundRecordVo)) {
+            flag = false;
+        //企业号为空
+        }else if (EmptyUtil.isNullOrEmpty(refundRecordVo.getEnterpriseId())){
+            flag = false;
+        //交易号为空
+        }else if (EmptyUtil.isNullOrEmpty(refundRecordVo.getTradingOrderNo())){
+            flag = false;
+         //退款请求号为空
+        }else if (EmptyUtil.isNullOrEmpty(refundRecordVo.getRefundNo())){
+            flag = false;
+        }else {
+            flag = true;
+        }
+        return flag;
+    }
+
+    @Override
+    public Boolean checkeCloseTrading(TradingVo tradingVo) {
+        Boolean flag =null;
+        //订单为空
+        if (EmptyUtil.isNullOrEmpty(tradingVo)) {
+            flag = false;
+        //企业号为空
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getEnterpriseId())){
+            flag = false;
+        //交易号为空
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getTradingOrderNo())){
+            flag = false;
+        }else {
+            flag = true;
+        }
+        return flag;
+    }
+
+    @Override
+    public Boolean checkeDownLoadBill(TradingVo tradingVo) {
+        Boolean flag =null;
+        //订单为空
+        if (EmptyUtil.isNullOrEmpty(tradingVo)) {
+            flag = false;
+        //企业号为空
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getEnterpriseId())){
+            flag = false;
+        //账单日期
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getBillDate())){
+            flag = false;
+        //账单账单类型
+        }else if (EmptyUtil.isNullOrEmpty(tradingVo.getBillType())){
+            flag = false;
+        }else {
+            flag = true;
+        }
+        return flag;
+    }
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов