diff --git a/.idea/code.iml b/.idea/code.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/code.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/constant/AutoFillConstant.java b/sky/sky-common/src/main/java/com/sky/constant/AutoFillConstant.java
new file mode 100644
index 0000000..5c9f8ae
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/constant/AutoFillConstant.java
@@ -0,0 +1,36 @@
+package com.sky.constant;
+
+/**
+ * 公共字段自动填充相关常量
+ *
+ * 此类用于定义实体类中自动填充公共字段时所需的方法名称常量。
+ */
+public class AutoFillConstant {
+ /**
+ * 实体类中设置创建时间的方法名称
+ *
+ * 当实体类需要自动填充创建时间时,可以使用该常量作为方法名称。
+ */
+ public static final String SET_CREATE_TIME = "setCreateTime";
+
+ /**
+ * 实体类中设置更新时间的方法名称
+ *
+ * 当实体类需要自动填充更新时间时,可以使用该常量作为方法名称。
+ */
+ public static final String SET_UPDATE_TIME = "setUpdateTime";
+
+ /**
+ * 实体类中设置创建者的方法名称
+ *
+ * 当实体类需要自动填充创建者信息时,可以使用该常量作为方法名称。
+ */
+ public static final String SET_CREATE_USER = "setCreateUser";
+
+ /**
+ * 实体类中设置更新者的方法名称
+ *
+ * 当实体类需要自动填充更新者信息时,可以使用该常量作为方法名称。
+ */
+ public static final String SET_UPDATE_USER = "setUpdateUser";
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/constant/JwtClaimsConstant.java b/sky/sky-common/src/main/java/com/sky/constant/JwtClaimsConstant.java
new file mode 100644
index 0000000..24ec50d
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/constant/JwtClaimsConstant.java
@@ -0,0 +1,43 @@
+/**
+ * JwtClaimsConstant 类定义了一组常量,这些常量用于在 JWT(JSON Web Tokens)中表示特定的声明(claims)。
+ * 这些常量提供了一种标准化的方式来引用 JWT 中的声明,使得代码更加清晰和易于维护。
+ */
+package com.sky.constant;
+
+import java.time.LocalDateTime;
+
+public class JwtClaimsConstant {
+
+ /**
+ * 代表员工ID的常量。
+ * 这个常量用于在 JWT 中标识员工的唯一标识符。
+ */
+ public static final String EMP_ID = "empId";
+
+ /**
+ * 代表用户ID的常量。
+ * 这个常量用于在 JWT 中标识用户的唯一标识符。
+ */
+ public static final String USER_ID = "userId";
+
+ // 下面的常量被注释掉了,如果需要使用,可以取消注释并添加相应的文档注释。
+
+ /*
+ * 代表用户电话号码的常量。
+ * 这个常量用于在 JWT 中标识用户的电话号码。
+ * public static final String PHONE = "phone";
+ */
+
+ /*
+ * 代表用户登录名的常量。
+ * 这个常量用于在 JWT 中标识用户的登录名。
+ * public static final String USERNAME = "username";
+ */
+
+ /*
+ * 代表用户姓名的常量。
+ * 这个常量用于在 JWT 中标识用户的姓名。
+ * public static final String NAME = "name";
+ */
+
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/constant/StatusConstant.java b/sky/sky-common/src/main/java/com/sky/constant/StatusConstant.java
new file mode 100644
index 0000000..3a0b469
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/constant/StatusConstant.java
@@ -0,0 +1,23 @@
+package com.sky.constant;
+
+/**
+ * 状态常量,用于表示启用或者禁用的状态。
+ *
+ * 此类定义了两个常量,分别用于表示启用(ENABLE)和禁用(DISABLE)的状态。
+ */
+public class StatusConstant {
+
+ /**
+ * 启用状态的标识
+ *
+ * 当需要表示一个状态为启用时,可以使用该常量。
+ */
+ public static final Integer ENABLE = 1;
+
+ /**
+ * 禁用状态的标识
+ *
+ * 当需要表示一个状态为禁用时,可以使用该常量。
+ */
+ public static final Integer DISABLE = 0;
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/context/BaseContext.java b/sky/sky-common/src/main/java/com/sky/context/BaseContext.java
new file mode 100644
index 0000000..01c08a0
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/context/BaseContext.java
@@ -0,0 +1,43 @@
+package com.sky.context;
+
+/**
+ * BaseContext 类提供了一个基于 ThreadLocal 的机制来存储和检索当前线程的上下文信息。
+ * 这种设计模式常用于保持线程安全的状态,例如在一次请求的生命周期内跟踪用户会话。
+ */
+public class BaseContext {
+
+ /**
+ * threadLocal 是一个 ThreadLocal 对象,用于存储每个线程的局部变量。
+ * 它保证了每个线程都拥有自己的变量副本,避免了多线程环境下的共享状态问题。
+ */
+ private static ThreadLocal threadLocal = new ThreadLocal<>();
+
+ /**
+ * 设置当前线程的上下文ID。
+ * 这个ID可以是用户ID、会话ID或其他任何需要在请求处理过程中保持的标识符。
+ *
+ * @param id 要设置的上下文ID。
+ */
+ public static void setCurrentId(Long id) {
+ threadLocal.set(id);
+ }
+
+ /**
+ * 获取当前线程的上下文ID。
+ * 如果当前线程没有设置过ID,这个方法将返回 null。
+ *
+ * @return 当前线程的上下文ID。
+ */
+ public static Long getCurrentId() {
+ return threadLocal.get();
+ }
+
+ /**
+ * 移除当前线程的上下文ID。
+ * 这个方法通常在请求处理结束时被调用,以清理线程局部变量,避免内存泄漏。
+ */
+ public static void removeCurrentId() {
+ threadLocal.remove();
+ }
+
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/exception/AccountNotFoundException.java b/sky/sky-common/src/main/java/com/sky/exception/AccountNotFoundException.java
new file mode 100644
index 0000000..064b19e
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/exception/AccountNotFoundException.java
@@ -0,0 +1,27 @@
+package com.sky.exception;
+
+/**
+ * 账号不存在异常
+ *
+ * 此类表示当尝试访问或操作一个不存在的账号时抛出的异常。
+ */
+public class AccountNotFoundException extends BaseException {
+
+ /**
+ * 构造一个账号不存在异常,无自定义错误信息。
+ *
+ * 使用默认的错误消息。
+ */
+ public AccountNotFoundException() {
+ super();
+ }
+
+ /**
+ * 构造一个账号不存在异常,并提供自定义错误信息。
+ *
+ * @param msg 自定义错误信息,描述账号不存在的具体情况。
+ */
+ public AccountNotFoundException(String msg) {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/exception/AddressBookBusinessException.java b/sky/sky-common/src/main/java/com/sky/exception/AddressBookBusinessException.java
new file mode 100644
index 0000000..ba5eb52
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/exception/AddressBookBusinessException.java
@@ -0,0 +1,19 @@
+package com.sky.exception;
+
+/**
+ * AddressBookBusinessException 类是一个自定义的业务异常类,专门用于处理地址簿应用中特定的业务逻辑错误。
+ * 这个异常类继承自 BaseException,使得它可以保持一致的异常处理机制,同时允许针对地址簿业务逻辑错误进行特定的异常处理。
+ */
+public class AddressBookBusinessException extends BaseException {
+
+ /**
+ * 构造函数,初始化 AddressBookBusinessException 实例。
+ *
+ * @param msg 错误信息,描述了异常的具体原因。
+ * 这个信息通常用于日志记录和错误报告,以便开发者或用户了解异常的具体情况。
+ */
+ public AddressBookBusinessException(String msg) {
+ super(msg); // 调用父类 BaseException 的构造函数,传递错误信息。
+ }
+
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/exception/LoginFailedException.java b/sky/sky-common/src/main/java/com/sky/exception/LoginFailedException.java
new file mode 100644
index 0000000..22704a7
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/exception/LoginFailedException.java
@@ -0,0 +1,17 @@
+package com.sky.exception;
+
+/**
+ * 登录失败异常
+ *
+ * 此类表示当用户登录系统时失败抛出的异常。
+ */
+public class LoginFailedException extends BaseException {
+ /**
+ * 构造一个登录失败异常,并提供自定义错误信息。
+ *
+ * @param msg 自定义错误信息,描述登录失败的具体情况。
+ */
+ public LoginFailedException(String msg) {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/exception/OrderBusinessException.java b/sky/sky-common/src/main/java/com/sky/exception/OrderBusinessException.java
new file mode 100644
index 0000000..e86a305
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/exception/OrderBusinessException.java
@@ -0,0 +1,19 @@
+package com.sky.exception;
+
+/**
+ * OrderBusinessException 类是一个自定义的业务异常类,专门用于处理订单应用中特定的业务逻辑错误。
+ * 这个异常类继承自 BaseException,使得它可以保持一致的异常处理机制,同时允许针对订单业务逻辑错误进行特定的异常处理。
+ */
+public class OrderBusinessException extends BaseException {
+
+ /**
+ * 构造函数,初始化 OrderBusinessException 实例。
+ *
+ * @param msg 错误信息,描述了异常的具体原因。
+ * 这个信息通常用于日志记录和错误报告,以便开发者或用户了解异常的具体情况。
+ */
+ public OrderBusinessException(String msg) {
+ super(msg); // 调用父类 BaseException 的构造函数,传递错误信息。
+ }
+
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/exception/SetmealEnableFailedException.java b/sky/sky-common/src/main/java/com/sky/exception/SetmealEnableFailedException.java
new file mode 100644
index 0000000..ccaeb7f
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/exception/SetmealEnableFailedException.java
@@ -0,0 +1,27 @@
+package com.sky.exception;
+
+/**
+ * 套餐启用失败异常
+ *
+ * 此类表示当尝试启用一个套餐时失败抛出的异常。
+ */
+public class SetmealEnableFailedException extends BaseException {
+
+ /**
+ * 构造一个套餐启用失败异常,无自定义错误信息。
+ *
+ * 使用默认的错误消息。
+ */
+ public SetmealEnableFailedException() {
+ super();
+ }
+
+ /**
+ * 构造一个套餐启用失败异常,并提供自定义错误信息。
+ *
+ * @param msg 自定义错误信息,描述套餐启用失败的具体情况。
+ */
+ public SetmealEnableFailedException(String msg) {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/exception/ShoppingCartBusinessException.java b/sky/sky-common/src/main/java/com/sky/exception/ShoppingCartBusinessException.java
new file mode 100644
index 0000000..39bb895
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/exception/ShoppingCartBusinessException.java
@@ -0,0 +1,19 @@
+package com.sky.exception;
+
+/**
+ * ShoppingCartBusinessException 类是一个自定义的业务异常类,专门用于处理购物车应用中特定的业务逻辑错误。
+ * 这个异常类继承自 BaseException,使得它可以保持一致的异常处理机制,同时允许针对购物车业务逻辑错误进行特定的异常处理。
+ */
+public class ShoppingCartBusinessException extends BaseException {
+
+ /**
+ * 构造函数,用于创建一个包含特定错误消息的 ShoppingCartBusinessException 实例。
+ *
+ * @param msg 详细描述异常原因的错误消息。
+ * 这个错误消息对于调试和日志记录至关重要,因为它提供了异常发生的上下文信息。
+ */
+ public ShoppingCartBusinessException(String msg) {
+ super(msg); // 调用父类 BaseException 的构造函数,并传递错误消息。
+ }
+
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/properties/AliOssProperties.java b/sky/sky-common/src/main/java/com/sky/properties/AliOssProperties.java
new file mode 100644
index 0000000..825c141
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/properties/AliOssProperties.java
@@ -0,0 +1,42 @@
+package com.sky.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 阿里云OSS配置属性类
+ *
+ * 此类用于存储和反射阿里云OSS的相关配置属性。
+ */
+@Component
+@ConfigurationProperties(prefix = "sky.alioss") // 指定配置文件中相关属性的前缀
+@Data // 使用lombok提供的注解来自动生成getter和setter方法,以及toString等方法
+public class AliOssProperties {
+
+ /**
+ * OSS服务的终端节点
+ */
+ private String endpoint;
+
+ /**
+ * 访问密钥ID
+ */
+ private String accessKeyId;
+
+ /**
+ * 访问密钥秘密
+ */
+ private String accessKeySecret;
+
+ /**
+ * 存储空间名称
+ */
+ private String bucketName;
+
+ /**
+ * 上传文件的路径
+ */
+ private String uploadPath;
+
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/properties/JwtProperties.java b/sky/sky-common/src/main/java/com/sky/properties/JwtProperties.java
new file mode 100644
index 0000000..c5c2c60
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/properties/JwtProperties.java
@@ -0,0 +1,53 @@
+package com.sky.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * JwtProperties 类用于存储和配置 JWT(JSON Web Tokens)相关的属性。
+ * 这个类通过 Spring Boot 的 @ConfigurationProperties 注解与 application.properties 或 application.yml 文件中的配置项绑定。
+ */
+@Component
+@ConfigurationProperties(prefix = "sky.jwt") // 指定配置文件中属性的前缀
+@Data
+public class JwtProperties {
+
+ /**
+ * adminSecretKey 用于管理端员工生成 JWT 令牌的密钥。
+ * 这个密钥用于签名和验证 JWT,确保令牌的安全性。
+ */
+ private String adminSecretKey;
+
+ /**
+ * adminTtl 表示管理端员工 JWT 令牌的有效期(单位:毫秒)。
+ * 这个值定义了令牌在过期前的有效时长。
+ */
+ private long adminTtl;
+
+ /**
+ * adminTokenName 表示管理端员工 JWT 令牌的名称。
+ * 这个名称用于在 HTTP 请求头中标识 JWT 令牌。
+ */
+ private String adminTokenName;
+
+ /**
+ * userSecretKey 用于用户端微信用户生成 JWT 令牌的密钥。
+ *
+ * 这个密钥同样用于签名和验证 JWT,确保令牌的安全性。
+ */
+ private String userSecretKey;
+
+ /**
+ * userTtl 表示用户端微信用户 JWT 令牌的有效期(单位:毫秒)。
+ * 这个值定义了用户令牌在过期前的有效时长。
+ */
+ private long userTtl;
+
+ /**
+ * userTokenName 表示用户端微信用户 JWT 令牌的名称。
+ * 这个名称用于在 HTTP 请求头中标识 JWT 令牌。
+ */
+ private String userTokenName;
+
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/result/PageResult.java b/sky/sky-common/src/main/java/com/sky/result/PageResult.java
new file mode 100644
index 0000000..283c0f7
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/result/PageResult.java
@@ -0,0 +1,34 @@
+package com.sky.result;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 封装分页查询结果的类
+ *
+ * 此类用于封装分页查询的结果,包括总记录数和当前页的数据集合。
+ */
+@Data // 使用lombok提供的注解来自动生成getter和setter方法,以及toString等方法
+@AllArgsConstructor // 使用lombok提供的注解来自动生成包含所有参数的构造函数
+@NoArgsConstructor // 使用lombok提供的注解来自动生成无参数的构造函数
+public class PageResult implements Serializable {
+
+ /**
+ * 总记录数
+ *
+ * 表示查询结果的总记录数。
+ */
+ private long total; // 总记录数
+
+ /**
+ * 当前页数据集合
+ *
+ * 表示当前页的数据集合。
+ */
+ private List records; // 当前页数据集合
+
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/result/Result.java b/sky/sky-common/src/main/java/com/sky/result/Result.java
new file mode 100644
index 0000000..2dd24a0
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/result/Result.java
@@ -0,0 +1,76 @@
+package com.sky.result;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * Result 类用于封装后端统一返回的结果。
+ * 这个类实现了 Serializable 接口,以便在网络传输或持久化时能够被序列化。
+ *
+ * @param 表示返回数据的类型,可以是任意类型。
+ */
+@Data
+public class Result implements Serializable {
+
+ private static final long serialVersionUID = 1L; // 序列化版本号,用于确保反序列化时的兼容性
+
+ /**
+ * 编码:1 表示成功,0 和其它数字表示失败。
+ * 这个字段用于指示操作的结果状态。
+ */
+ private Integer code;
+
+ /**
+ * 错误信息,用于描述操作失败的原因。
+ * 这个字段在操作失败时提供详细的错误信息,便于调试和用户反馈。
+ */
+ private String msg;
+
+ /**
+ * 数据字段,存储返回的具体数据。
+ * 这个字段可以是任意类型的数据,通常用于返回操作成功时的结果。
+ */
+ private T data;
+
+ /**
+ * 静态方法,创建一个表示成功的 Result 对象,不带数据。
+ *
+ * @param 返回数据的类型
+ * @return 一个成功的 Result 对象,状态码为 1。
+ */
+ public static Result success() {
+ Result result = new Result();
+ result.code = 1; // 设置状态码为成功
+ return result;
+ }
+
+ /**
+ * 静态方法,创建一个表示成功的 Result 对象,并包含返回数据。
+ *
+ * @param object 要返回的数据
+ * @param 返回数据的类型
+ * @return 一个成功的 Result 对象,状态码为 1,包含返回的数据。
+ */
+ public static Result success(T object) {
+ Result result = new Result();
+ result.data = object; // 设置返回的数据
+ result.code = 1; // 设置状态码为成功
+ return result;
+ }
+
+ /**
+ * 静态方法,创建一个表示失败的 Result 对象,并包含错误信息。
+ *
+ * @param msg 错误信息,描述失败的原因
+ * @param 返回数据的类型
+ * @return 一个失败的 Result 对象,状态码为 0,包含错误信息。
+ */
+ public static Result error(String msg) {
+ Result result = new Result();
+ result.msg = msg; // 设置错误信息
+ result.code = 0; // 设置状态码为失败
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/utils/JwtUtil.java b/sky/sky-common/src/main/java/com/sky/utils/JwtUtil.java
new file mode 100644
index 0000000..b8e2c1a
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/utils/JwtUtil.java
@@ -0,0 +1,70 @@
+package com.sky.utils;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * JWT工具类
+ *
+ * 提供JWT的生成和解析功能。
+ */
+public class JwtUtil {
+
+ /**
+ * 生成JWT
+ *
+ * 使用HS256算法,私钥使用固定秘钥。
+ *
+ * @param secretKey JWT秘钥
+ * @param ttlMillis JWT过期时间(毫秒)
+ * @param claims 设置的信息
+ * @return 生成的JWT字符串
+ */
+ public static String createJWT(String secretKey, long ttlMillis, Map claims) {
+ // 指定签名时使用的签名算法,即header部分
+ SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
+
+ // 生成JWT的时间
+ long expMillis = System.currentTimeMillis() + ttlMillis;
+ Date exp = new Date(expMillis);
+
+ // 设置jwt的body
+ JwtBuilder builder = Jwts.builder()
+ // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
+ .setClaims(claims)
+ // 设置签名使用的签名算法和签名使用的秘钥
+ .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
+ // 设置过期时间
+ .setExpiration(exp);
+
+ return builder.compact();
+ }
+
+ /**
+ * Token解密
+ *
+ * 解析JWT字符串,获取其中的Claims信息。
+ *
+ * @param secretKey JWT秘钥,此秘钥一定要保留好在服务端,不能暴露出去,否则sign就可以被伪造。
+ * 如果对接多个客户端建议改造成多个。
+ * @param token 加密后的token
+ * @return 解析后的Claims对象
+ */
+ public static Claims parseJWT(String secretKey, String token) {
+ // 得到DefaultJwtParser
+ Claims claims = Jwts.parser()
+ // 设置签名的秘钥
+ .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
+ // 设置需要解析的jwt
+ .parseClaimsJws(token)
+ .getBody();
+ return claims;
+ }
+
+}
\ No newline at end of file
diff --git a/sky/sky-common/src/main/java/com/sky/utils/WeChatPayUtil.java b/sky/sky-common/src/main/java/com/sky/utils/WeChatPayUtil.java
new file mode 100644
index 0000000..585ccf1
--- /dev/null
+++ b/sky/sky-common/src/main/java/com/sky/utils/WeChatPayUtil.java
@@ -0,0 +1,233 @@
+package com.sky.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.sky.properties.WeChatProperties;
+import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
+import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.http.HttpHeaders;
+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.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.math.BigDecimal;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+
+/**
+ * 微信支付工具类,提供微信支付相关的操作,如下单、退款等。
+ */
+@Component
+public class WeChatPayUtil {
+
+ /**
+ * 微信支付下单接口地址。
+ */
+ public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
+
+ /**
+ * 申请退款接口地址。
+ */
+ public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
+
+ @Autowired
+ private WeChatProperties weChatProperties;
+
+ /**
+ * 获取调用微信接口的客户端工具对象。
+ * 使用商户API私钥和微信支付平台证书进行签名和验签。
+ *
+ * @return CloseableHttpClient 客户端工具对象。
+ */
+ private CloseableHttpClient getClient() {
+ PrivateKey merchantPrivateKey = null;
+ try {
+ // 加载商户API私钥
+ merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));
+ // 加载微信支付平台证书
+ X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));
+ List wechatPayCertificates = Arrays.asList(x509Certificate);
+
+ // 构建HttpClient,自动处理签名和验签
+ WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
+ .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
+ .withWechatPay(wechatPayCertificates);
+ CloseableHttpClient httpClient = builder.build();
+ return httpClient;
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 发送POST请求到指定URL。
+ *
+ * @param url 请求的URL地址
+ * @param body 请求的JSON体
+ * @return 响应结果字符串
+ * @throws Exception 可能抛出的异常
+ */
+ private String post(String url, String body) throws Exception {
+ CloseableHttpClient httpClient = getClient();
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
+ httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
+ httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
+ httpPost.setEntity(new StringEntity(body, "UTF-8"));
+
+ CloseableHttpResponse response = httpClient.execute(httpPost);
+ try {
+ String bodyAsString = EntityUtils.toString(response.getEntity());
+ return bodyAsString;
+ } finally {
+ httpClient.close();
+ response.close();
+ }
+ }
+
+ /**
+ * 发送GET请求到指定URL。
+ *
+ * @param url 请求的URL地址
+ * @return 响应结果字符串
+ * @throws Exception 可能抛出的异常
+ */
+ private String get(String url) throws Exception {
+ CloseableHttpClient httpClient = getClient();
+ HttpGet httpGet = new HttpGet(url);
+ httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
+ httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
+ httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
+
+ CloseableHttpResponse response = httpClient.execute(httpGet);
+ try {
+ String bodyAsString = EntityUtils.toString(response.getEntity());
+ return bodyAsString;
+ } finally {
+ httpClient.close();
+ response.close();
+ }
+ }
+
+ /**
+ * 使用JSAPI进行微信支付下单。
+ *
+ * @param orderNum 商户订单号
+ * @param total 总金额(元)
+ * @param description 商品描述
+ * @param openid 微信用户的openid
+ * @return 响应结果字符串
+ * @throws Exception 可能抛出的异常
+ */
+ private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("appid", weChatProperties.getAppid());
+ jsonObject.put("mchid", weChatProperties.getMchid());
+ jsonObject.put("description", description);
+ jsonObject.put("out_trade_no", orderNum);
+ jsonObject.put("notify_url", weChatProperties.getNotifyUrl());
+
+ JSONObject amount = new JSONObject();
+ amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
+ amount.put("currency", "CNY");
+
+ jsonObject.put("amount", amount);
+
+ JSONObject payer = new JSONObject();
+ payer.put("openid", openid);
+
+ jsonObject.put("payer", payer);
+
+ String body = jsonObject.toJSONString();
+ return post(JSAPI, body);
+ }
+
+ /**
+ * 小程序支付接口。
+ *
+ * @param orderNum 商户订单号
+ * @param total 金额,单位元
+ * @param description 商品描述
+ * @param openid 微信用户的openid
+ * @return 包含支付参数的JSONObject
+ * @throws Exception 可能抛出的异常
+ */
+ public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
+ String bodyAsString = jsapi(orderNum, total, description, openid);
+ JSONObject jsonObject = JSON.parseObject(bodyAsString);
+ String prepayId = jsonObject.getString("prepay_id");
+ if (prepayId != null) {
+ String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
+ String nonceStr = RandomStringUtils.randomNumeric(32);
+ ArrayList