mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-08 14:15:34 +08:00
feat(system): 增强平台移动端应用配置与校验
完善 MobileApp 配置读写、错误与校验文案, 补充 mobile 相关能力。
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"notExist": "Mobile app configuration does not exist",
|
||||
"notEnabled": "Mobile app configuration is disabled: {0}/{1}",
|
||||
"appTypeNotFound": "No matching mobile app type found: {0}",
|
||||
"platformNotFound": "No matching mobile platform found: {0}"
|
||||
"platformNotFound": "No matching mobile platform found: {0}",
|
||||
"platformNotAllowed": "App type {0} does not support mobile platform {1}",
|
||||
"configRequired": "Please submit app config for platform {0}",
|
||||
"configPlatformMismatch": "App config does not match platform {0}",
|
||||
"alipayCertRequired": "In certificate mode, please upload app cert, Alipay cert and root cert"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"notExist": "移动端应用配置不存在",
|
||||
"notEnabled": "移动端应用配置未启用: {0}/{1}",
|
||||
"appTypeNotFound": "未找到对应的移动端应用端类型: {0}",
|
||||
"platformNotFound": "未找到对应的移动端平台: {0}"
|
||||
"platformNotFound": "未找到对应的移动端平台: {0}",
|
||||
"platformNotAllowed": "端类型 {0} 不支持移动平台 {1}",
|
||||
"configRequired": "请提交与平台 {0} 对应的应用配置",
|
||||
"configPlatformMismatch": "应用配置与平台 {0} 不匹配",
|
||||
"alipayCertRequired": "证书模式下请上传应用公钥证书、支付宝公钥证书与根证书"
|
||||
}
|
||||
|
||||
@@ -53,6 +53,15 @@ public class MobileAppController {
|
||||
return Res.ok(mobileAppService.findById(id));
|
||||
}
|
||||
|
||||
@PermCode(code = "view", nameCn = "移动端应用查看", nameEn = "Mobile App View")
|
||||
@Operation(summary = "按端类型+平台查询(不存在返回 null)")
|
||||
@GetMapping("/get-by-type-platform")
|
||||
public Result<MobileAppResult> findByAppTypeAndPlatform(
|
||||
@NotBlank(message = "{validation.field.appType.notBlank}") String appType,
|
||||
@NotBlank(message = "{validation.field.platform.notBlank}") String platform) {
|
||||
return Res.ok(mobileAppService.findByAppTypeAndPlatform(appType, platform).orElse(null));
|
||||
}
|
||||
|
||||
@PermCode(code = "manage", nameCn = "移动端应用管理", nameEn = "Mobile App Manage")
|
||||
@Operation(summary = "保存(按端类型+平台 upsert)")
|
||||
@PostMapping("/save")
|
||||
|
||||
@@ -5,26 +5,32 @@ import cn.daxpay.open.platform.system.param.mobile.MobileAppParam;
|
||||
import cn.daxpay.open.platform.system.result.mobile.MobileAppResult;
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.NullValuePropertyMappingStrategy;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/// # 移动端应用配置转换
|
||||
/// # 移动端应用配置外壳转换
|
||||
///
|
||||
/// copy 使用 IGNORE 策略, 前端未传的敏感字段(null)不会覆盖数据库原值,
|
||||
/// 与前端 diffForm + 后端脱敏回显双重保护配合, 避免误清空。
|
||||
/// 嵌套平台配置与 appConfig 密文由 [cn.daxpay.open.platform.system.service.mobile.MobileAppService] 处理,
|
||||
/// 此处仅映射外壳字段。
|
||||
@Mapper
|
||||
public interface MobileAppConvert {
|
||||
|
||||
MobileAppConvert CONVERT = Mappers.getMapper(MobileAppConvert.class);
|
||||
|
||||
/// 实体转结果
|
||||
/// 实体转结果(嵌套配置由 Service 填充)
|
||||
@Mapping(target = "wxMini", ignore = true)
|
||||
@Mapping(target = "alipayMini", ignore = true)
|
||||
@Mapping(target = "dyMini", ignore = true)
|
||||
MobileAppResult toResult(MobileApp entity);
|
||||
|
||||
/// 参数转实体(新增)
|
||||
/// 参数转实体(新增, appConfig 由 Service 序列化后写入)
|
||||
@Mapping(target = "appConfig", ignore = true)
|
||||
MobileApp toEntity(MobileAppParam param);
|
||||
|
||||
/// 参数拷贝到实体(更新)
|
||||
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
@Mapping(target = "appConfig", ignore = true)
|
||||
void copy(MobileAppParam param, @MappingTarget MobileApp entity);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,25 @@ import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager;
|
||||
import cn.daxpay.open.platform.system.entity.mobile.MobileApp;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/// # 移动端应用配置管理器
|
||||
@Repository
|
||||
public class MobileAppManager extends BaseManager<MobileAppMapper, MobileApp> {
|
||||
|
||||
/// 按端类型 + 移动平台查询(唯一键)
|
||||
public Optional<MobileApp> findByAppTypeAndPlatform(String appType, String platform) {
|
||||
return lambdaQuery()
|
||||
.eq(MobileApp::getAppType, appType)
|
||||
.eq(MobileApp::getPlatform, platform)
|
||||
.oneOpt();
|
||||
}
|
||||
|
||||
/// 按端类型查询全部平台配置
|
||||
public List<MobileApp> findAllByAppType(String appType) {
|
||||
return lambdaQuery()
|
||||
.eq(MobileApp::getAppType, appType)
|
||||
.list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.daxpay.open.platform.system.entity.mobile;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.base.MpBaseEntity;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.handler.encrypt.DataEncryptTypeHandler;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.handler.type.JsonbStringTypeHandler;
|
||||
import cn.daxpay.open.platform.system.convert.mobile.MobileAppConvert;
|
||||
import cn.daxpay.open.platform.system.result.mobile.MobileAppResult;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
@@ -14,7 +15,8 @@ import lombok.experimental.Accessors;
|
||||
/// # 移动端应用配置
|
||||
///
|
||||
/// 平台级移动端应用配置, 按端类型(appType)+移动平台(platform)维度, 每组合一条记录。
|
||||
/// app_config/notify_config 以 JSON 文本存储, 通过 [DataEncryptTypeHandler] AES-256-GCM 加密入库。
|
||||
/// - app_config: text, 含密钥, 经 [DataEncryptTypeHandler] AES-256-GCM 加密入库
|
||||
/// - notify_config: jsonb, 通知模板/开关等非敏感配置, 经 [JsonbStringTypeHandler] 明文存储
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@@ -29,15 +31,12 @@ public class MobileApp extends MpBaseEntity implements ToResult<MobileAppResult>
|
||||
/// @see cn.daxpay.open.platform.system.enums.MobilePlatformEnum
|
||||
private String platform;
|
||||
|
||||
/// 应用名称(展示用)
|
||||
private String appName;
|
||||
|
||||
/// 平台特有密钥配置(JSON文本, 加密存储)
|
||||
/// 平台特有密钥配置(JSON文本, 加密存储为 text)
|
||||
@TableField(typeHandler = DataEncryptTypeHandler.class)
|
||||
private String appConfig;
|
||||
|
||||
/// 消息通知配置(JSON文本, 加密存储)
|
||||
@TableField(typeHandler = DataEncryptTypeHandler.class)
|
||||
/// 消息通知配置(jsonb, 非敏感, 明文; Java 侧为原始 JSON 字符串)
|
||||
@TableField(typeHandler = JsonbStringTypeHandler.class)
|
||||
private String notifyConfig;
|
||||
|
||||
/// 是否启用第三方账号用户绑定
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.daxpay.open.platform.system.mobile.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付宝小程序应用配置(落库 JSON 形状)
|
||||
///
|
||||
/// 序列化后写入 [cn.daxpay.open.platform.system.entity.mobile.MobileApp#appConfig] 并加密存储。
|
||||
/// authType 与平台支付宝认证一致: public_key / cert。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AlipayMiniAppConfig {
|
||||
|
||||
/// 小程序 AppId
|
||||
private String appId;
|
||||
|
||||
/// 鉴权方式: public_key(公钥) / cert(证书)
|
||||
private String authType;
|
||||
|
||||
/// 应用私钥
|
||||
private String privateKey;
|
||||
|
||||
/// 支付宝公钥(公钥模式)
|
||||
private String alipayPublicKey;
|
||||
|
||||
/// 应用公钥证书(证书模式)
|
||||
private String appCert;
|
||||
|
||||
/// 支付宝公钥证书(证书模式)
|
||||
private String alipayCert;
|
||||
|
||||
/// 支付宝根证书(证书模式)
|
||||
private String alipayRootCert;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.daxpay.open.platform.system.mobile.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 抖音小程序应用配置(落库 JSON 形状, 预留)
|
||||
///
|
||||
/// 序列化后写入 [cn.daxpay.open.platform.system.entity.mobile.MobileApp#appConfig] 并加密存储。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class DyMiniAppConfig {
|
||||
|
||||
/// 小程序 AppId
|
||||
private String appId;
|
||||
|
||||
/// 小程序 AppSecret
|
||||
private String appSecret;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.daxpay.open.platform.system.mobile.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 微信小程序应用配置(落库 JSON 形状)
|
||||
///
|
||||
/// 序列化后写入 [cn.daxpay.open.platform.system.entity.mobile.MobileApp#appConfig] 并加密存储。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class WxMiniAppConfig {
|
||||
|
||||
/// 小程序 AppId
|
||||
private String appId;
|
||||
|
||||
/// 小程序 AppSecret
|
||||
private String appSecret;
|
||||
|
||||
/// 原始 ID(gh_ 开头, 可选)
|
||||
private String originalId;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cn.daxpay.open.platform.system.mobile.config.convert;
|
||||
|
||||
import cn.daxpay.open.platform.system.mobile.config.AlipayMiniAppConfig;
|
||||
import cn.daxpay.open.platform.system.mobile.config.DyMiniAppConfig;
|
||||
import cn.daxpay.open.platform.system.mobile.config.WxMiniAppConfig;
|
||||
import cn.daxpay.open.platform.system.mobile.config.param.AlipayMiniAppConfigParam;
|
||||
import cn.daxpay.open.platform.system.mobile.config.param.DyMiniAppConfigParam;
|
||||
import cn.daxpay.open.platform.system.mobile.config.param.WxMiniAppConfigParam;
|
||||
import cn.daxpay.open.platform.system.mobile.config.result.AlipayMiniAppConfigResult;
|
||||
import cn.daxpay.open.platform.system.mobile.config.result.DyMiniAppConfigResult;
|
||||
import cn.daxpay.open.platform.system.mobile.config.result.WxMiniAppConfigResult;
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.NullValuePropertyMappingStrategy;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/// # 移动端各平台应用配置转换
|
||||
///
|
||||
/// Param → Config 使用 IGNORE, 空敏感字段不覆盖目标(merge 时先 copy 旧值再 overlay)。
|
||||
@Mapper
|
||||
public interface MobileAppConfigConvert {
|
||||
|
||||
MobileAppConfigConvert CONVERT = Mappers.getMapper(MobileAppConfigConvert.class);
|
||||
|
||||
WxMiniAppConfig toConfig(WxMiniAppConfigParam param);
|
||||
|
||||
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
void copy(WxMiniAppConfigParam param, @MappingTarget WxMiniAppConfig config);
|
||||
|
||||
WxMiniAppConfigResult toResult(WxMiniAppConfig config);
|
||||
|
||||
AlipayMiniAppConfig toConfig(AlipayMiniAppConfigParam param);
|
||||
|
||||
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
void copy(AlipayMiniAppConfigParam param, @MappingTarget AlipayMiniAppConfig config);
|
||||
|
||||
AlipayMiniAppConfigResult toResult(AlipayMiniAppConfig config);
|
||||
|
||||
DyMiniAppConfig toConfig(DyMiniAppConfigParam param);
|
||||
|
||||
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
void copy(DyMiniAppConfigParam param, @MappingTarget DyMiniAppConfig config);
|
||||
|
||||
DyMiniAppConfigResult toResult(DyMiniAppConfig config);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.daxpay.open.platform.system.mobile.config.param;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付宝小程序应用配置入参
|
||||
///
|
||||
/// 敏感字段为空表示不更新; 公钥/证书材料互斥校验在 Service 按新建/更新处理。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "支付宝小程序应用配置参数")
|
||||
public class AlipayMiniAppConfigParam {
|
||||
|
||||
@NotBlank(message = "{validation.field.appId.notBlank}")
|
||||
@Schema(description = "小程序 AppId")
|
||||
private String appId;
|
||||
|
||||
@NotBlank(message = "{validation.field.authType.notBlank}")
|
||||
@Schema(description = "鉴权方式: public_key / cert")
|
||||
private String authType;
|
||||
|
||||
@Schema(description = "应用私钥(空则不更新)")
|
||||
private String privateKey;
|
||||
|
||||
@Schema(description = "支付宝公钥(公钥模式, 空则不更新)")
|
||||
private String alipayPublicKey;
|
||||
|
||||
@Schema(description = "应用公钥证书(证书模式, 空则不更新)")
|
||||
private String appCert;
|
||||
|
||||
@Schema(description = "支付宝公钥证书(证书模式, 空则不更新)")
|
||||
private String alipayCert;
|
||||
|
||||
@Schema(description = "支付宝根证书(证书模式, 空则不更新)")
|
||||
private String alipayRootCert;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.daxpay.open.platform.system.mobile.config.param;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 抖音小程序应用配置入参(预留)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "抖音小程序应用配置参数")
|
||||
public class DyMiniAppConfigParam {
|
||||
|
||||
@NotBlank(message = "{validation.field.appId.notBlank}")
|
||||
@Schema(description = "小程序 AppId")
|
||||
private String appId;
|
||||
|
||||
@Schema(description = "小程序 AppSecret(空则不更新)")
|
||||
private String appSecret;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.daxpay.open.platform.system.mobile.config.param;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 微信小程序应用配置入参
|
||||
///
|
||||
/// 敏感字段 appSecret 为空表示不更新(配合前端 diffForm)。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "微信小程序应用配置参数")
|
||||
public class WxMiniAppConfigParam {
|
||||
|
||||
@NotBlank(message = "{validation.field.appId.notBlank}")
|
||||
@Schema(description = "小程序 AppId")
|
||||
private String appId;
|
||||
|
||||
@Schema(description = "小程序 AppSecret(空则不更新)")
|
||||
private String appSecret;
|
||||
|
||||
@Schema(description = "原始 ID(gh_ 开头, 可选)")
|
||||
private String originalId;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.daxpay.open.platform.system.mobile.config.result;
|
||||
|
||||
import cn.daxpay.open.platform.common.json.sensitive.SensitiveInfo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付宝小程序应用配置出参(敏感字段脱敏)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "支付宝小程序应用配置")
|
||||
public class AlipayMiniAppConfigResult {
|
||||
|
||||
@Schema(description = "小程序 AppId")
|
||||
private String appId;
|
||||
|
||||
@Schema(description = "鉴权方式: public_key / cert")
|
||||
private String authType;
|
||||
|
||||
@SensitiveInfo
|
||||
@Schema(description = "应用私钥")
|
||||
private String privateKey;
|
||||
|
||||
@SensitiveInfo
|
||||
@Schema(description = "支付宝公钥")
|
||||
private String alipayPublicKey;
|
||||
|
||||
@SensitiveInfo
|
||||
@Schema(description = "应用公钥证书")
|
||||
private String appCert;
|
||||
|
||||
@SensitiveInfo
|
||||
@Schema(description = "支付宝公钥证书")
|
||||
private String alipayCert;
|
||||
|
||||
@SensitiveInfo
|
||||
@Schema(description = "支付宝根证书")
|
||||
private String alipayRootCert;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.daxpay.open.platform.system.mobile.config.result;
|
||||
|
||||
import cn.daxpay.open.platform.common.json.sensitive.SensitiveInfo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 抖音小程序应用配置出参(预留, 敏感字段脱敏)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "抖音小程序应用配置")
|
||||
public class DyMiniAppConfigResult {
|
||||
|
||||
@Schema(description = "小程序 AppId")
|
||||
private String appId;
|
||||
|
||||
@SensitiveInfo
|
||||
@Schema(description = "小程序 AppSecret")
|
||||
private String appSecret;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.daxpay.open.platform.system.mobile.config.result;
|
||||
|
||||
import cn.daxpay.open.platform.common.json.sensitive.SensitiveInfo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 微信小程序应用配置出参(敏感字段脱敏)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "微信小程序应用配置")
|
||||
public class WxMiniAppConfigResult {
|
||||
|
||||
@Schema(description = "小程序 AppId")
|
||||
private String appId;
|
||||
|
||||
@SensitiveInfo
|
||||
@Schema(description = "小程序 AppSecret")
|
||||
private String appSecret;
|
||||
|
||||
@Schema(description = "原始 ID")
|
||||
private String originalId;
|
||||
}
|
||||
@@ -1,33 +1,53 @@
|
||||
package cn.daxpay.open.platform.system.param.mobile;
|
||||
|
||||
import cn.daxpay.open.platform.system.mobile.config.param.AlipayMiniAppConfigParam;
|
||||
import cn.daxpay.open.platform.system.mobile.config.param.DyMiniAppConfigParam;
|
||||
import cn.daxpay.open.platform.system.mobile.config.param.WxMiniAppConfigParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 移动端应用配置参数
|
||||
///
|
||||
/// 保存时按 appType + platform 进行 upsert, 二者组合为唯一键。
|
||||
/// 保存时按 appType + platform 进行 upsert。平台密钥走嵌套强类型对象, 与 platform 一一对应:
|
||||
/// - wx_mini → wxMini
|
||||
/// - alipay_mini → alipayMini
|
||||
/// - dy_mini → dyMini
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "移动端应用配置参数")
|
||||
public class MobileAppParam {
|
||||
|
||||
@Schema(description = "主键(更新时必填)")
|
||||
@Schema(description = "主键(更新时传)")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "端类型")
|
||||
/// 端类型
|
||||
/// @see cn.daxpay.open.platform.system.enums.MobileAppTypeEnum
|
||||
@NotBlank(message = "{validation.field.appType.notBlank}")
|
||||
@Schema(description = "端类型: merchant/admin/cashier")
|
||||
private String appType;
|
||||
|
||||
@Schema(description = "移动平台")
|
||||
/// 移动平台
|
||||
/// @see cn.daxpay.open.platform.system.enums.MobilePlatformEnum
|
||||
@NotBlank(message = "{validation.field.platform.notBlank}")
|
||||
@Schema(description = "移动平台: wx_h5/wx_mini/alipay_mini/dy_mini/android/ios")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "应用名称")
|
||||
private String appName;
|
||||
@Valid
|
||||
@Schema(description = "微信小程序配置(platform=wx_mini 时必填)")
|
||||
private WxMiniAppConfigParam wxMini;
|
||||
|
||||
@Schema(description = "平台特有密钥配置(JSON文本)")
|
||||
private String appConfig;
|
||||
@Valid
|
||||
@Schema(description = "支付宝小程序配置(platform=alipay_mini 时必填)")
|
||||
private AlipayMiniAppConfigParam alipayMini;
|
||||
|
||||
@Schema(description = "消息通知配置(JSON文本)")
|
||||
@Valid
|
||||
@Schema(description = "抖音小程序配置(platform=dy_mini 时必填, 预留)")
|
||||
private DyMiniAppConfigParam dyMini;
|
||||
|
||||
@Schema(description = "消息通知配置(jsonb 原始JSON文本, 明文非敏感)")
|
||||
private String notifyConfig;
|
||||
|
||||
@Schema(description = "是否启用第三方账号用户绑定")
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package cn.daxpay.open.platform.system.result.mobile;
|
||||
|
||||
import cn.daxpay.open.platform.core.result.BaseResult;
|
||||
import cn.daxpay.open.platform.system.mobile.config.result.AlipayMiniAppConfigResult;
|
||||
import cn.daxpay.open.platform.system.mobile.config.result.DyMiniAppConfigResult;
|
||||
import cn.daxpay.open.platform.system.mobile.config.result.WxMiniAppConfigResult;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 移动端应用配置结果
|
||||
///
|
||||
/// 平台密钥以嵌套强类型返回(敏感字段脱敏), 按 platform 仅填充对应嵌套。
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@@ -19,13 +24,16 @@ public class MobileAppResult extends BaseResult {
|
||||
@Schema(description = "移动平台")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "应用名称")
|
||||
private String appName;
|
||||
@Schema(description = "微信小程序配置")
|
||||
private WxMiniAppConfigResult wxMini;
|
||||
|
||||
@Schema(description = "平台特有密钥配置(JSON文本)")
|
||||
private String appConfig;
|
||||
@Schema(description = "支付宝小程序配置")
|
||||
private AlipayMiniAppConfigResult alipayMini;
|
||||
|
||||
@Schema(description = "消息通知配置(JSON文本)")
|
||||
@Schema(description = "抖音小程序配置")
|
||||
private DyMiniAppConfigResult dyMini;
|
||||
|
||||
@Schema(description = "消息通知配置(jsonb 原始JSON文本, 明文非敏感)")
|
||||
private String notifyConfig;
|
||||
|
||||
@Schema(description = "是否启用第三方账号用户绑定")
|
||||
|
||||
@@ -1,41 +1,64 @@
|
||||
package cn.daxpay.open.platform.system.service.mobile;
|
||||
|
||||
import cn.daxpay.open.platform.capability.alipay.auth.config.AlipayAuthTypeEnum;
|
||||
import cn.daxpay.open.platform.common.json.util.JacksonUtil;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import cn.daxpay.open.platform.system.convert.mobile.MobileAppConvert;
|
||||
import cn.daxpay.open.platform.system.dao.mobile.MobileAppManager;
|
||||
import cn.daxpay.open.platform.system.entity.mobile.MobileApp;
|
||||
import cn.daxpay.open.platform.system.enums.MobileAppTypeEnum;
|
||||
import cn.daxpay.open.platform.system.enums.MobilePlatformEnum;
|
||||
import cn.daxpay.open.platform.system.mobile.config.AlipayMiniAppConfig;
|
||||
import cn.daxpay.open.platform.system.mobile.config.DyMiniAppConfig;
|
||||
import cn.daxpay.open.platform.system.mobile.config.WxMiniAppConfig;
|
||||
import cn.daxpay.open.platform.system.mobile.config.convert.MobileAppConfigConvert;
|
||||
import cn.daxpay.open.platform.system.mobile.config.param.AlipayMiniAppConfigParam;
|
||||
import cn.daxpay.open.platform.system.mobile.config.param.DyMiniAppConfigParam;
|
||||
import cn.daxpay.open.platform.system.mobile.config.param.WxMiniAppConfigParam;
|
||||
import cn.daxpay.open.platform.system.param.mobile.MobileAppParam;
|
||||
import cn.daxpay.open.platform.system.result.mobile.MobileAppResult;
|
||||
import cn.hutool.core.util.DesensitizedUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/// # 移动端应用配置服务
|
||||
///
|
||||
/// 平台级配置, 按端类型(appType)+移动平台(platform)维度管理。
|
||||
/// app_config/notify_config 经 [cn.daxpay.open.platform.common.mybatisplus.handler.encrypt.DataEncryptTypeHandler] 加密入库;
|
||||
/// 返回前端时对 app_config 内敏感键(appSecret/privateKey/alipayPublicKey/证书等)脱敏;
|
||||
/// 保存时敏感键为空则保留库中原值(配合前端 diffForm)。
|
||||
/// 平台级配置, 按端类型(appType)+移动平台(platform)维度管理, 每组合最多一条(唯一键)。
|
||||
/// - app_config: 强类型 Config 序列化后经 [DataEncryptTypeHandler] 加密入库
|
||||
/// - API 层: wxMini / alipayMini / dyMini 嵌套对象; notify_config 仍为明文 jsonb 字符串
|
||||
///
|
||||
/// ## 端类型与平台白名单
|
||||
/// - merchant: wx_h5 / wx_mini / alipay_mini / dy_mini
|
||||
/// - admin: wx_mini / alipay_mini / dy_mini
|
||||
/// - cashier: wx_mini / alipay_mini(抖音本期未开放)
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MobileAppService {
|
||||
|
||||
/// app_config 中需要脱敏/保护的敏感键
|
||||
/// 含支付宝小程序公钥字段与证书模式三本证书
|
||||
private static final Set<String> SENSITIVE_KEYS = Set.of(
|
||||
"appSecret", "privateKey", "alipayPublicKey", "clientSecret",
|
||||
"appCert", "alipayCert", "alipayRootCert");
|
||||
/// 各端类型允许配置的移动平台(本期开放范围)
|
||||
private static final Map<MobileAppTypeEnum, Set<MobilePlatformEnum>> ALLOWED_PLATFORMS = Map.of(
|
||||
MobileAppTypeEnum.MERCHANT, Set.of(
|
||||
MobilePlatformEnum.WX_H5,
|
||||
MobilePlatformEnum.WX_MINI,
|
||||
MobilePlatformEnum.ALIPAY_MINI,
|
||||
MobilePlatformEnum.DY_MINI),
|
||||
MobileAppTypeEnum.ADMIN, Set.of(
|
||||
MobilePlatformEnum.WX_MINI,
|
||||
MobilePlatformEnum.ALIPAY_MINI,
|
||||
MobilePlatformEnum.DY_MINI),
|
||||
MobileAppTypeEnum.CASHIER, Set.of(
|
||||
MobilePlatformEnum.WX_MINI,
|
||||
MobilePlatformEnum.ALIPAY_MINI));
|
||||
|
||||
private final MobileAppManager manager;
|
||||
|
||||
@@ -46,37 +69,89 @@ public class MobileAppService {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 按端类型查询所有平台配置(端详情页Tab列表)
|
||||
/// 按端类型查询所有平台配置(端详情页 Tab 列表)
|
||||
public List<MobileAppResult> findAllByAppType(String appType) {
|
||||
return manager.findAllByField(MobileApp::getAppType, appType).stream()
|
||||
MobileAppTypeEnum.findByCode(appType);
|
||||
return manager.findAllByAppType(appType).stream()
|
||||
.map(this::toMaskedResult)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 查询单条
|
||||
/// 查询单条(运营端, 脱敏)
|
||||
public MobileAppResult findById(Long id) {
|
||||
// 通用: 移动端应用配置不存在
|
||||
MobileApp entity = manager.findById(id)
|
||||
.orElseThrow(() -> new DataNotExistException("error.mobile_app.notExist"));
|
||||
return toMaskedResult(entity);
|
||||
}
|
||||
|
||||
/// 按端类型+平台查询(运营端, 脱敏; 不存在返回 empty)
|
||||
public Optional<MobileAppResult> findByAppTypeAndPlatform(String appType, String platform) {
|
||||
MobileAppTypeEnum.findByCode(appType);
|
||||
MobilePlatformEnum.findByCode(platform);
|
||||
return manager.findByAppTypeAndPlatform(appType, platform)
|
||||
.map(this::toMaskedResult);
|
||||
}
|
||||
|
||||
/// 运行时读取实体(不脱敏, appConfig 为解密后 JSON 明文)
|
||||
public MobileApp getEntity(String appType, String platform) {
|
||||
return manager.findByAppTypeAndPlatform(appType, platform)
|
||||
.orElseThrow(() -> new DataNotExistException("error.mobile_app.notExist"));
|
||||
}
|
||||
|
||||
/// 运行时读取已启用的配置实体
|
||||
public MobileApp getEnabledEntity(String appType, String platform) {
|
||||
MobileApp entity = getEntity(appType, platform);
|
||||
if (!Boolean.TRUE.equals(entity.getEnabled())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.mobile_app.notEnabled", appType, platform);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// 运行时: 解析微信小程序配置(明文密钥)
|
||||
public WxMiniAppConfig getWxMiniConfig(String appType) {
|
||||
return parseConfig(getEnabledEntity(appType, MobilePlatformEnum.WX_MINI.getCode()).getAppConfig(),
|
||||
WxMiniAppConfig.class);
|
||||
}
|
||||
|
||||
/// 运行时: 解析支付宝小程序配置(明文密钥)
|
||||
public AlipayMiniAppConfig getAlipayMiniConfig(String appType) {
|
||||
return parseConfig(getEnabledEntity(appType, MobilePlatformEnum.ALIPAY_MINI.getCode()).getAppConfig(),
|
||||
AlipayMiniAppConfig.class);
|
||||
}
|
||||
|
||||
/// 运行时: 解析抖音小程序配置(明文密钥)
|
||||
public DyMiniAppConfig getDyMiniConfig(String appType) {
|
||||
return parseConfig(getEnabledEntity(appType, MobilePlatformEnum.DY_MINI.getCode()).getAppConfig(),
|
||||
DyMiniAppConfig.class);
|
||||
}
|
||||
|
||||
/// 保存(按端类型+平台组合 upsert)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public MobileAppResult save(MobileAppParam param) {
|
||||
var existing = manager.lambdaQuery()
|
||||
.eq(MobileApp::getAppType, param.getAppType())
|
||||
.eq(MobileApp::getPlatform, param.getPlatform())
|
||||
.oneOpt();
|
||||
MobilePlatformEnum platform = validateSaveParam(param);
|
||||
String appConfigJson = buildAppConfigJson(param, platform,
|
||||
manager.findByAppTypeAndPlatform(param.getAppType(), param.getPlatform())
|
||||
.map(MobileApp::getAppConfig)
|
||||
.orElse(null));
|
||||
|
||||
var existing = manager.findByAppTypeAndPlatform(param.getAppType(), param.getPlatform());
|
||||
if (existing.isPresent()) {
|
||||
var entity = existing.get();
|
||||
// 敏感键为空时保留库中原值, 避免 diffForm 跳过字段后被清空
|
||||
param.setAppConfig(mergeSensitiveJson(entity.getAppConfig(), param.getAppConfig()));
|
||||
MobileAppConvert.CONVERT.copy(param, entity);
|
||||
entity.setAppConfig(appConfigJson);
|
||||
manager.updateById(entity);
|
||||
return toMaskedResult(entity);
|
||||
}
|
||||
|
||||
var entity = MobileAppConvert.CONVERT.toEntity(param);
|
||||
entity.setAppConfig(appConfigJson);
|
||||
if (entity.getEnabled() == null) {
|
||||
entity.setEnabled(true);
|
||||
}
|
||||
if (entity.getBindingEnabled() == null) {
|
||||
entity.setBindingEnabled(false);
|
||||
}
|
||||
manager.save(entity);
|
||||
return toMaskedResult(entity);
|
||||
}
|
||||
@@ -85,75 +160,220 @@ public class MobileAppService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateEnabled(Long id, Boolean enabled) {
|
||||
var entity = manager.findById(id)
|
||||
// 通用: 移动端应用配置不存在
|
||||
.orElseThrow(() -> new DataNotExistException("error.mobile_app.notExist"));
|
||||
entity.setEnabled(enabled);
|
||||
manager.updateById(entity);
|
||||
}
|
||||
|
||||
/// 实体转结果并对 app_config 敏感键脱敏
|
||||
/// 保存前校验白名单 + platform 与嵌套对象匹配
|
||||
private MobilePlatformEnum validateSaveParam(MobileAppParam param) {
|
||||
MobileAppTypeEnum appType = MobileAppTypeEnum.findByCode(param.getAppType());
|
||||
MobilePlatformEnum platform = MobilePlatformEnum.findByCode(param.getPlatform());
|
||||
|
||||
Set<MobilePlatformEnum> allowed = ALLOWED_PLATFORMS.get(appType);
|
||||
if (allowed == null || !allowed.contains(platform)) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.mobile_app.platformNotAllowed",
|
||||
appType.getCode(), platform.getCode());
|
||||
}
|
||||
|
||||
// 统计传入的嵌套配置数量, 必须恰好 1 个且与 platform 对应
|
||||
int nestedCount = 0;
|
||||
if (param.getWxMini() != null) {
|
||||
nestedCount++;
|
||||
}
|
||||
if (param.getAlipayMini() != null) {
|
||||
nestedCount++;
|
||||
}
|
||||
if (param.getDyMini() != null) {
|
||||
nestedCount++;
|
||||
}
|
||||
if (nestedCount != 1) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.mobile_app.configRequired", platform.getCode());
|
||||
}
|
||||
|
||||
boolean match = switch (platform) {
|
||||
case WX_MINI -> param.getWxMini() != null;
|
||||
case ALIPAY_MINI -> param.getAlipayMini() != null;
|
||||
case DY_MINI -> param.getDyMini() != null;
|
||||
default -> false;
|
||||
};
|
||||
if (!match) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.mobile_app.configPlatformMismatch", platform.getCode());
|
||||
}
|
||||
return platform;
|
||||
}
|
||||
|
||||
/// 按平台合并并序列化 app_config JSON
|
||||
private String buildAppConfigJson(MobileAppParam param, MobilePlatformEnum platform, String oldJson) {
|
||||
return switch (platform) {
|
||||
case WX_MINI -> {
|
||||
WxMiniAppConfig merged = mergeWx(oldJson, param.getWxMini());
|
||||
validateWxForCreate(merged, oldJson);
|
||||
yield JacksonUtil.toJson(merged);
|
||||
}
|
||||
case ALIPAY_MINI -> {
|
||||
AlipayMiniAppConfig merged = mergeAlipay(oldJson, param.getAlipayMini());
|
||||
validateAlipayForCreate(merged, oldJson);
|
||||
yield JacksonUtil.toJson(merged);
|
||||
}
|
||||
case DY_MINI -> {
|
||||
DyMiniAppConfig merged = mergeDy(oldJson, param.getDyMini());
|
||||
validateDyForCreate(merged, oldJson);
|
||||
yield JacksonUtil.toJson(merged);
|
||||
}
|
||||
default -> throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.mobile_app.configPlatformMismatch", platform.getCode());
|
||||
};
|
||||
}
|
||||
|
||||
private WxMiniAppConfig mergeWx(String oldJson, WxMiniAppConfigParam param) {
|
||||
WxMiniAppConfig config = parseConfigOrNew(oldJson, WxMiniAppConfig.class);
|
||||
// appId 必填, 总是覆盖
|
||||
config.setAppId(param.getAppId());
|
||||
if (StrUtil.isNotBlank(param.getAppSecret())) {
|
||||
config.setAppSecret(param.getAppSecret());
|
||||
}
|
||||
// originalId 允许传空串清空
|
||||
if (param.getOriginalId() != null) {
|
||||
config.setOriginalId(param.getOriginalId());
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private AlipayMiniAppConfig mergeAlipay(String oldJson, AlipayMiniAppConfigParam param) {
|
||||
AlipayMiniAppConfig config = parseConfigOrNew(oldJson, AlipayMiniAppConfig.class);
|
||||
config.setAppId(param.getAppId());
|
||||
config.setAuthType(param.getAuthType());
|
||||
if (StrUtil.isNotBlank(param.getPrivateKey())) {
|
||||
config.setPrivateKey(param.getPrivateKey());
|
||||
}
|
||||
if (StrUtil.isNotBlank(param.getAlipayPublicKey())) {
|
||||
config.setAlipayPublicKey(param.getAlipayPublicKey());
|
||||
}
|
||||
if (StrUtil.isNotBlank(param.getAppCert())) {
|
||||
config.setAppCert(param.getAppCert());
|
||||
}
|
||||
if (StrUtil.isNotBlank(param.getAlipayCert())) {
|
||||
config.setAlipayCert(param.getAlipayCert());
|
||||
}
|
||||
if (StrUtil.isNotBlank(param.getAlipayRootCert())) {
|
||||
config.setAlipayRootCert(param.getAlipayRootCert());
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private DyMiniAppConfig mergeDy(String oldJson, DyMiniAppConfigParam param) {
|
||||
DyMiniAppConfig config = parseConfigOrNew(oldJson, DyMiniAppConfig.class);
|
||||
config.setAppId(param.getAppId());
|
||||
if (StrUtil.isNotBlank(param.getAppSecret())) {
|
||||
config.setAppSecret(param.getAppSecret());
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/// 新建时强制敏感材料齐全
|
||||
private void validateWxForCreate(WxMiniAppConfig config, String oldJson) {
|
||||
if (StrUtil.isNotBlank(oldJson)) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(config.getAppSecret())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"validation.field.appSecret.notBlank");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDyForCreate(DyMiniAppConfig config, String oldJson) {
|
||||
if (StrUtil.isNotBlank(oldJson)) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(config.getAppSecret())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"validation.field.appSecret.notBlank");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAlipayForCreate(AlipayMiniAppConfig config, String oldJson) {
|
||||
if (StrUtil.isNotBlank(oldJson)) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(config.getPrivateKey())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"validation.field.privateKey.notBlank");
|
||||
}
|
||||
AlipayAuthTypeEnum authType = AlipayAuthTypeEnum.fromCode(config.getAuthType());
|
||||
if (authType.isCert()) {
|
||||
if (StrUtil.isBlank(config.getAppCert())
|
||||
|| StrUtil.isBlank(config.getAlipayCert())
|
||||
|| StrUtil.isBlank(config.getAlipayRootCert())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.mobile_app.alipayCertRequired");
|
||||
}
|
||||
} else if (StrUtil.isBlank(config.getAlipayPublicKey())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"validation.field.alipayPublicKey.notBlank");
|
||||
}
|
||||
}
|
||||
|
||||
/// 实体 → 脱敏结果(填充对应嵌套配置)
|
||||
private MobileAppResult toMaskedResult(MobileApp entity) {
|
||||
MobileAppResult result = entity.toResult();
|
||||
result.setAppConfig(maskSensitiveJson(result.getAppConfig()));
|
||||
MobileAppResult result = MobileAppConvert.CONVERT.toResult(entity);
|
||||
String json = entity.getAppConfig();
|
||||
if (StrUtil.isBlank(json) || StrUtil.isBlank(entity.getPlatform())) {
|
||||
return result;
|
||||
}
|
||||
MobilePlatformEnum platform;
|
||||
try {
|
||||
platform = MobilePlatformEnum.findByCode(entity.getPlatform());
|
||||
} catch (Exception e) {
|
||||
return result;
|
||||
}
|
||||
try {
|
||||
switch (platform) {
|
||||
case WX_MINI -> result.setWxMini(
|
||||
MobileAppConfigConvert.CONVERT.toResult(parseConfig(json, WxMiniAppConfig.class)));
|
||||
case ALIPAY_MINI -> result.setAlipayMini(
|
||||
MobileAppConfigConvert.CONVERT.toResult(parseConfig(json, AlipayMiniAppConfig.class)));
|
||||
case DY_MINI -> result.setDyMini(
|
||||
MobileAppConfigConvert.CONVERT.toResult(parseConfig(json, DyMiniAppConfig.class)));
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("parse appConfig for result failed, platform={}: {}", entity.getPlatform(), e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 对 JSON 文本中的敏感键做脱敏(password 风格), 非 JSON 或空串原样返回
|
||||
private String maskSensitiveJson(String json) {
|
||||
private <T> T parseConfig(String json, Class<T> type) {
|
||||
if (StrUtil.isBlank(json)) {
|
||||
return json;
|
||||
throw new DataNotExistException("error.mobile_app.notExist");
|
||||
}
|
||||
try {
|
||||
Map<String, Object> map = JacksonUtil.toBean(json, new TypeReference<Map<String, Object>>() {});
|
||||
if (map == null || map.isEmpty()) {
|
||||
return json;
|
||||
}
|
||||
boolean changed = false;
|
||||
for (String key : SENSITIVE_KEYS) {
|
||||
Object val = map.get(key);
|
||||
if (val instanceof String s && StrUtil.isNotBlank(s)) {
|
||||
map.put(key, DesensitizedUtil.password(s));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed ? JacksonUtil.toJson(map) : json;
|
||||
} catch (Exception e) {
|
||||
log.warn("maskSensitiveJson parse failed, return raw: {}", e.getMessage());
|
||||
return json;
|
||||
T config = JacksonUtil.toBean(json, type);
|
||||
if (config == null) {
|
||||
throw new DataNotExistException("error.mobile_app.notExist");
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/// 合并新旧 app_config: 新值中敏感键为空/null 时沿用旧值
|
||||
private String mergeSensitiveJson(String oldJson, String newJson) {
|
||||
if (StrUtil.isBlank(newJson)) {
|
||||
// 前端未传则整段保留旧值
|
||||
return oldJson;
|
||||
private <T> T parseConfigOrNew(String json, Class<T> type) {
|
||||
if (StrUtil.isBlank(json)) {
|
||||
try {
|
||||
return type.getDeclaredConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("create config failed: " + type.getName(), e);
|
||||
}
|
||||
}
|
||||
if (StrUtil.isBlank(oldJson)) {
|
||||
return newJson;
|
||||
T config = JacksonUtil.toBean(json, type);
|
||||
if (config != null) {
|
||||
return config;
|
||||
}
|
||||
try {
|
||||
Map<String, Object> oldMap = JacksonUtil.toBean(oldJson, new TypeReference<Map<String, Object>>() {});
|
||||
Map<String, Object> newMap = JacksonUtil.toBean(newJson, new TypeReference<Map<String, Object>>() {});
|
||||
if (oldMap == null) {
|
||||
oldMap = new LinkedHashMap<>();
|
||||
}
|
||||
if (newMap == null) {
|
||||
return oldJson;
|
||||
}
|
||||
for (String key : SENSITIVE_KEYS) {
|
||||
Object newVal = newMap.get(key);
|
||||
if (newVal == null || (newVal instanceof String s && StrUtil.isBlank(s))) {
|
||||
Object oldVal = oldMap.get(key);
|
||||
if (oldVal != null) {
|
||||
newMap.put(key, oldVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
return JacksonUtil.toJson(newMap);
|
||||
return type.getDeclaredConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
log.warn("mergeSensitiveJson parse failed, use newJson: {}", e.getMessage());
|
||||
return newJson;
|
||||
throw new IllegalStateException("create config failed: " + type.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user