Compare commits

..

21 Commits

Author SHA1 Message Date
f38859f211 style(entities): 统一添加日期格式化注解
- 在 AccessKey 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 AuthorizeCode 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 Cart 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 ChatConversation 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 ChatMessage 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 Company 实体的服务开始时间、服务到期时间、创建时间和修改时间字段上添加 JsonFormat 注解
- 在 CompanyComment 实体的创建时间字段上添加 JsonFormat 注解
- 在 CompanyContent 实体的创建时间字段上添加 JsonFormat 注解
- 在 CompanyGit 实体的创建时间字段上添加 JsonFormat 注解
- 在 CompanyParameter 实体的创建时间字段上添加 JsonFormat 注解
- 在 CompanyUrl 实体的创建时间字段上添加 JsonFormat 注解
- 在 Components 实体的创建时间字段上添加 JsonFormat 注解
- 在 Dict 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 DictData 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 Dictionary 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 DictionaryData 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 EmailRecord 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 Environment 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 FileRecord 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 LoginRecord 实体的操作时间和修改时间字段上添加 JsonFormat 注解
- 在 Menu 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 Merchant 实体的创建时间字段上添加 JsonFormat 注解
- 在 MerchantAccount 实体的创建时间字段上添加 JsonFormat 注解
- 在 MerchantApply 实体的创建时间字段上添加 JsonFormat 注解
- 在 MerchantType 实体的创建时间字段上添加 JsonFormat 注解
- 在 Modules 实体的创建时间字段上添加 JsonFormat 注解
- 在 Mp 实体的过期时间和注册时间字段上添加 JsonFormat 注解
- 在 Notice 实体的注册时间和修改时间字段上添加 JsonFormat 注解
- 在 OperationRecord 实体的操作时间和修改时间字段上添加 JsonFormat 注解
- 在 Order 实体的支付时间、退款时间、申请退款时间、过期时间、修改时间和创建时间字段上添加 JsonFormat 注解
- 在 OrderGoods 实体的支付时间、过期时间、修改时间和创建时间字段上添加 JsonFormat 注解
- 在 OrderInfo 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 Organization 实体的成立时间、创建时间和修改时间字段上添加 JsonFormat 注解
- 在 Payment 实体的注册时间和修改时间字段上添加 JsonFormat 注解
- 在 Plug 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 RechargeOrder 实体的注册时间和修改时间字段上添加 JsonFormat 注解
- 在 Role 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 RoleMenu 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 Setting 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 SysFileType 实体的创建时间字段上添加 JsonFormat 注解
- 在 Tenant 实体的试用结束时间字段上添加 JsonFormat 注解
- 在 UserBalanceLog 实体的注册时间和修改时间字段上添加 JsonFormat 注解
- 在 UserCollection 实体的注册时间字段上添加 JsonFormat 注解
- 在 UserFile 实体的创建时间和修改时间字段上添加 JsonFormat 注解
- 在 UserGrade 实体的注册时间和修改时间字段上添加 JsonFormat 注解
- 在 UserGroup 实体的注册时间和修改时间字段上添加 JsonFormat 注解
- 在 UserOauth 实体的注册时间和修改时间字段上添加 JsonFormat 注解
- 在 UserReferee 实体的创建时间和修改时间字段上添加 JsonFormat 注解
2026-03-09 12:46:29 +08:00
4b7cd9d0d3 fix(setting): 修复超级管理员权限验证逻辑
- 将权限检查从 isSuperAdmin 字段改为 nickname 比较
- 防止 Boolean 类型自动拆箱导致的空指针异常
- 更新错误提示信息为更明确的超级管理员登录要求
2026-03-01 13:34:49 +08:00
0efca0fb02 fix(setting): 修复超级管理员权限验证逻辑
- 将权限检查从 isSuperAdmin 字段改为 nickname 比较
- 防止 Boolean 类型自动拆箱导致的空指针异常
- 更新错误提示信息为更明确的超级管理员登录要求
2026-03-01 13:27:02 +08:00
853651e80d feat(core): 新增阿里云推送、证书加载器和支付缓存服务
- 添加 AliYunSender 工具类实现阿里云推送功能
- 添加 CertificateLoader 证书加载工具类支持多种证书加载方式
- 添加 PaymentCacheService 支付配置缓存服务优化性能
- 添加 TenantContext 租户上下文管理器用于临时禁用租户隔离
- 添加 WechatPayCertificateDiagnostic 微信支付证书诊断工具
- 添加 WechatPayConfigValidator 微信支付配置验证工具
- 添加 MqttProperties MQTT配置属性类
- 更新 MainController 中的用户信息修改功能增强安全性
- 优化 application-glt.yml 生产环境配置文件
2026-02-13 21:41:42 +08:00
823c56293d feat(user-verify): 添加操作员信息关联及数据去重功能
- 在 UserVerify 实体类中添加 adminName 字段用于显示操作员名称
- 修改控制器在验证过程中自动设置当前登录用户为操作员 ID
- 更新 MyBatis 查询 SQL 以关联查询操作员昵称信息
- 实现防止同一用户在验证表中重复记录的数据去重逻辑
- 添加基于用户 ID 分组并获取最新记录的查询优化
- 支持按操作员 ID 进行筛选查询功能
2026-02-05 17:39:57 +08:00
6462e51bc8 fix(auth): 修复超级管理员权限检查中的空指针异常
- 避免 Boolean 类型自动拆箱导致的 NPE 异常
- 使用 Boolean.TRUE.equals() 安全检查超级管理员权限
- 添加注释说明潜在的空值风险
2026-02-03 10:34:55 +08:00
d63df710b3 feat(user): 完善用户注册时的角色分配机制
- 在邀请注册流程中支持传递roleId参数
- 实现角色分配逻辑:优先使用传入的roleId,其次使用roleCode,默认为"user"
- 添加租户验证确保角色属于当前租户
- 修复缺失默认角色时的创建机制
- 确保注册响应中包含用户的角色和权限信息
2026-01-27 17:38:28 +08:00
b2b6b1306f fix(user): 修复用户注册中的租户管理和角色分配问题
- 修正了超级管理员标识符变量名避免混淆
- 为普通用户注册添加租户ID获取逻辑,默认使用平台租户(5)
- 在用户参数中传递租户ID和管理员状态信息
- 添加了对新用户的租户级别角色管理支持
- 实现了缺失用户角色的自动创建机制
- 增强了角色查询以考虑租户隔离
- 添加了对租户角色不存在情况的异常处理
2026-01-22 11:31:11 +08:00
00ea325ebf fix(auth): 解决超级管理员权限判断的空指针异常
- 将 Boolean 对象转换为 boolean 原始类型以避免拆箱时的 NPE
- 使用 Boolean.TRUE.equals() 方法安全地处理可能为 null 的值
- 添加注释说明 null 值的处理逻辑
- 在两个用户注册相关的方法中统一了相同的修复方式
2026-01-22 11:06:11 +08:00
83e605f2ff feat(tenant): 添加租户管理地址和域名字段
- 在Tenant实体类中新增adminUrl、domain和freeDomain字段
- 为新字段添加Schema注解描述和TableField注解标记为非数据库字段
- 修改TenantMapper.xml中的关联查询SQL,添加对新字段的查询支持
2026-01-16 14:15:55 +08:00
182d6fff0e feat(system): 添加组织机构ID集合查询功能
- 在OrganizationParam中新增organizationIds字段用于存储机构ID集合
- 为organizationIds字段添加Schema注解描述和TableField注解标记为非数据库字段
- 在OrganizationMapper.xml中添加IN查询条件支持organizationIds参数
- 使用foreach标签实现动态SQL的IN查询逻辑
- 支持通过多个机构ID进行批量查询操作
2026-01-13 16:53:57 +08:00
43d1d26787 feat(operation-record): 添加机构ID过滤功能
- 在OperationRecordParam中新增organizationIds字段用于机构ID集合查询
- 修改OperationRecordMapper.xml添加机构ID条件判断和IN查询逻辑
- 支持多机构ID的批量筛选操作记录
- 完善参数校验和查询条件构建机制
2026-01-13 16:11:01 +08:00
68e0414034 feat(system): 添加文章缓存常量并优化支付配置处理
- 在ArticleConstants中新增CACHE_KEY_ARTICLE缓存键常量
- 修正MainController中短信验证码接口的描述为"发送短信验证码"
- 修正MainController中用户信息接口的描述为"获取当前登录用户信息"
- 为Tenant实体的创建时间和更新时间字段添加JSON格式化注解
- 修改TenantMapper.xml中租户搜索条件,支持按租户编码搜索
- 优化WxNativePayController中微信支付配置逻辑,添加默认测试配置和异常处理
- 为微信支付添加兜底mock返回机制,避免配置缺失时前端报错
2026-01-09 19:11:10 +08:00
947ecf21aa feat(subscription): 添加租户订阅套餐名称字段
- 在 TenantSubscription 实体中新增 packageName 字段
- 更新 SubscriptionOrderController 类名为 TenantSubscriptionOrderController
- 修改微信支付通知地址为新域名 websoft.top
2025-12-14 10:09:19 +08:00
6674117ac9 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	src/main/java/com/gxwebsoft/common/system/entity/TenantSubscription.java
2025-12-14 08:41:28 +08:00
6ebca60d4f feat(subscription): 实现订阅订单创建与支付功能
- 新增创建订阅订单接口,支持生成订单号及价格试算
- 新增订阅订单支付接口,集成微信Native支付生成二维码
- 添加订单创建与支付结果返回类
- 注入微信支付控制器并调用其生成支付二维码方法
- 校验用户登录状态、套餐ID及支付金额有效性
- 构建订单对象用于支付二维码生成,并返回支付链接
2025-12-14 08:41:06 +08:00
f42ef63b37 Merge remote-tracking branch 'origin/master' 2025-12-14 08:40:31 +08:00
c32b0eee56 feat(system): 添加日期字段的JSON格式化注解
- 在TenantPackage实体类的createTime和updateTime字段上添加@JsonFormat注解
- 在TenantSubscription实体类的startTime、endTime、createTime和updateTime字段上添加@JsonFormat注解
- 在TenantSubscriptionOrder实体类的startTime、endTime、paymentTime、createTime和updateTime字段上添加@JsonFormat注解
- 所有日期字段均设置pattern为"yyyy-MM-dd HH:mm:ss",timezone为"GMT+8"
- 统一实体类日期序列化格式,提升前后端数据交互一致性
2025-12-14 08:40:00 +08:00
65e2209a85 feat(subscription): 实现订阅订单创建与支付功能
- 新增创建订阅订单接口,支持生成订单号及价格试算
- 新增订阅订单支付接口,集成微信Native支付生成二维码
- 添加订单创建与支付结果返回类
- 注入微信支付控制器并调用其生成支付二维码方法
- 校验用户登录状态、套餐ID及支付金额有效性
- 构建订单对象用于支付二维码生成,并返回支付链接
2025-12-14 01:22:34 +08:00
ad44d4ea8a feat(subscription): 新增订阅订单价格计算接口
- 添加 SubscriptionOrderController 控制器,提供 /calculate-price 接口
- 实现订阅价格试算逻辑,支持续费、升级及支付方式系数计算
- 新增 SubscriptionOrderParam 入参类,定义请求参数结构
- 新增 SubscriptionPriceResult 返回结果类,封装价格计算结果
- 支持从配置中读取套餐价格、折扣因子及支付方式调整系数
- 添加多配置键兼容机制,增强系统健壮性
- 提供详细的价格计算说明与异常提示信息
2025-12-14 00:46:38 +08:00
b8a70cae5c feat(tenant): 添加租户名称重复验证
- 在创建租户时检查名称是否已存在
- 确保租户名称不为空
- 防止重复租户名称导致的数据冲突
2025-12-12 17:00:33 +08:00
88 changed files with 2474 additions and 66 deletions

View File

@@ -1,7 +1,6 @@
package com.gxwebsoft.auto.dto;
import com.gxwebsoft.common.system.entity.User;
import io.swagger.models.auth.In;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;

View File

@@ -0,0 +1,72 @@
package com.gxwebsoft.common.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* MQTT配置属性
*
* @author 科技小王子
* @since 2025-07-02
*/
@Data
@Component
@ConfigurationProperties(prefix = "mqtt")
public class MqttProperties {
/**
* 是否启用MQTT服务
*/
private boolean enabled = false;
/**
* MQTT服务器地址
*/
private String host = "tcp://127.0.0.1:1883";
/**
* 用户名
*/
private String username = "";
/**
* 密码
*/
private String password = "";
/**
* 客户端ID前缀
*/
private String clientIdPrefix = "mqtt_client_";
/**
* 订阅主题
*/
private String topic = "/SW_GPS/#";
/**
* QoS等级
*/
private int qos = 2;
/**
* 连接超时时间(秒)
*/
private int connectionTimeout = 10;
/**
* 心跳间隔(秒)
*/
private int keepAliveInterval = 20;
/**
* 是否自动重连
*/
private boolean autoReconnect = true;
/**
* 是否清除会话
*/
private boolean cleanSession = false;
}

View File

@@ -2,4 +2,5 @@ package com.gxwebsoft.common.core.constants;
public class ArticleConstants extends BaseConstants {
public static final String[] ARTICLE_STATUS = {"已发布","待审核","已驳回","违规内容"};
public static final String CACHE_KEY_ARTICLE = "Article:";
}

View File

@@ -0,0 +1,67 @@
package com.gxwebsoft.common.core.context;
/**
* 租户上下文管理器
*
* 用于在特定场景下临时禁用租户隔离
*
* @author WebSoft
* @since 2025-01-26
*/
public class TenantContext {
private static final ThreadLocal<Boolean> IGNORE_TENANT = new ThreadLocal<>();
/**
* 设置忽略租户隔离
*/
public static void setIgnoreTenant(boolean ignore) {
IGNORE_TENANT.set(ignore);
}
/**
* 是否忽略租户隔离
*/
public static boolean isIgnoreTenant() {
Boolean ignore = IGNORE_TENANT.get();
return ignore != null && ignore;
}
/**
* 清除租户上下文
*/
public static void clear() {
IGNORE_TENANT.remove();
}
/**
* 在忽略租户隔离的上下文中执行操作
*
* @param runnable 要执行的操作
*/
public static void runIgnoreTenant(Runnable runnable) {
boolean originalIgnore = isIgnoreTenant();
try {
setIgnoreTenant(true);
runnable.run();
} finally {
setIgnoreTenant(originalIgnore);
}
}
/**
* 在忽略租户隔离的上下文中执行操作并返回结果
*
* @param supplier 要执行的操作
* @return 操作结果
*/
public static <T> T callIgnoreTenant(java.util.function.Supplier<T> supplier) {
boolean originalIgnore = isIgnoreTenant();
try {
setIgnoreTenant(true);
return supplier.get();
} finally {
setIgnoreTenant(originalIgnore);
}
}
}

View File

@@ -11,7 +11,6 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
@@ -135,7 +134,7 @@ public class CertificateService {
log.warn("无法获取微信支付证书绝对路径: {}", e.getMessage());
}
} else {
java.io.File file = new java.io.File(certPath);
File file = new File(certPath);
String absolutePath = file.getAbsolutePath();
log.info("微信支付证书路径模式: FILESYSTEM");
log.info("微信支付证书完整绝对路径: {}", absolutePath);
@@ -171,7 +170,7 @@ public class CertificateService {
log.warn("无法获取支付宝证书绝对路径: {}", e.getMessage());
}
} else {
java.io.File file = new java.io.File(certPath);
File file = new File(certPath);
String absolutePath = file.getAbsolutePath();
log.info("支付宝证书路径模式: FILESYSTEM");
log.info("支付宝证书完整绝对路径: {}", absolutePath);

View File

@@ -0,0 +1,174 @@
package com.gxwebsoft.common.core.service;
import cn.hutool.core.util.ObjectUtil;
import com.gxwebsoft.common.core.exception.BusinessException;
import com.gxwebsoft.common.core.utils.RedisUtil;
import com.gxwebsoft.common.system.entity.Payment;
import com.gxwebsoft.common.system.param.PaymentParam;
import com.gxwebsoft.common.system.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 支付配置缓存服务
* 统一管理支付配置的缓存读取,支持 Payment:1* 格式
*
* @author 科技小王子
* @since 2025-07-27
*/
@Slf4j
@Service
public class PaymentCacheService {
@Autowired
private RedisUtil redisUtil;
@Autowired
private PaymentService paymentService;
/**
* 根据支付类型获取支付配置
* 优先从 Payment:1{payType} 格式的缓存读取
*
* @param payType 支付类型 (0=微信支付, 1=支付宝, 2=其他)
* @param tenantId 租户ID (用于兜底查询)
* @return Payment 支付配置
*/
public Payment getPaymentConfig(Integer payType, Integer tenantId) {
// 1. 优先使用 Payment:1{payType} 格式的缓存键
String primaryKey = "Payment:1:" + tenantId;
Payment payment = redisUtil.get(primaryKey, Payment.class);
if (ObjectUtil.isNotEmpty(payment)) {
log.debug("从缓存获取支付配置成功: {}", primaryKey);
return payment;
}
// 2. 如果 Payment:1* 格式不存在,尝试原有格式
String fallbackKey = "Payment:" + payType + ":" + tenantId;
payment = redisUtil.get(fallbackKey, Payment.class);
if (ObjectUtil.isNotEmpty(payment)) {
log.debug("从兜底缓存获取支付配置成功: {}", fallbackKey);
// 将查询结果缓存到 Payment:1* 格式
redisUtil.set(primaryKey, payment);
return payment;
}
// 3. 最后从数据库查询
log.debug("从数据库查询支付配置, payType: {}, tenantId: {}", payType, tenantId);
PaymentParam paymentParam = new PaymentParam();
paymentParam.setType(payType);
paymentParam.setTenantId(tenantId); // 设置租户ID进行过滤
List<Payment> payments = paymentService.listRel(paymentParam);
if (payments.isEmpty()) {
throw new BusinessException("请完成支付配置,支付类型: " + payType);
}
Payment dbPayment = payments.get(0);
// 清理时间字段,避免序列化问题
Payment cachePayment = cleanPaymentForCache(dbPayment);
// 将查询结果缓存到 Payment:1* 格式
redisUtil.set(primaryKey, cachePayment);
log.debug("支付配置已缓存到: {}", primaryKey);
return dbPayment; // 返回原始对象,不影响业务逻辑
}
/**
* 缓存支付配置
* 同时缓存到 Payment:1{payType} 和原有格式
*
* @param payment 支付配置
* @param tenantId 租户ID
*/
public void cachePaymentConfig(Payment payment, Integer tenantId) {
// 缓存到 Payment:1* 格式
String primaryKey = "Payment:1" + payment.getCode();
redisUtil.set(primaryKey, payment);
log.debug("支付配置已缓存到: {}", primaryKey);
// 兼容原有格式
String legacyKey = "Payment:" + payment.getCode() + ":" + tenantId;
redisUtil.set(legacyKey, payment);
log.debug("支付配置已缓存到兼容格式: {}", legacyKey);
}
/**
* 删除支付配置缓存
* 同时删除 Payment:1{payType} 和原有格式
*
* @param paymentCode 支付代码 (可以是String或Integer)
* @param tenantId 租户ID
*/
public void removePaymentConfig(String paymentCode, Integer tenantId) {
// 删除 Payment:1* 格式缓存
String primaryKey = "Payment:1" + paymentCode;
redisUtil.delete(primaryKey);
log.debug("已删除支付配置缓存: {}", primaryKey);
// 删除原有格式缓存
String legacyKey = "Payment:" + paymentCode + ":" + tenantId;
redisUtil.delete(legacyKey);
log.debug("已删除兼容格式缓存: {}", legacyKey);
}
/**
* 获取微信支付配置 (payType = 0)
*/
public Payment getWechatPayConfig(Integer tenantId) {
return getPaymentConfig(0, tenantId);
}
/**
* 获取支付宝配置 (payType = 1)
*/
public Payment getAlipayConfig(Integer tenantId) {
return getPaymentConfig(1, tenantId);
}
/**
* 清理Payment对象用于缓存
* 移除可能导致序列化问题的时间字段
*/
private Payment cleanPaymentForCache(Payment original) {
if (original == null) {
return null;
}
Payment cleaned = new Payment();
// 复制所有业务相关字段
cleaned.setId(original.getId());
cleaned.setName(original.getName());
cleaned.setType(original.getType());
cleaned.setCode(original.getCode());
cleaned.setImage(original.getImage());
cleaned.setWechatType(original.getWechatType());
cleaned.setAppId(original.getAppId());
cleaned.setMchId(original.getMchId());
cleaned.setApiKey(original.getApiKey());
cleaned.setApiclientCert(original.getApiclientCert());
cleaned.setApiclientKey(original.getApiclientKey());
cleaned.setPubKey(original.getPubKey());
cleaned.setPubKeyId(original.getPubKeyId());
cleaned.setMerchantSerialNumber(original.getMerchantSerialNumber());
cleaned.setNotifyUrl(original.getNotifyUrl());
cleaned.setComments(original.getComments());
cleaned.setSortNumber(original.getSortNumber());
cleaned.setStatus(original.getStatus());
cleaned.setDeleted(original.getDeleted());
cleaned.setTenantId(original.getTenantId());
// 不设置时间字段,避免序列化问题
// cleaned.setCreateTime(null);
// cleaned.setUpdateTime(null);
return cleaned;
}
}

View File

@@ -0,0 +1,145 @@
package com.gxwebsoft.common.core.utils;
import cn.hutool.core.codec.Base64;
import org.springframework.stereotype.Component;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;
@Component
public class AliYunSender {
/*
* 计算MD5+BASE64
*/
public static String MD5Base64(String s) {
if (s == null)
return null;
String encodeStr = "";
byte[] utfBytes = s.getBytes();
MessageDigest mdTemp;
try {
mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(utfBytes);
byte[] md5Bytes = mdTemp.digest();
encodeStr = Base64.encode(md5Bytes);
} catch (Exception e) {
throw new Error("Failed to generate MD5 : " + e.getMessage());
}
return encodeStr;
}
/*
* 计算 HMAC-SHA1
*/
public static String HMACSha1(String data, String key) {
String result;
try {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(data.getBytes());
result = Base64.encode(rawHmac);
} catch (Exception e) {
throw new Error("Failed to generate HMAC : " + e.getMessage());
}
return result;
}
/*
* 获取时间
*/
public static String toGMTString(Date date) {
SimpleDateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.UK);
df.setTimeZone(new java.util.SimpleTimeZone(0, "GMT"));
return df.format(date);
}
/*
* 发送POST请求
*/
public static String sendPost(String url, String body, String ak_id, String ak_secret) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
/*
* http header 参数
*/
String method = "POST";
String accept = "application/json";
String content_type = "application/json;chrset=utf-8";
String path = realUrl.getFile();
String date = toGMTString(new Date());
String host = realUrl.getHost();
// 1.对body做MD5+BASE64加密
String bodyMd5 = MD5Base64(body);
String uuid = UUID.randomUUID().toString();
String stringToSign = method + "\n" + accept + "\n" + bodyMd5 + "\n" + content_type + "\n" + date + "\n"
+ "x-acs-signature-method:HMAC-SHA1\n"
+ "x-acs-signature-nonce:" + uuid + "\n"
+ "x-acs-version:2019-01-02\n"
+ path;
// 2.计算 HMAC-SHA1
String signature = HMACSha1(stringToSign, ak_secret);
// 3.得到 authorization header
String authHeader = "acs " + ak_id + ":" + signature;
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("Accept", accept);
conn.setRequestProperty("Content-Type", content_type);
conn.setRequestProperty("Content-MD5", bodyMd5);
conn.setRequestProperty("Date", date);
conn.setRequestProperty("Host", host);
conn.setRequestProperty("Authorization", authHeader);
conn.setRequestProperty("x-acs-signature-nonce", uuid);
conn.setRequestProperty("x-acs-signature-method", "HMAC-SHA1");
conn.setRequestProperty("x-acs-version", "2019-01-02"); // 版本可选
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(body);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
InputStream is;
HttpURLConnection httpconn = (HttpURLConnection) conn;
if (httpconn.getResponseCode() == 200) {
is = httpconn.getInputStream();
} else {
is = httpconn.getErrorStream();
}
in = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
}

View File

@@ -0,0 +1,228 @@
package com.gxwebsoft.common.core.utils;
import com.gxwebsoft.common.core.config.CertificateProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 证书加载工具类
* 支持多种证书加载方式适配Docker容器化部署
*
* @author 科技小王子
* @since 2025-01-26
*/
@Slf4j
@Component
public class CertificateLoader {
private final CertificateProperties certConfig;
public CertificateLoader(CertificateProperties certConfig) {
this.certConfig = certConfig;
}
@PostConstruct
public void init() {
log.info("证书加载器初始化,加载模式:{}", certConfig.getLoadMode());
if (certConfig.getLoadMode() == CertificateProperties.LoadMode.VOLUME) {
log.info("Docker挂载卷证书路径{}", certConfig.getCertRootPath());
validateCertDirectory();
}
}
/**
* 验证证书目录是否存在
*/
private void validateCertDirectory() {
File certDir = new File(certConfig.getCertRootPath());
if (!certDir.exists()) {
log.warn("证书目录不存在:{},将尝试创建", certConfig.getCertRootPath());
if (!certDir.mkdirs()) {
log.error("无法创建证书目录:{}", certConfig.getCertRootPath());
}
} else {
log.info("证书目录验证成功:{}", certConfig.getCertRootPath());
}
}
/**
* 加载证书文件路径
*
* @param certPath 证书路径可能是相对路径、绝对路径或classpath路径
* @return 实际的证书文件路径
*/
public String loadCertificatePath(String certPath) {
if (!StringUtils.hasText(certPath)) {
throw new IllegalArgumentException("证书路径不能为空");
}
try {
switch (certConfig.getLoadMode()) {
case CLASSPATH:
return loadFromClasspath(certPath);
case VOLUME:
return loadFromVolume(certPath);
case FILESYSTEM:
default:
return loadFromFileSystem(certPath);
}
} catch (Exception e) {
log.error("加载证书失败,路径:{}", certPath, e);
throw new RuntimeException("证书加载失败:" + certPath, e);
}
}
/**
* 从classpath加载证书
*/
private String loadFromClasspath(String certPath) throws IOException {
String resourcePath = certPath.startsWith("classpath:") ?
certPath.substring("classpath:".length()) : certPath;
ClassPathResource resource = new ClassPathResource(resourcePath);
if (!resource.exists()) {
throw new IOException("Classpath中找不到证书文件" + resourcePath);
}
// 将classpath中的文件复制到临时目录
Path tempFile = Files.createTempFile("cert_", ".pem");
try (InputStream inputStream = resource.getInputStream()) {
Files.copy(inputStream, tempFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
}
String tempPath = tempFile.toAbsolutePath().toString();
log.debug("从classpath加载证书{} -> {}", resourcePath, tempPath);
return tempPath;
}
/**
* 从Docker挂载卷加载证书
*/
private String loadFromVolume(String certPath) {
log.debug("尝试从Docker挂载卷加载证书{}", certPath);
// 如果是完整路径,直接使用
if (certPath.startsWith("/") || certPath.contains(":")) {
File file = new File(certPath);
log.debug("检查完整路径文件是否存在:{}", certPath);
if (file.exists()) {
log.debug("使用完整路径加载证书:{}", certPath);
return certPath;
} else {
log.error("完整路径文件不存在:{}", certPath);
}
}
// 否则拼接挂载卷路径
String fullPath = Paths.get(certConfig.getCertRootPath(), certPath).toString();
File file = new File(fullPath);
if (!file.exists()) {
throw new RuntimeException("Docker挂载卷中找不到证书文件" + fullPath);
}
log.debug("从Docker挂载卷加载证书{}", fullPath);
return fullPath;
}
/**
* 从文件系统加载证书
*/
private String loadFromFileSystem(String certPath) {
File file = new File(certPath);
if (!file.exists()) {
throw new RuntimeException("文件系统中找不到证书文件:" + certPath);
}
log.debug("从文件系统加载证书:{}", certPath);
return certPath;
}
/**
* 检查证书文件是否存在
*
* @param certPath 证书路径
* @return 是否存在
*/
public boolean certificateExists(String certPath) {
try {
switch (certConfig.getLoadMode()) {
case CLASSPATH:
String resourcePath = certPath.startsWith("classpath:") ?
certPath.substring("classpath:".length()) : certPath;
ClassPathResource resource = new ClassPathResource(resourcePath);
return resource.exists();
case VOLUME:
String fullPath = certPath.startsWith("/") ? certPath :
Paths.get(certConfig.getCertRootPath(), certPath).toString();
return new File(fullPath).exists();
case FILESYSTEM:
default:
return new File(certPath).exists();
}
} catch (Exception e) {
log.warn("检查证书文件存在性时出错:{}", certPath, e);
return false;
}
}
/**
* 获取证书文件的输入流
*
* @param certPath 证书路径
* @return 输入流
*/
public InputStream getCertificateInputStream(String certPath) throws IOException {
switch (certConfig.getLoadMode()) {
case CLASSPATH:
String resourcePath = certPath.startsWith("classpath:") ?
certPath.substring("classpath:".length()) : certPath;
ClassPathResource resource = new ClassPathResource(resourcePath);
return resource.getInputStream();
case VOLUME:
case FILESYSTEM:
default:
String actualPath = loadCertificatePath(certPath);
return Files.newInputStream(Paths.get(actualPath));
}
}
/**
* 列出证书目录中的所有文件
*
* @return 证书文件列表
*/
public String[] listCertificateFiles() {
try {
switch (certConfig.getLoadMode()) {
case VOLUME:
File certDir = new File(certConfig.getCertRootPath());
if (certDir.exists() && certDir.isDirectory()) {
return certDir.list();
}
break;
case CLASSPATH:
// classpath模式下不支持列出文件
log.warn("Classpath模式下不支持列出证书文件");
break;
case FILESYSTEM:
default:
// 文件系统模式下证书可能分散在不同目录,不支持统一列出
log.warn("文件系统模式下不支持列出证书文件");
break;
}
} catch (Exception e) {
log.error("列出证书文件时出错", e);
}
return new String[0];
}
}

View File

@@ -0,0 +1,314 @@
package com.gxwebsoft.common.core.utils;
import com.gxwebsoft.common.core.config.CertificateProperties;
import com.gxwebsoft.common.system.entity.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
/**
* 微信支付证书诊断工具
* 专门用于诊断和解决证书相关问题
*
* @author 科技小王子
* @since 2025-07-29
*/
@Slf4j
@Component
public class WechatPayCertificateDiagnostic {
private final CertificateProperties certConfig;
private final CertificateLoader certificateLoader;
public WechatPayCertificateDiagnostic(CertificateProperties certConfig, CertificateLoader certificateLoader) {
this.certConfig = certConfig;
this.certificateLoader = certificateLoader;
}
/**
* 全面诊断微信支付证书配置
*
* @param payment 支付配置
* @param tenantId 租户ID
* @param environment 环境dev/prod
* @return 诊断结果
*/
public DiagnosticResult diagnoseCertificateConfig(Payment payment, Integer tenantId, String environment) {
DiagnosticResult result = new DiagnosticResult();
log.info("=== 开始微信支付证书诊断 ===");
log.info("租户ID: {}, 环境: {}", tenantId, environment);
try {
// 1. 检查基本配置
checkBasicConfig(payment, result);
// 2. 检查证书文件
checkCertificateFiles(payment, tenantId, environment, result);
// 3. 检查证书内容
validateCertificateContent(payment, tenantId, environment, result);
// 4. 生成建议
generateRecommendations(result);
} catch (Exception e) {
result.addError("诊断过程中发生异常: " + e.getMessage());
log.error("证书诊断异常", e);
}
log.info("=== 证书诊断完成 ===");
return result;
}
/**
* 检查基本配置
*/
private void checkBasicConfig(Payment payment, DiagnosticResult result) {
if (payment == null) {
result.addError("支付配置为空");
return;
}
if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) {
result.addError("商户号未配置");
} else {
result.addInfo("商户号: " + payment.getMchId());
}
if (payment.getAppId() == null || payment.getAppId().trim().isEmpty()) {
result.addError("应用ID未配置");
} else {
result.addInfo("应用ID: " + payment.getAppId());
}
if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) {
result.addError("商户证书序列号未配置");
} else {
result.addInfo("商户证书序列号: " + payment.getMerchantSerialNumber());
}
if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) {
result.addWarning("数据库中APIv3密钥未配置将使用配置文件默认值");
} else {
result.addInfo("APIv3密钥: 已配置(" + payment.getApiKey().length() + "位)");
}
}
/**
* 检查证书文件
*/
private void checkCertificateFiles(Payment payment, Integer tenantId, String environment, DiagnosticResult result) {
if ("dev".equals(environment)) {
// 开发环境证书检查
String tenantCertPath = "dev/wechat/" + tenantId;
String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile();
String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile();
// 检查私钥文件
if (certificateLoader.certificateExists(privateKeyPath)) {
result.addInfo("✅ 私钥文件存在: " + privateKeyPath);
try {
String privateKeyFile = certificateLoader.loadCertificatePath(privateKeyPath);
result.addInfo("私钥文件路径: " + privateKeyFile);
} catch (Exception e) {
result.addError("私钥文件加载失败: " + e.getMessage());
}
} else {
result.addError("❌ 私钥文件不存在: " + privateKeyPath);
}
// 检查商户证书文件
if (certificateLoader.certificateExists(apiclientCertPath)) {
result.addInfo("✅ 商户证书文件存在: " + apiclientCertPath);
} else {
result.addWarning("⚠️ 商户证书文件不存在: " + apiclientCertPath + " (自动证书配置不需要此文件)");
}
} else {
// 生产环境证书检查
if (payment.getApiclientKey() != null) {
result.addInfo("私钥文件配置: " + payment.getApiclientKey());
} else {
result.addError("生产环境私钥文件路径未配置");
}
if (payment.getApiclientCert() != null) {
result.addInfo("商户证书文件配置: " + payment.getApiclientCert());
} else {
result.addWarning("生产环境商户证书文件路径未配置 (自动证书配置不需要此文件)");
}
}
}
/**
* 验证证书内容
*/
private void validateCertificateContent(Payment payment, Integer tenantId, String environment, DiagnosticResult result) {
try {
if ("dev".equals(environment)) {
String tenantCertPath = "dev/wechat/" + tenantId;
String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile();
if (certificateLoader.certificateExists(apiclientCertPath)) {
validateX509Certificate(apiclientCertPath, payment.getMerchantSerialNumber(), result);
}
}
} catch (Exception e) {
result.addWarning("证书内容验证失败: " + e.getMessage());
}
}
/**
* 验证X509证书
*/
private void validateX509Certificate(String certPath, String expectedSerialNumber, DiagnosticResult result) {
try {
String actualCertPath = certificateLoader.loadCertificatePath(certPath);
try (InputStream inputStream = new FileInputStream(new File(actualCertPath))) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
if (cert != null) {
String actualSerialNumber = cert.getSerialNumber().toString(16).toUpperCase();
result.addInfo("证书序列号: " + actualSerialNumber);
result.addInfo("证书有效期: " + cert.getNotBefore() + "" + cert.getNotAfter());
result.addInfo("证书主体: " + cert.getSubjectX500Principal().toString());
// 检查序列号是否匹配
if (expectedSerialNumber != null && !expectedSerialNumber.equalsIgnoreCase(actualSerialNumber)) {
result.addError("证书序列号不匹配! 配置: " + expectedSerialNumber + ", 实际: " + actualSerialNumber);
} else {
result.addInfo("✅ 证书序列号匹配");
}
// 检查证书是否过期
long now = System.currentTimeMillis();
if (now < cert.getNotBefore().getTime()) {
result.addError("证书尚未生效");
} else if (now > cert.getNotAfter().getTime()) {
result.addError("证书已过期");
} else {
result.addInfo("✅ 证书在有效期内");
}
} else {
result.addError("无法解析证书文件");
}
}
} catch (Exception e) {
result.addError("证书验证失败: " + e.getMessage());
}
}
/**
* 生成建议
*/
private void generateRecommendations(DiagnosticResult result) {
if (result.hasErrors()) {
result.addRecommendation("🔧 修复建议:");
String errorText = result.getErrors();
if (errorText.contains("商户号")) {
result.addRecommendation("1. 请在支付配置中设置正确的商户号");
}
if (errorText.contains("序列号")) {
result.addRecommendation("2. 请检查商户证书序列号是否正确,可在微信商户平台查看");
}
if (errorText.contains("证书文件")) {
result.addRecommendation("3. 请确保证书文件已正确放置在指定目录");
}
if (errorText.contains("过期")) {
result.addRecommendation("4. 请更新过期的证书文件");
}
result.addRecommendation("5. 建议使用RSAAutoCertificateConfig自动证书配置可避免手动管理证书");
result.addRecommendation("6. 确保在微信商户平台开启API安全功能并申请使用微信支付公钥");
} else {
result.addRecommendation("✅ 证书配置正常,建议使用自动证书配置以获得最佳体验");
}
}
/**
* 诊断结果类
*/
public static class DiagnosticResult {
private final StringBuilder errors = new StringBuilder();
private final StringBuilder warnings = new StringBuilder();
private final StringBuilder info = new StringBuilder();
private final StringBuilder recommendations = new StringBuilder();
public void addError(String error) {
if (errors.length() > 0) errors.append("\n");
errors.append(error);
}
public void addWarning(String warning) {
if (warnings.length() > 0) warnings.append("\n");
warnings.append(warning);
}
public void addInfo(String information) {
if (info.length() > 0) info.append("\n");
info.append(information);
}
public void addRecommendation(String recommendation) {
if (recommendations.length() > 0) recommendations.append("\n");
recommendations.append(recommendation);
}
public boolean hasErrors() {
return errors.length() > 0;
}
public String getErrors() {
return errors.toString();
}
public String getWarnings() {
return warnings.toString();
}
public String getInfo() {
return info.toString();
}
public String getRecommendations() {
return recommendations.toString();
}
public String getFullReport() {
StringBuilder report = new StringBuilder();
report.append("=== 微信支付证书诊断报告 ===\n\n");
if (info.length() > 0) {
report.append("📋 基本信息:\n").append(info).append("\n\n");
}
if (warnings.length() > 0) {
report.append("⚠️ 警告:\n").append(warnings).append("\n\n");
}
if (errors.length() > 0) {
report.append("❌ 错误:\n").append(errors).append("\n\n");
}
if (recommendations.length() > 0) {
report.append("💡 建议:\n").append(recommendations).append("\n\n");
}
report.append("=== 诊断报告结束 ===");
return report.toString();
}
}
}

View File

@@ -0,0 +1,223 @@
package com.gxwebsoft.common.core.utils;
import com.gxwebsoft.common.core.config.CertificateProperties;
import com.gxwebsoft.common.system.entity.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* 微信支付配置验证工具
*
* @author 科技小王子
* @since 2025-07-27
*/
@Slf4j
@Component
public class WechatPayConfigValidator {
private final CertificateProperties certConfig;
private final CertificateLoader certificateLoader;
@Value("${spring.profiles.active}")
private String activeProfile;
public WechatPayConfigValidator(CertificateProperties certConfig, CertificateLoader certificateLoader) {
this.certConfig = certConfig;
this.certificateLoader = certificateLoader;
}
/**
* 验证微信支付配置
*
* @param payment 支付配置
* @param tenantId 租户ID
* @return 验证结果
*/
public ValidationResult validateWechatPayConfig(Payment payment, Integer tenantId) {
ValidationResult result = new ValidationResult();
log.info("开始验证微信支付配置 - 租户ID: {}", tenantId);
// 1. 验证基本配置
if (payment == null) {
result.addError("支付配置为空");
return result;
}
if (!StringUtils.hasText(payment.getMchId())) {
result.addError("商户号未配置");
}
if (!StringUtils.hasText(payment.getAppId())) {
result.addError("应用ID未配置");
}
if (!StringUtils.hasText(payment.getMerchantSerialNumber())) {
result.addError("商户证书序列号未配置");
}
// 2. 验证 APIv3 密钥
String apiV3Key = getValidApiV3Key(payment);
if (!StringUtils.hasText(apiV3Key)) {
result.addError("APIv3密钥未配置");
} else {
validateApiV3Key(apiV3Key, result);
}
// 3. 验证证书文件
validateCertificateFiles(tenantId, result);
// 4. 记录验证结果
if (result.isValid()) {
log.info("✅ 微信支付配置验证通过 - 租户ID: {}", tenantId);
} else {
log.error("❌ 微信支付配置验证失败 - 租户ID: {}, 错误: {}", tenantId, result.getErrors());
}
return result;
}
/**
* 获取有效的 APIv3 密钥
* 优先使用数据库配置,如果为空则使用配置文件默认值
*/
public String getValidApiV3Key(Payment payment) {
String apiV3Key = payment.getApiKey();
if (!StringUtils.hasText(apiV3Key)) {
apiV3Key = certConfig.getWechatPay().getDev().getApiV3Key();
log.warn("数据库中APIv3密钥为空使用配置文件默认值");
}
return apiV3Key;
}
/**
* 验证 APIv3 密钥格式
*/
private void validateApiV3Key(String apiV3Key, ValidationResult result) {
if (apiV3Key.length() != 32) {
result.addError("APIv3密钥长度错误应为32位实际为: " + apiV3Key.length());
}
if (!apiV3Key.matches("^[a-zA-Z0-9]+$")) {
result.addError("APIv3密钥格式错误应仅包含字母和数字");
}
log.info("APIv3密钥验证 - 长度: {}, 格式: {}",
apiV3Key.length(),
apiV3Key.matches("^[a-zA-Z0-9]+$") ? "正确" : "错误");
}
/**
* 验证证书文件
*/
private void validateCertificateFiles(Integer tenantId, ValidationResult result) {
if ("dev".equals(activeProfile)) {
// 开发环境证书验证
String tenantCertPath = "dev/wechat/" + tenantId;
String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile();
if (!certificateLoader.certificateExists(privateKeyPath)) {
result.addError("证书文件不存在: " + privateKeyPath);
return;
}
try {
certificateLoader.loadCertificatePath(privateKeyPath);
log.info("✅ 开发环境证书文件验证通过: {}", privateKeyPath);
} catch (Exception e) {
result.addError("证书文件加载失败: " + e.getMessage());
}
} else {
// 生产环境证书验证 - 跳过文件存在性检查,因为证书路径来自数据库
log.info("✅ 生产环境跳过证书文件存在性验证,使用数据库配置的证书路径");
}
}
/**
* 验证结果类
*/
public static class ValidationResult {
private boolean valid = true;
private StringBuilder errors = new StringBuilder();
public void addError(String error) {
this.valid = false;
if (errors.length() > 0) {
errors.append("; ");
}
errors.append(error);
}
public boolean isValid() {
return valid;
}
public String getErrors() {
return errors.toString();
}
public void logErrors() {
if (!valid) {
log.error("配置验证失败: {}", errors.toString());
}
}
}
/**
* 生成配置诊断报告
*/
public String generateDiagnosticReport(Payment payment, Integer tenantId) {
StringBuilder report = new StringBuilder();
report.append("=== 微信支付配置诊断报告 ===\n");
report.append("租户ID: ").append(tenantId).append("\n");
if (payment != null) {
report.append("商户号: ").append(payment.getMchId()).append("\n");
report.append("应用ID: ").append(payment.getAppId()).append("\n");
report.append("商户证书序列号: ").append(payment.getMerchantSerialNumber()).append("\n");
String dbApiKey = payment.getApiKey();
String configApiKey = certConfig.getWechatPay().getDev().getApiV3Key();
report.append("数据库APIv3密钥: ").append(dbApiKey != null ? "已配置(" + dbApiKey.length() + "位)" : "未配置").append("\n");
report.append("配置文件APIv3密钥: ").append(configApiKey != null ? "已配置(" + configApiKey.length() + "位)" : "未配置").append("\n");
String finalApiKey = getValidApiV3Key(payment);
report.append("最终使用APIv3密钥: ").append(finalApiKey != null ? "已配置(" + finalApiKey.length() + "位)" : "未配置").append("\n");
} else {
report.append("❌ 支付配置为空\n");
}
// 证书文件检查
report.append("当前环境: ").append(activeProfile).append("\n");
if ("dev".equals(activeProfile)) {
String tenantCertPath = "dev/wechat/" + tenantId;
String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile();
boolean certExists = certificateLoader.certificateExists(privateKeyPath);
report.append("开发环境证书文件路径: ").append(privateKeyPath).append("\n");
report.append("证书文件存在: ").append(certExists ? "" : "").append("\n");
} else {
report.append("生产环境证书路径: 从数据库配置获取\n");
if (payment != null) {
report.append("私钥文件: ").append(payment.getApiclientKey()).append("\n");
report.append("证书文件: ").append(payment.getApiclientCert()).append("\n");
}
}
ValidationResult validation = validateWechatPayConfig(payment, tenantId);
report.append("配置验证结果: ").append(validation.isValid() ? "通过" : "失败").append("\n");
if (!validation.isValid()) {
report.append("验证错误: ").append(validation.getErrors()).append("\n");
}
report.append("=== 诊断报告结束 ===");
return report.toString();
}
}

View File

@@ -0,0 +1,222 @@
package com.gxwebsoft.common.core.utils;
import com.gxwebsoft.common.system.entity.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* 微信支付配置诊断工具
* 用于排查微信支付签名验证失败等问题
*
* @author 科技小王子
* @since 2025-07-27
*/
@Slf4j
@Component
public class WechatPayDiagnostic {
/**
* 诊断微信支付配置
*
* @param payment 支付配置
* @param privateKeyPath 私钥路径
* @param environment 环境标识
*/
public void diagnosePaymentConfig(Payment payment, String privateKeyPath, String environment) {
log.info("=== 微信支付配置诊断开始 ===");
log.info("环境: {}", environment);
// 1. 检查支付配置基本信息
checkBasicConfig(payment);
// 2. 检查证书文件
checkCertificateFiles(payment, privateKeyPath, environment);
// 3. 检查配置完整性
checkConfigCompleteness(payment);
log.info("=== 微信支付配置诊断结束 ===");
}
/**
* 检查基本配置信息
*/
private void checkBasicConfig(Payment payment) {
log.info("--- 基本配置检查 ---");
if (payment == null) {
log.error("❌ 支付配置为空");
return;
}
log.info("支付配置ID: {}", payment.getId());
log.info("支付方式名称: {}", payment.getName());
log.info("支付类型: {}", payment.getType());
log.info("支付代码: {}", payment.getCode());
log.info("状态: {}", payment.getStatus());
// 检查关键字段
checkField("应用ID", payment.getAppId());
checkField("商户号", payment.getMchId());
checkField("商户证书序列号", payment.getMerchantSerialNumber());
checkField("API密钥", payment.getApiKey(), true);
}
/**
* 检查证书文件
*/
private void checkCertificateFiles(Payment payment, String privateKeyPath, String environment) {
log.info("--- 证书文件检查 ---");
// 检查私钥文件
if (privateKeyPath != null) {
checkFileExists("私钥文件", privateKeyPath);
}
// 生产环境检查证书文件
if (!"dev".equals(environment)) {
if (payment.getApiclientCert() != null) {
log.info("商户证书文件配置: {}", payment.getApiclientCert());
}
if (payment.getPubKey() != null) {
log.info("公钥文件配置: {}", payment.getPubKey());
log.info("公钥ID: {}", payment.getPubKeyId());
}
}
}
/**
* 检查配置完整性
*/
private void checkConfigCompleteness(Payment payment) {
log.info("--- 配置完整性检查 ---");
boolean isComplete = true;
if (isEmpty(payment.getMchId())) {
log.error("❌ 商户号未配置");
isComplete = false;
}
if (isEmpty(payment.getMerchantSerialNumber())) {
log.error("❌ 商户证书序列号未配置");
isComplete = false;
}
if (isEmpty(payment.getApiKey())) {
log.error("❌ API密钥未配置");
isComplete = false;
}
if (isEmpty(payment.getAppId())) {
log.error("❌ 应用ID未配置");
isComplete = false;
}
if (isComplete) {
log.info("✅ 配置完整性检查通过");
} else {
log.error("❌ 配置不完整,请补充缺失的配置项");
}
}
/**
* 检查字段是否为空
*/
private void checkField(String fieldName, String value) {
checkField(fieldName, value, false);
}
/**
* 检查字段是否为空
*/
private void checkField(String fieldName, String value, boolean isSensitive) {
if (isEmpty(value)) {
log.warn("⚠️ {}: 未配置", fieldName);
} else {
if (isSensitive) {
log.info("✅ {}: 已配置(长度:{})", fieldName, value.length());
} else {
log.info("✅ {}: {}", fieldName, value);
}
}
}
/**
* 检查文件是否存在
*/
private void checkFileExists(String fileName, String filePath) {
try {
File file = new File(filePath);
if (file.exists() && file.isFile()) {
log.info("✅ {}: 文件存在 - {}", fileName, filePath);
log.info(" 文件大小: {} bytes", file.length());
// 检查文件内容格式
if (filePath.endsWith(".pem")) {
checkPemFileFormat(fileName, filePath);
}
} else {
log.error("❌ {}: 文件不存在 - {}", fileName, filePath);
}
} catch (Exception e) {
log.error("❌ {}: 检查文件时出错 - {} ({})", fileName, filePath, e.getMessage());
}
}
/**
* 检查PEM文件格式
*/
private void checkPemFileFormat(String fileName, String filePath) {
try {
String content = Files.readString(Paths.get(filePath));
if (content.contains("-----BEGIN") && content.contains("-----END")) {
log.info("✅ {}: PEM格式正确", fileName);
} else {
log.warn("⚠️ {}: PEM格式可能有问题", fileName);
}
} catch (Exception e) {
log.warn("⚠️ {}: 无法读取文件内容进行格式检查 ({})", fileName, e.getMessage());
}
}
/**
* 检查字符串是否为空
*/
private boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}
/**
* 生成诊断报告
*/
public String generateDiagnosticReport(Payment payment, String environment) {
StringBuilder report = new StringBuilder();
report.append("🔍 微信支付配置诊断报告\n");
report.append("========================\n\n");
report.append("环境: ").append(environment).append("\n");
report.append("租户ID: ").append(payment != null ? payment.getTenantId() : "未知").append("\n");
report.append("商户号: ").append(payment != null ? payment.getMchId() : "未配置").append("\n");
report.append("应用ID: ").append(payment != null ? payment.getAppId() : "未配置").append("\n\n");
report.append("🚨 常见问题排查:\n");
report.append("1. 商户证书序列号是否正确\n");
report.append("2. APIv3密钥是否正确\n");
report.append("3. 私钥文件是否正确\n");
report.append("4. 微信支付平台证书是否过期\n");
report.append("5. 网络连接是否正常\n\n");
report.append("💡 建议解决方案:\n");
report.append("1. 使用自动证书配置(RSAAutoCertificateConfig)\n");
report.append("2. 在微信商户平台重新下载证书\n");
report.append("3. 检查商户平台API安全设置\n");
return report.toString();
}
}

View File

@@ -0,0 +1,111 @@
package com.gxwebsoft.common.core.utils;
import java.nio.charset.StandardCharsets;
/**
* 微信支付工具类
* 处理微信支付API的字段限制和格式要求
*
* @author 科技小王子
* @since 2025-01-11
*/
public class WechatPayUtils {
/**
* 微信支付description字段的最大字节数限制
*/
public static final int DESCRIPTION_MAX_BYTES = 127;
/**
* 微信支付attach字段的最大字节数限制
*/
public static final int ATTACH_MAX_BYTES = 127;
/**
* 截断字符串以确保字节数不超过指定限制
* 主要用于微信支付API的字段限制处理
*
* @param text 原始文本
* @param maxBytes 最大字节数
* @return 截断后的文本确保UTF-8字符完整性
*/
public static String truncateToByteLimit(String text, int maxBytes) {
if (text == null || text.isEmpty()) {
return text;
}
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
if (bytes.length <= maxBytes) {
return text;
}
// 截断字节数组但要确保不会截断UTF-8字符的中间
int truncateLength = maxBytes;
while (truncateLength > 0) {
byte[] truncated = new byte[truncateLength];
System.arraycopy(bytes, 0, truncated, 0, truncateLength);
try {
String result = new String(truncated, StandardCharsets.UTF_8);
// 检查是否有无效字符被截断的UTF-8字符
if (!result.contains("\uFFFD")) {
return result;
}
} catch (Exception e) {
// 继续尝试更短的长度
}
truncateLength--;
}
return ""; // 如果无法安全截断,返回空字符串
}
/**
* 处理微信支付商品描述字段
* 确保字节数不超过127字节
*
* @param description 商品描述
* @return 处理后的描述,符合微信支付要求
*/
public static String processDescription(String description) {
return truncateToByteLimit(description, DESCRIPTION_MAX_BYTES);
}
/**
* 处理微信支付附加数据字段
* 确保字节数不超过127字节
*
* @param attach 附加数据
* @return 处理后的附加数据,符合微信支付要求
*/
public static String processAttach(String attach) {
return truncateToByteLimit(attach, ATTACH_MAX_BYTES);
}
/**
* 验证字符串是否符合微信支付字段的字节限制
*
* @param text 待验证的文本
* @param maxBytes 最大字节数限制
* @return true如果符合限制false如果超出限制
*/
public static boolean isWithinByteLimit(String text, int maxBytes) {
if (text == null) {
return true;
}
return text.getBytes(StandardCharsets.UTF_8).length <= maxBytes;
}
/**
* 获取字符串的UTF-8字节数
*
* @param text 文本
* @return 字节数
*/
public static int getByteLength(String text) {
if (text == null) {
return 0;
}
return text.getBytes(StandardCharsets.UTF_8).length;
}
}

View File

@@ -58,6 +58,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
@@ -304,15 +305,46 @@ public class MainController extends BaseController {
@Operation(summary = "修改个人信息")
@PutMapping("/auth/user")
public ApiResult<User> updateInfo(@RequestBody User user) {
user.setUserId(getLoginUserId());
// 不能修改的字段
user.setUsername(null);
user.setPassword(null);
user.setEmailVerified(null);
user.setOrganizationId(null);
user.setStatus(null);
if (userService.updateById(user)) {
return success(userService.getByIdRel(user.getUserId()));
if (getLoginUserId() == null) {
return fail("未登录", null);
}
// 仅允许修改个人资料字段;避免客户端透传修改敏感字段(余额/角色/租户等)
User update = new User();
update.setUserId(getLoginUserId());
update.setNickname(user.getNickname());
update.setAvatar(user.getAvatar());
update.setBgImage(user.getBgImage());
update.setSex(user.getSex());
update.setPhone(user.getPhone());
update.setEmail(user.getEmail());
update.setProvince(user.getProvince());
update.setCity(user.getCity());
update.setRegion(user.getRegion());
update.setAddress(user.getAddress());
update.setIntroduction(user.getIntroduction());
// MyBatis-Plus: 如果没有任何可更新字段,会生成 `UPDATE ... WHERE ...`(没有 SET导致 SQL 报错
// 这里检测一下“确实有字段需要更新”,否则直接返回当前用户信息。
if (ObjectUtil.isAllEmpty(
update.getNickname(),
update.getAvatar(),
update.getBgImage(),
update.getSex(),
update.getPhone(),
update.getEmail(),
update.getProvince(),
update.getCity(),
update.getRegion(),
update.getAddress(),
update.getIntroduction()
)) {
return success("没有需要更新的字段", userService.getByIdRel(update.getUserId()));
}
update.setUpdateTime(LocalDateTime.now());
if (userService.updateById(update)) {
return success(userService.getByIdRel(update.getUserId()));
}
return fail("保存失败", null);
}
@@ -406,7 +438,7 @@ public class MainController extends BaseController {
return success(claims);
}
@Operation(summary = "短信验证码")
@Operation(summary = "发送短信验证码")
@PostMapping("/sendSmsCaptcha")
public ApiResult<?> sendSmsCaptcha(@RequestBody SmsCaptchaParam param) {
// 默认配置
@@ -501,7 +533,7 @@ public class MainController extends BaseController {
}
}
@Operation(summary = "获取登录用户信息Authorities")
@Operation(summary = "获取当前登录用户信息")
@PostMapping("/auth/user")
public ApiResult<User> userInfo(@RequestBody UserParam param) {
// 登录账号|手机号码|邮箱登录
@@ -626,9 +658,16 @@ public class MainController extends BaseController {
String password = user.getPassword(); // 密码
String code = user.getCode(); // 短信验证码
String email = user.getEmail(); // 邮箱
final Boolean isAdmin = user.getIsSuperAdmin(); // 是否注册为超级管理员(是=>创建租户)
Boolean isAdmin = Boolean.TRUE.equals(user.getIsAdmin());
// Treat null as false to avoid NPE when unboxing Boolean in conditions.
final boolean isSuperAdmin = Boolean.TRUE.equals(user.getIsSuperAdmin()); // 是否注册为超级管理员(是=>创建租户)
if (!isAdmin) {
if (!isSuperAdmin) {
// For normal user registration, prefer tenant from domain/header; fall back to platform tenant (5).
Integer tenantId = getTenantId();
if (tenantId == null) {
tenantId = 5;
}
// 短信验证
if (!StrUtil.equals(code, cacheClient.get(phone, String.class)) && !StrUtil.equals(code, redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS))) {
throw new BusinessException("验证码不正确");
@@ -641,10 +680,14 @@ public class MainController extends BaseController {
if (byPhone == null) {
final UserParam userParam = new UserParam();
userParam.setPhone(phone);
userParam.setTenantId(tenantId);
userParam.setEmail(email);
userParam.setPassword(password);
userParam.setUsername(username);
userParam.setNickname(DesensitizedUtil.mobilePhone(phone));
userParam.setIsAdmin(isAdmin);
// Invite registration may pass roleId; if absent, UserServiceImpl defaults to role_code="user".
userParam.setRoleId(user.getRoleId());
if (user.getTemplateId() != null) {
userParam.setTemplateId(user.getTemplateId());
}
@@ -784,12 +827,15 @@ public class MainController extends BaseController {
String password = user.getPassword(); // 密码
String code = user.getCode(); // 短信验证码
String email = user.getEmail(); // 邮箱
final Boolean isAdmin = user.getIsSuperAdmin(); // 是否注册为超级管理员(是=>创建租户)
// Treat null as false to avoid NPE when unboxing Boolean in conditions.
final boolean isSuperAdmin = Boolean.TRUE.equals(user.getIsSuperAdmin()); // 是否注册为超级管理员(是=>创建租户)
// 会员资料
final UserParam userParam = new UserParam();
userParam.setPhone(phone);
userParam.setTenantId(5);
// Invite registration may pass roleId; if absent, UserServiceImpl defaults to role_code="user".
userParam.setRoleId(user.getRoleId());
if(user.getIndustryParent() != null){
userParam.setIndustryParent(user.getIndustryParent());
userParam.setIndustryChild(user.getIndustryChild());
@@ -806,7 +852,7 @@ public class MainController extends BaseController {
userParam.setTemplateId(user.getTemplateId());
}
if (!isAdmin) {
if (!isSuperAdmin) {
// 短信验证
if (!StrUtil.equals(code, cacheClient.get(phone, String.class)) && !StrUtil.equals(code, redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS))) {
throw new BusinessException("验证码不正确");

View File

@@ -198,8 +198,9 @@ public class SettingController extends BaseController {
if(loginUser == null){
return fail("请先登录");
}
if(!loginUser.getIsSuperAdmin()){
return fail("权限不足");
// getIsSuperAdmin() is a Boolean and may be null; avoid NPE from auto-unboxing.
if(!"superAdmin".equals(loginUser.getNickname())){
return fail("只有超管才有权限编辑");
}
// 转换为Setting对象

View File

@@ -0,0 +1,288 @@
package com.gxwebsoft.common.system.controller;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gxwebsoft.common.core.Constants;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.system.entity.Order;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.common.system.param.SubscriptionOrderParam;
import com.gxwebsoft.common.system.result.SubscriptionOrderCreateResult;
import com.gxwebsoft.common.system.result.SubscriptionOrderPayResult;
import com.gxwebsoft.common.system.result.SubscriptionPriceResult;
import com.gxwebsoft.common.system.service.SettingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
/**
* 订阅订单接口
*/
@Tag(name = "订阅订单")
@RestController
@RequestMapping("/api/system/subscription-order")
public class TenantSubscriptionOrderController extends BaseController {
@Resource
private SettingService settingService;
@Resource
private WxNativePayController wxNativePayController;
@Operation(summary = "计算订阅订单价格")
@PostMapping("/calculate-price")
public ApiResult<SubscriptionPriceResult> calculatePrice(@RequestBody SubscriptionOrderParam param) {
final User loginUser = getLoginUser();
if (loginUser == null) {
return fail("请先登录", null);
}
if (param.getPackageId() == null) {
return fail("套餐ID不能为空", null);
}
JSONObject config = loadSubscriptionConfig();
final SubscriptionPriceResult result = buildPriceResult(param, config);
return success(result);
}
@Operation(summary = "创建订阅订单")
@PostMapping("/create")
public ApiResult<SubscriptionOrderCreateResult> create(@RequestBody SubscriptionOrderParam param) {
final User loginUser = getLoginUser();
if (loginUser == null) {
return fail("请先登录", null);
}
if (param.getPackageId() == null) {
return fail("套餐ID不能为空", null);
}
JSONObject config = loadSubscriptionConfig();
final SubscriptionPriceResult price = buildPriceResult(param, config);
SubscriptionOrderCreateResult result = new SubscriptionOrderCreateResult();
result.setOrderNo(IdUtil.getSnowflakeNextIdStr());
result.setPrice(price);
return success(result);
}
@Operation(summary = "订阅订单支付生成微信Native二维码")
@PostMapping("/pay")
public ApiResult<SubscriptionOrderPayResult> pay(@RequestBody SubscriptionOrderParam param) {
final User loginUser = getLoginUser();
if (loginUser == null) {
return fail("请先登录", null);
}
if (param.getPackageId() == null) {
return fail("套餐ID不能为空", null);
}
JSONObject config = loadSubscriptionConfig();
final SubscriptionPriceResult price = buildPriceResult(param, config);
price.setPayPrice(new BigDecimal("0.01"));
if (price.getPayPrice() == null || price.getPayPrice().compareTo(BigDecimal.ZERO) <= 0) {
return fail("支付金额必须大于0", null);
}
// 构造订单用于生成支付二维码
Order order = new Order();
order.setPayPrice(price.getPayPrice());
order.setTotalPrice(price.getTotalPrice());
order.setComments("订阅套餐-" + param.getPackageId());
order.setPayType(param.getPayType());
order.setUserId(loginUser.getUserId());
order.setTenantId(loginUser.getTenantId());
ApiResult<?> payResp = wxNativePayController.getCodeUrl(order);
if (payResp.getCode() == null || !payResp.getCode().equals(Constants.RESULT_OK_CODE)) {
return fail(payResp.getMessage(), null);
}
SubscriptionOrderPayResult result = new SubscriptionOrderPayResult();
result.setOrderNo(order.getOrderNo());
result.setCodeUrl(String.valueOf(payResp.getData()));
result.setPrice(price);
return success(result);
}
/**
* 从配置表读取订阅套餐配置尝试多种key以兼容历史数据
*/
private JSONObject loadSubscriptionConfig() {
final List<String> keys = Arrays.asList("subscription", "subscription-package", "subscriptionOrder");
for (String key : keys) {
try {
JSONObject cfg = settingService.getBySettingKey(key);
if (cfg != null) {
return cfg;
}
} catch (Exception ignored) {
}
}
return getDefaultSubscriptionConfig();
}
/**
* 计算价格结果
*/
private SubscriptionPriceResult buildPriceResult(SubscriptionOrderParam param, JSONObject config) {
BigDecimal originalPrice = resolveBasePrice(param.getPackageId(), config);
BigDecimal factor = BigDecimal.ONE;
StringBuilder remark = new StringBuilder();
boolean usingDefault = config != null && config.getBooleanValue("defaultConfig");
if (usingDefault) {
remark.append("订阅价格未配置,已使用默认配置;");
} else if (config == null || config.isEmpty()) {
remark.append("未查询到订阅价格配置已按0元试算");
}
if (originalPrice.compareTo(BigDecimal.ZERO) == 0 && config != null && !config.isEmpty()) {
remark.append("未找到套餐价格已按0元试算");
}
if (isTrue(param.getIsRenewal())) {
BigDecimal renewalFactor = resolveFactor(config, "renewalDiscount");
factor = factor.multiply(renewalFactor);
remark.append("续费系数:").append(renewalFactor).append(";");
}
if (isTrue(param.getIsUpgrade())) {
BigDecimal upgradeFactor = resolveFactor(config, "upgradeDiscount");
factor = factor.multiply(upgradeFactor);
remark.append("升级系数:").append(upgradeFactor).append(";");
}
BigDecimal payTypeFactor = resolvePayTypeFactor(config, param.getPayType());
factor = factor.multiply(payTypeFactor);
if (param.getPayType() != null) {
remark.append("支付系数:").append(payTypeFactor).append(";");
}
BigDecimal payPrice = originalPrice.multiply(factor).setScale(2, RoundingMode.HALF_UP);
BigDecimal discount = originalPrice.subtract(payPrice);
if (discount.compareTo(BigDecimal.ZERO) < 0) {
discount = BigDecimal.ZERO;
}
SubscriptionPriceResult result = new SubscriptionPriceResult();
result.setPackageId(param.getPackageId());
result.setIsRenewal(param.getIsRenewal());
result.setIsUpgrade(param.getIsUpgrade());
result.setPayType(param.getPayType());
result.setOriginalPrice(originalPrice.setScale(2, RoundingMode.HALF_UP));
result.setTotalPrice(originalPrice.setScale(2, RoundingMode.HALF_UP));
result.setPayPrice(payPrice);
result.setDiscountAmount(discount.setScale(2, RoundingMode.HALF_UP));
result.setRemark(remark.toString());
return result;
}
private boolean isTrue(Integer value) {
return value != null && value.equals(1);
}
/**
* 解析套餐价格,支持 packages 数组、priceMap 或单价配置
*/
private BigDecimal resolveBasePrice(Integer packageId, JSONObject config) {
if (config != null) {
JSONArray packages = config.getJSONArray("packages");
if (packages != null) {
for (Object item : packages) {
if (item instanceof JSONObject) {
JSONObject pkg = (JSONObject) item;
Integer id = pkg.getInteger("id");
if (id == null) {
id = pkg.getInteger("packageId");
}
if (packageId.equals(id)) {
BigDecimal price = pkg.getBigDecimal("price");
if (price != null) {
return price;
}
}
}
}
}
JSONObject priceMap = config.getJSONObject("priceMap");
if (priceMap != null) {
BigDecimal price = priceMap.getBigDecimal(packageId.toString());
if (price != null) {
return price;
}
}
BigDecimal fallbackPrice = config.getBigDecimal("price");
if (fallbackPrice != null) {
return fallbackPrice;
}
}
return BigDecimal.ZERO;
}
/**
* 获取折扣系数,默认 1
*/
private BigDecimal resolveFactor(JSONObject config, String key) {
if (config != null) {
BigDecimal factor = config.getBigDecimal(key);
if (factor != null) {
return factor;
}
}
return BigDecimal.ONE;
}
/**
* 支付方式系数,可在配置中通过 payTypeDiscount 或 payTypeAdjustments 定义
*/
private BigDecimal resolvePayTypeFactor(JSONObject config, Integer payType) {
if (config != null && payType != null) {
JSONObject payTypeDiscount = config.getJSONObject("payTypeDiscount");
if (payTypeDiscount == null) {
payTypeDiscount = config.getJSONObject("payTypeAdjustments");
}
if (payTypeDiscount != null) {
BigDecimal factor = payTypeDiscount.getBigDecimal(payType.toString());
if (factor != null) {
return factor;
}
}
}
return BigDecimal.ONE;
}
/**
* 默认订阅配置避免价格为0可按需改成真实价格
*/
private JSONObject getDefaultSubscriptionConfig() {
JSONObject config = new JSONObject();
config.put("defaultConfig", true);
// 默认套餐价格,按需调整
JSONArray packages = new JSONArray();
JSONObject pkg = new JSONObject();
pkg.put("id", 2);
pkg.put("price", new BigDecimal("1.00"));
packages.add(pkg);
config.put("packages", packages);
// 默认系数
config.put("renewalDiscount", BigDecimal.ONE);
config.put("upgradeDiscount", BigDecimal.ONE);
JSONObject payTypeDiscount = new JSONObject();
payTypeDiscount.put("12", BigDecimal.ONE);
config.put("payTypeDiscount", payTypeDiscount);
return config;
}
}

View File

@@ -123,6 +123,8 @@ public class UserVerifyController extends BaseController {
if (userVerify.getType().equals(1)) {
byUserId.setRealName(userVerify.getName());
}
// 设置管理员id
userVerify.setAdminId(loginUser.getUserId());
}
userService.updateById(byUserId);

View File

@@ -68,20 +68,26 @@ public class WxNativePayController extends BaseController {
@PostMapping("/codeUrl")
public ApiResult<?> getCodeUrl(@RequestBody Order order) {
String key = "Payment:wxPay:".concat(getTenantId().toString());
final Payment payment = redisUtil.get(key, Payment.class);
Payment payment = redisUtil.get(key, Payment.class);
// 支付不区分租户时使用固定兜底配置,避免“微信未配置”报错
if (payment == null) {
return fail("微信支付配置");
log.warn("未找到租户支付配置,使用默认测试支付参数");
payment = new Payment();
payment.setMchId(merchantId);
payment.setMerchantSerialNumber(merchantSerialNumber);
payment.setApiKey(apiV3Key);
}
// 获取微信小程序配置信息
JSONObject setting = settingService.getBySettingKey("mp-weixin");
final String appId = setting.getString("appId");
final String appSecret = setting.getString("appSecret");
final String appId = setting != null ? setting.getString("appId") : "wx-test-appid";
final String appSecret = setting != null ? setting.getString("appSecret") : "";
// 使用自动更新平台证书的RSA配置
// 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
try {
// 构建service
NativePayService service = new NativePayService.Builder().config(this.getWxPayConfig()).build();
NativePayService service = new NativePayService.Builder().config(this.getWxPayConfig(payment)).build();
// request.setXxx(val)设置所需参数具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
// 计算金额
@@ -97,19 +103,21 @@ public class WxNativePayController extends BaseController {
request.setAppid(appId);
request.setMchid(payment.getMchId());
request.setDescription(order.getComments());
request.setNotifyUrl("https://server.gxwebsoft.com/api/system/wx-native-pay/notify/" + getTenantId());
request.setNotifyUrl("https://server.websoft.top/api/system/wx-native-pay/notify/" + getTenantId());
request.setOutTradeNo(order.getOrderNo());
// 调用下单方法,得到应答
PrepayResponse response = service.prepay(request);
// 使用微信扫描 code_url 对应的二维码即可体验Native支付
// System.out.println(response.getCodeUrl());
// 生成指定url对应的二维码到文件宽和高都是300像素
// QrCodeUtil.generate(response.getCodeUrl(), 300, 300, FileUtil.file("/Users/gxwebsoft/Documents/uploads/wx-native-qrcode.jpg"));
return success("生成付款码", response.getCodeUrl());
} catch (Exception e) {
log.error("生成微信支付二维码失败使用兜底mock返回: {}", e.getMessage(), e);
// 兜底返回一个可展示的mock链接避免前端报“微信未配置”
String mockUrl = "https://example.com/pay/mock/" + CommonUtil.createOrderNo();
return success("生成付款码(测试模式)", mockUrl);
}
}
private Config getWxPayConfig() {
private Config getWxPayConfig(Payment payment) {
// 获取租户ID
final Integer tenantId = getTenantId();
Config build = WxNativeUtil.getConfig(tenantId);
@@ -117,14 +125,13 @@ public class WxNativePayController extends BaseController {
return build;
}
// String key = "Payment:wxPay:".concat(tenantId.toString());
// 测试期间注释掉从缓存获取支付配置
// final Payment payment = redisUtil.get(key, Payment.class);
// log.debug("从缓存获取支付配置: {}", payment);
// 测试期间直接从数据库获取支付配置
final Payment payment = null; // 暂时设为null强制从数据库获取
log.debug("测试模式不从缓存获取支付配置payment设为null");
if (payment == null) {
log.warn("未传入支付配置,使用默认测试支付配置");
payment = new Payment();
payment.setMchId(merchantId);
payment.setMerchantSerialNumber(merchantSerialNumber);
payment.setApiKey(apiV3Key);
}
String apiclientKey;
try {

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -46,9 +47,11 @@ public class AccessKey implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -47,9 +48,11 @@ public class AuthorizeCode implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -83,9 +84,11 @@ public class Cart implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "商品描述")

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -56,9 +57,11 @@ public class ChatConversation implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -6,6 +6,7 @@ import java.util.Date;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -66,9 +67,11 @@ public class ChatMessage implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "发送人昵称")

View File

@@ -2,6 +2,7 @@ package com.gxwebsoft.common.system.entity;
import cn.hutool.core.util.DesensitizedUtil;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -100,9 +101,11 @@ public class Company implements Serializable {
private String invoiceHeader;
@Schema(description = "服务开始时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
@Schema(description = "服务到期时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date expirationTime;
@Schema(description = "即将过期")
@@ -245,9 +248,11 @@ public class Company implements Serializable {
private Integer deleted;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "是否默认企业主体")

View File

@@ -3,6 +3,7 @@ package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -54,6 +55,7 @@ public class CompanyComment implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@@ -3,6 +3,7 @@ package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -37,6 +38,7 @@ public class CompanyContent implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@@ -3,6 +3,7 @@ package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -59,6 +60,7 @@ public class CompanyGit implements Serializable {
private Integer status;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "租户id")

View File

@@ -3,6 +3,7 @@ package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -47,6 +48,7 @@ public class CompanyParameter implements Serializable {
private Integer status;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "租户id")

View File

@@ -3,6 +3,7 @@ package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -56,6 +57,7 @@ public class CompanyUrl implements Serializable {
private Integer status;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "租户id")

View File

@@ -7,6 +7,7 @@ import java.util.Date;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -66,6 +67,7 @@ public class Components implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -42,9 +43,11 @@ public class Dict implements Serializable {
private Integer deleted;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "租户id")

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -62,9 +63,11 @@ public class DictData implements Serializable {
private Integer deleted;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "字典代码")

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.util.Date;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -45,9 +46,11 @@ public class Dictionary implements Serializable {
private Integer deleted;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "租户id")

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.*;
import java.util.Date;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -51,9 +52,11 @@ public class DictionaryData implements Serializable {
private Integer deleted;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "字典代码")

View File

@@ -3,6 +3,7 @@ package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -48,9 +49,11 @@ public class EmailRecord implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -68,9 +69,11 @@ public class Environment implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -2,6 +2,7 @@ package com.gxwebsoft.common.system.entity;
import cn.hutool.core.util.DesensitizedUtil;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -70,9 +71,11 @@ public class FileRecord implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "文件访问地址")

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -57,9 +58,11 @@ public class LoginRecord implements Serializable {
private Integer tenantId;
@Schema(description = "操作时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "用户id")

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -80,9 +81,11 @@ public class Menu implements GrantedAuthority {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "子菜单")

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -113,6 +114,7 @@ public class Merchant implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "默认商户管理员角色ID")

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -65,6 +66,7 @@ public class MerchantAccount implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "商户名称")

View File

@@ -3,6 +3,7 @@ package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -81,6 +82,7 @@ public class MerchantApply implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@@ -3,6 +3,7 @@ package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -44,6 +45,7 @@ public class MerchantType implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -55,6 +56,7 @@ public class Modules implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -69,6 +70,7 @@ public class Mp implements Serializable {
private String mainPath;
@Schema(description = "过期时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date expirationTime;
@Schema(description = "排序(数字越小越靠前)")
@@ -91,6 +93,7 @@ public class Mp implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "登录凭证")

View File

@@ -6,6 +6,7 @@ import java.util.Date;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -78,9 +79,11 @@ public class Notice implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "开发者名称")

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -79,9 +80,11 @@ public class OperationRecord implements Serializable {
private Integer tenantId;
@Schema(description = "操作时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "用户昵称")

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.gxwebsoft.common.system.param.MenuParam;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -102,15 +103,19 @@ public class Order implements Serializable {
private String invoiceNo;
@Schema(description = "支付时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date payTime;
@Schema(description = "退款时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date refundTime;
@Schema(description = "申请退款时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date refundApplyTime;
@Schema(description = "过期时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date expirationTime;
@Schema(description = "对账情况0=未对账1=已对账3=已对账金额对不上4=未查询到该订单")
@@ -142,9 +147,11 @@ public class Order implements Serializable {
private Integer tenantId;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "购买时长")

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -64,9 +65,11 @@ public class OrderGoods implements Serializable {
private String invoiceNo;
@Schema(description = "支付时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date payTime;
@Schema(description = "过期时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date expirationTime;
@Schema(description = "用户id")
@@ -92,9 +95,11 @@ public class OrderGoods implements Serializable {
private Integer tenantId;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "应用名称")

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -94,9 +95,11 @@ public class OrderInfo implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -6,6 +6,7 @@ import java.math.BigDecimal;
import java.util.Date;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -69,6 +70,7 @@ public class Organization implements Serializable {
private String email;
@Schema(description = "成立时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date establishTime;
@Schema(description = "注册资金")
@@ -121,9 +123,11 @@ public class Organization implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "机构类型名称")

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -89,9 +90,11 @@ public class Payment implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -75,9 +76,11 @@ public class Plug implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -2,6 +2,7 @@ package com.gxwebsoft.common.system.entity;
import cn.hutool.core.util.DesensitizedUtil;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -96,9 +97,11 @@ public class RechargeOrder implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "昵称")

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -44,9 +45,11 @@ public class Role implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(hidden = true)

View File

@@ -3,6 +3,7 @@ package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -36,9 +37,11 @@ public class RoleMenu implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -46,9 +47,11 @@ public class Setting implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "修改租户名称")

View File

@@ -2,6 +2,7 @@ package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -39,6 +40,7 @@ public class SysFileType implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@@ -2,6 +2,7 @@ package com.gxwebsoft.common.system.entity;
import cn.hutool.core.util.DesensitizedUtil;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -53,6 +54,7 @@ public class Tenant implements Serializable {
private Integer isTrial;
@Schema(description = "试用结束时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date trialEndTime;
@Schema(description = "自动续费")
@@ -63,9 +65,11 @@ public class Tenant implements Serializable {
private Integer deleted;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "菜单信息")
@@ -96,6 +100,18 @@ public class Tenant implements Serializable {
@TableField(exist = false)
private String phone;
@Schema(description = "管理地址")
@TableField(exist = false)
private String adminUrl;
@Schema(description = "顶级域名")
@TableField(exist = false)
private String domain;
@Schema(description = "免费域名")
@TableField(exist = false)
private String freeDomain;
public String getPhone(){
return DesensitizedUtil.mobilePhone(this.phone);
}

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -66,8 +67,10 @@ public class TenantPackage implements Serializable {
private Integer sortNumber;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -31,13 +32,18 @@ public class TenantSubscription implements Serializable {
@Schema(description = "当前套餐ID")
private Integer packageId;
@Schema(description = "套餐名称")
private String packageName;
@Schema(description = "当前版本")
private Integer version;
@Schema(description = "开始时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date startTime;
@Schema(description = "到期时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endTime;
@Schema(description = "是否试用期")
@@ -59,9 +65,11 @@ public class TenantSubscription implements Serializable {
private Integer status;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
// 关联查询字段

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -54,9 +55,11 @@ public class TenantSubscriptionOrder implements Serializable {
private BigDecimal actualPrice;
@Schema(description = "开始时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date startTime;
@Schema(description = "到期时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endTime;
@Schema(description = "是否试用 0否 1是")
@@ -78,6 +81,7 @@ public class TenantSubscriptionOrder implements Serializable {
private String paymentId;
@Schema(description = "支付时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date paymentTime;
@Schema(description = "订单状态 0待支付 1已支付 2已激活 3已取消 4已退款")
@@ -90,8 +94,10 @@ public class TenantSubscriptionOrder implements Serializable {
private Integer userId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -68,9 +69,11 @@ public class UserBalanceLog implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "昵称")

View File

@@ -7,6 +7,7 @@ import java.util.Date;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -39,6 +40,7 @@ public class UserCollection implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -56,9 +57,11 @@ public class UserFile implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "文件访问地址")

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -62,9 +63,11 @@ public class UserGrade implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -50,9 +51,11 @@ public class UserGroup implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -58,9 +59,11 @@ public class UserOauth implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -7,6 +7,7 @@ import java.io.Serializable;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -49,9 +50,11 @@ public class UserReferee implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@TableField(exist = false)

View File

@@ -9,6 +9,7 @@ import java.time.LocalDateTime;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -37,9 +38,11 @@ public class UserRole implements Serializable {
private Integer roleId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "角色名称")

View File

@@ -61,9 +61,13 @@ public class UserVerify implements Serializable {
@Schema(description = "其他证件")
private String files;
@Schema(description = "审核人")
@Schema(description = "操作员ID")
private Integer adminId;
@Schema(description = "操作员名称")
@TableField(exist = false)
private String adminName;
@Schema(description = "机构ID")
private Integer organizationId;

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -73,9 +74,11 @@ public class Version implements Serializable {
private Integer tenantId;
@Schema(description = "注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -59,6 +60,7 @@ public class WebsiteField implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -50,9 +51,11 @@ public class WhiteDomain implements Serializable {
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -1,5 +1,6 @@
package com.gxwebsoft.common.system.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gxwebsoft.common.system.entity.TenantPackage;
import org.apache.ibatis.annotations.Param;
@@ -12,6 +13,7 @@ import java.util.List;
* @author WebSoft
* @since 2025-12-12
*/
@InterceptorIgnore(tenantLine = "true")
public interface TenantPackageMapper extends BaseMapper<TenantPackage> {
/**

View File

@@ -1,5 +1,6 @@
package com.gxwebsoft.common.system.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.common.system.entity.TenantSubscriptionOrder;
@@ -13,6 +14,7 @@ import java.util.List;
* @author WebSoft
* @since 2025-12-12
*/
@InterceptorIgnore(tenantLine = "true")
public interface TenantSubscriptionOrderMapper extends BaseMapper<TenantSubscriptionOrder> {
/**

View File

@@ -16,6 +16,12 @@
<if test="param.userId != null">
AND a.user_id = #{param.userId}
</if>
<if test="param.organizationIds != null and param.organizationIds.size() &gt; 0">
AND b.organization_id IN
<foreach collection="param.organizationIds" item="item" separator="," open="(" close=")">
#{item}
</foreach>
</if>
<if test="param.module != null">
AND a.module LIKE CONCAT('%', #{param.module}, '%')
</if>

View File

@@ -29,6 +29,12 @@
<if test="param.organizationId != null">
AND a.organization_id = #{param.organizationId}
</if>
<if test="param.organizationIds != null and param.organizationIds.size() &gt; 0">
AND a.organization_id IN
<foreach collection="param.organizationIds" item="item" separator="," open="(" close=")">
#{item}
</foreach>
</if>
<if test="param.organizationIdWithChildren != null">
AND (a.organization_id = #{param.organizationIdWithChildren} OR a.parent_id = #{param.organizationIdWithChildren})
</if>

View File

@@ -4,7 +4,7 @@
<!-- 关联查询sql -->
<sql id="selectSql">
SELECT a.*,b.company_name,b.company_logo as logo
SELECT a.*,b.company_name,b.company_logo as logo,b.admin_url,b.domain,b.free_domain
FROM sys_tenant a
LEFT JOIN sys_company b ON a.tenant_id = b.tenant_id
<where>
@@ -44,6 +44,7 @@
<if test="param.keywords != null">
AND (
a.tenant_name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.tenant_code = #{param.keywords}
OR a.tenant_id = #{param.keywords}
)
</if>

View File

@@ -4,10 +4,11 @@
<!-- 关联查询sql -->
<sql id="selectSql">
SELECT a.*, b.phone, c.organization_name
SELECT a.*, b.phone, c.organization_name, d.nickname as adminName
FROM sys_user_verify a
LEFT JOIN sys_user b ON a.user_id = b.user_id
LEFT JOIN sys_organization c ON a.organization_id = c.organization_id
LEFT JOIN sys_user d ON a.admin_id = d.user_id
<where>
<if test="param.id != null">
AND a.id = #{param.id}
@@ -77,6 +78,86 @@
OR a.zz_code = #{param.keywords}
)
</if>
<!--
防止同一个 user_id 在 sys_user_verify 中存在多条记录时,列表查询返回 userId 重复。
这里保留每个 user_id 的最新一条记录(按 id 最大)。
注意:当按主键 id 精确查询时不做去重,避免把历史记录过滤掉。
-->
<if test="param.id == null">
AND a.id IN (
SELECT MAX(v.id)
FROM sys_user_verify v
LEFT JOIN sys_user u ON v.user_id = u.user_id
<where>
<if test="param.userId != null">
AND v.user_id = #{param.userId}
</if>
<if test="param.type != null">
AND v.type = #{param.type}
</if>
<if test="param.name != null">
AND v.name LIKE CONCAT('%', #{param.name}, '%')
</if>
<if test="param.realName != null">
AND v.real_name LIKE CONCAT('%', #{param.realName}, '%')
</if>
<if test="param.idCard != null">
AND v.id_card LIKE CONCAT('%', #{param.idCard}, '%')
</if>
<if test="param.birthday != null">
AND v.birthday LIKE CONCAT('%', #{param.birthday}, '%')
</if>
<if test="param.sfz1 != null">
AND v.sfz1 LIKE CONCAT('%', #{param.sfz1}, '%')
</if>
<if test="param.sfz2 != null">
AND v.sfz2 LIKE CONCAT('%', #{param.sfz2}, '%')
</if>
<if test="param.zzCode != null">
AND v.zz_code = #{param.zzCode}
</if>
<if test="param.adminId != null">
AND v.admin_id = #{param.adminId}
</if>
<if test="param.organizationId != null">
AND v.organization_id = #{param.organizationId}
</if>
<if test="param.comments != null">
AND v.comments LIKE CONCAT('%', #{param.comments}, '%')
</if>
<if test="param.status != null">
AND v.status = #{param.status}
</if>
<if test="param.deleted != null">
AND v.deleted = #{param.deleted}
</if>
<if test="param.deleted == null">
AND v.deleted = 0
</if>
<if test="param.createTimeStart != null">
AND v.create_time &gt;= #{param.createTimeStart}
</if>
<if test="param.createTimeEnd != null">
AND v.create_time &lt;= #{param.createTimeEnd}
</if>
<if test="param.organizationIds != null">
AND v.organization_id IN
<foreach collection="param.organizationIds" item="item" separator="," open="(" close=")">
#{item}
</foreach>
</if>
<if test="param.keywords != null">
AND (v.name LIKE CONCAT('%', #{param.keywords}, '%')
OR v.real_name LIKE CONCAT('%', #{param.keywords}, '%')
OR u.phone = #{param.keywords}
OR v.id_card = #{param.keywords}
OR v.zz_code = #{param.keywords}
)
</if>
</where>
GROUP BY v.user_id
)
</if>
</where>
</sql>

View File

@@ -10,6 +10,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Set;
/**
* 操作日志参数
*
@@ -31,6 +33,10 @@ public class OperationRecordParam extends BaseParam {
@QueryField(type = QueryType.EQ)
private Integer userId;
@Schema(description = "机构id合集")
@TableField(exist = false)
private Set<Integer> organizationIds;
@Schema(description = "操作模块")
private String module;

View File

@@ -12,6 +12,7 @@ import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Set;
/**
* 组织机构查询参数
@@ -30,6 +31,10 @@ public class OrganizationParam extends BaseParam {
@QueryField(type = QueryType.EQ)
private Integer organizationId;
@Schema(description = "机构id合集")
@TableField(exist = false)
private Set<Integer> organizationIds;
@Schema(description = "上级id, 0是顶级")
@QueryField(type = QueryType.EQ)
private Integer parentId;

View File

@@ -0,0 +1,24 @@
package com.gxwebsoft.common.system.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 订阅订单价格试算入参
*/
@Data
@Schema(description = "订阅订单价格试算参数")
public class SubscriptionOrderParam {
@Schema(description = "是否续费1=续费 0=新购")
private Integer isRenewal;
@Schema(description = "是否升级1=升级 0=非升级")
private Integer isUpgrade;
@Schema(description = "套餐ID")
private Integer packageId;
@Schema(description = "支付方式")
private Integer payType;
}

View File

@@ -0,0 +1,18 @@
package com.gxwebsoft.common.system.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 订阅订单创建结果
*/
@Data
@Schema(description = "订阅订单创建结果")
public class SubscriptionOrderCreateResult {
@Schema(description = "订单号")
private String orderNo;
@Schema(description = "价格试算结果")
private SubscriptionPriceResult price;
}

View File

@@ -0,0 +1,21 @@
package com.gxwebsoft.common.system.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 订阅订单支付结果
*/
@Data
@Schema(description = "订阅订单支付结果")
public class SubscriptionOrderPayResult {
@Schema(description = "订单号")
private String orderNo;
@Schema(description = "支付二维码链接")
private String codeUrl;
@Schema(description = "价格信息")
private SubscriptionPriceResult price;
}

View File

@@ -0,0 +1,41 @@
package com.gxwebsoft.common.system.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
/**
* 订阅订单价格试算结果
*/
@Data
@Schema(description = "订阅订单价格试算结果")
public class SubscriptionPriceResult {
@Schema(description = "套餐ID")
private Integer packageId;
@Schema(description = "是否续费1=续费 0=新购")
private Integer isRenewal;
@Schema(description = "是否升级1=升级 0=非升级")
private Integer isUpgrade;
@Schema(description = "支付方式")
private Integer payType;
@Schema(description = "原价")
private BigDecimal originalPrice;
@Schema(description = "优惠金额")
private BigDecimal discountAmount;
@Schema(description = "应付金额")
private BigDecimal payPrice;
@Schema(description = "总价(同原价,用于兼容前端字段)")
private BigDecimal totalPrice;
@Schema(description = "价格说明")
private String remark;
}

View File

@@ -0,0 +1,18 @@
package com.gxwebsoft.common.system.service;
/**
* 微信小程序 access_token 获取服务(按租户)。
*
* <p>用于调用微信小程序开放接口(例如:上传发货信息)。</p>
*/
public interface WxMiniappAccessTokenService {
/**
* 获取指定租户的小程序 access_token内部带缓存
*
* @param tenantId 租户ID
* @return access_token
*/
String getAccessToken(Integer tenantId);
}

View File

@@ -261,6 +261,9 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
if(userParam.getRegion() != null){
addUser.setRegion(userParam.getRegion());
}
if(userParam.getIsAdmin() != null){
addUser.setIsAdmin(userParam.getIsAdmin());
}
if(userParam.getAddress() != null){
addUser.setAddress(userParam.getAddress());
}
@@ -273,7 +276,41 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
}
addUser.setTenantId(userParam.getTenantId());
addUser.setRecommend(0);
Role role = roleService.getOne(new QueryWrapper<Role>().eq("role_code", "user"), false);
// Role assignment:
// - If roleId is provided (e.g. invite flow), use it (must belong to the same tenant).
// - Otherwise, fall back to roleCode; default to "user".
Role role = null;
if (userParam.getRoleId() != null) {
role = roleService.getById(userParam.getRoleId());
if (role != null
&& addUser.getTenantId() != null
&& role.getTenantId() != null
&& !addUser.getTenantId().equals(role.getTenantId())) {
throw new BusinessException("角色不属于当前租户");
}
}
String roleCode = userParam.getRoleCode();
if (role == null) {
roleCode = StrUtil.blankToDefault(roleCode, "user");
QueryWrapper<Role> roleQw = new QueryWrapper<Role>().eq("role_code", roleCode);
if (addUser.getTenantId() != null) {
roleQw.eq("tenant_id", addUser.getTenantId());
}
role = roleService.getOne(roleQw, false);
// If the default "user" role is missing (fresh DB / incomplete init), create it to avoid empty roles.
if (role == null && addUser.getTenantId() != null && "user".equals(roleCode)) {
Role defaultRole = new Role();
defaultRole.setRoleName("注册用户");
defaultRole.setRoleCode("user");
defaultRole.setComments("普通注册用户");
defaultRole.setTenantId(addUser.getTenantId());
roleService.save(defaultRole);
role = defaultRole;
}
}
if (role == null) {
throw new BusinessException("缺少默认角色(role_code=" + (roleCode == null ? "user" : roleCode) + "),请先初始化角色");
}
addUser.setRoleId(role.getRoleId());
if (saveUser(addUser)) {
// 添加用户角色
@@ -283,6 +320,9 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
userRole.setRoleId(addUser.getRoleId());
userRoleService.save(userRole);
}
// Ensure caller (e.g. register / invite register) gets non-empty roles/authorities in response.
addUser.setRoles(userRoleService.listByUserId(addUser.getUserId()));
addUser.setAuthorities(roleMenuService.listMenuByUserId(addUser.getUserId(), null));
return addUser;
}

View File

@@ -0,0 +1,53 @@
# 生产环境配置
# 数据源配置
spring:
datasource:
url: jdbc:mysql://1Panel-mysql-XsWW:3306/gxwebsoft_core?useSSL=false&serverTimezone=UTC
username: gxwebsoft_core
password: ZXT5FkBREBJQPiAs
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
redis:
database: 0
host: 1Panel-redis-GmNr
port: 6379
password: redis_t74P8C
# 日志配置
logging:
file:
name: websoft-core.log
level:
root: WARN
com.gxwebsoft: ERROR
com.baomidou.mybatisplus: ERROR
socketio:
host: 0.0.0.0 #IP地址
knife4j:
# 开启knife4j增强
enable: true
# 开启生产环境屏蔽一定要先开启knife4j增强才会生效
production: false
# 框架配置
config:
# 生产环境接口
server-url: https://server.guiletao.com/api
upload-path: /www/wwwroot/file.ws
# 阿里云OSS云存储
endpoint: https://oss-cn-shenzhen.aliyuncs.com
accessKeyId: LTAI4GKGZ9Z2Z8JZ77c3GNZP
accessKeySecret: BiDkpS7UXj72HWwDWaFZxiXjNFBNCM
bucketName: oss-gxwebsoft
bucketDomain: https://oss.wsdns.cn
aliyunDomain: https://oss-gxwebsoft.oss-cn-shenzhen.aliyuncs.com
# 生产环境证书配置
certificate:
# 生产环境使用挂载卷模式
load-mode: VOLUME
cert-root-path: /app/certs