feat: 支付产品配置表 + 后端 PayProductConfig 全套 (entity/mapper/service/controller)

This commit is contained in:
bootx
2026-06-13 18:04:30 +08:00
parent ebe175653e
commit 5d120fb788
10 changed files with 378 additions and 3875 deletions

View File

@@ -0,0 +1,33 @@
-- 支付产品配置表 产品配置状态存储
DROP TABLE IF EXISTS pay_product_config;
CREATE TABLE pay_product_config (
id int8 NOT NULL,
product varchar(32) COLLATE pg_catalog.default NOT NULL,
channel varchar(32) COLLATE pg_catalog.default NOT NULL,
active_env varchar(32) COLLATE pg_catalog.default DEFAULT 'prod',
configured bool DEFAULT false,
remark varchar(255) COLLATE pg_catalog.default,
creator int8,
create_time timestamptz(6),
last_modifier int8,
last_modified_time timestamptz(6),
version int4 DEFAULT 0,
deleted bool NOT NULL DEFAULT false
);
COMMENT ON TABLE pay_product_config IS '支付产品配置';
COMMENT ON COLUMN pay_product_config.id IS '主键';
COMMENT ON COLUMN pay_product_config.product IS '产品编码';
COMMENT ON COLUMN pay_product_config.channel IS '通道编码';
COMMENT ON COLUMN pay_product_config.active_env IS '生效环境: prod/sandbox';
COMMENT ON COLUMN pay_product_config.configured IS '是否已配置参数';
COMMENT ON COLUMN pay_product_config.remark IS '备注';
COMMENT ON COLUMN pay_product_config.creator IS '创建者ID';
COMMENT ON COLUMN pay_product_config.create_time IS '创建时间';
COMMENT ON COLUMN pay_product_config.last_modifier IS '最后修改ID';
COMMENT ON COLUMN pay_product_config.last_modified_time IS '最后修改时间';
COMMENT ON COLUMN pay_product_config.version IS '版本号';
COMMENT ON COLUMN pay_product_config.deleted IS '删除标志';
ALTER TABLE pay_product_config ADD CONSTRAINT pk_pay_product_config PRIMARY KEY (id);
CREATE UNIQUE INDEX idx_ppc_product ON pay_product_config(product) WHERE deleted = false;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
package org.dromara.daxpay.payment.admin.controller.masterdata.product;
import org.dromara.daxpay.payment.pay.param.masterdata.product.PayProductConfigParam;
import org.dromara.daxpay.payment.pay.result.masterdata.product.PayProductConfigResult;
import org.dromara.daxpay.payment.pay.service.masterdata.product.PayProductConfigService;
import org.dromara.daxpay.platform.core.annotation.PermCode;
import org.dromara.daxpay.platform.core.rest.Res;
import org.dromara.daxpay.platform.core.rest.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/// # 支付产品配置管理
///
@PermCode(menuCode = "payment:product")
@Validated
@Tag(name = "支付产品配置管理")
@RestController
@RequestMapping("/admin/product-config")
@RequiredArgsConstructor
public class PayProductConfigController {
private final PayProductConfigService payProductConfigService;
@PermCode(code = "view", nameCn = "产品配置查看", nameEn = "Product Config View")
@Operation(summary = "全量查询产品配置列表(卡片页使用)")
@GetMapping("/list-all")
public Result<List<PayProductConfigResult>> listAll() {
return Res.ok(payProductConfigService.listAll());
}
@PermCode(code = "edit", nameCn = "产品配置编辑", nameEn = "Product Config Edit")
@Operation(summary = "切换产品生效环境")
@PostMapping("/switch-env")
public Result<Void> switchEnv(
@NotBlank(message = "{validation.field.product.notBlank}") String product,
@NotNull(message = "{validation.field.sandbox.notNull}") Boolean sandbox) {
payProductConfigService.switchEnv(product, sandbox);
return Res.ok();
}
@PermCode(code = "edit", nameCn = "产品配置编辑", nameEn = "Product Config Edit")
@Operation(summary = "保存产品配置")
@PostMapping("/save")
public Result<Void> save(@RequestBody @Validated PayProductConfigParam param) {
payProductConfigService.saveOrUpdate(param);
return Res.ok();
}
}

View File

@@ -0,0 +1,16 @@
package org.dromara.daxpay.payment.pay.convert.product;
import org.dromara.daxpay.payment.pay.entity.masterdata.product.PayProductConfig;
import org.dromara.daxpay.payment.pay.result.masterdata.product.PayProductConfigResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/// # 支付产品配置转换
///
@Mapper
public interface PayProductConfigConvert {
PayProductConfigConvert CONVERT = Mappers.getMapper(PayProductConfigConvert.class);
PayProductConfigResult toResult(PayProductConfig entity);
}

View File

@@ -0,0 +1,24 @@
package org.dromara.daxpay.payment.pay.dao.product;
import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager;
import org.dromara.daxpay.payment.pay.entity.masterdata.product.PayProductConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/// # 支付产品配置
///
@Slf4j
@Repository
@RequiredArgsConstructor
public class PayProductConfigManager extends BaseManager<PayProductConfigMapper, PayProductConfig> {
/// 根据产品编码查询
public Optional<PayProductConfig> findByProduct(String product) {
return lambdaQuery()
.eq(PayProductConfig::getProduct, product)
.oneOpt();
}
}

View File

@@ -0,0 +1,11 @@
package org.dromara.daxpay.payment.pay.dao.product;
import org.dromara.daxpay.payment.pay.entity.masterdata.product.PayProductConfig;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 支付产品配置
///
@Mapper
public interface PayProductConfigMapper extends MPJBaseMapper<PayProductConfig> {
}

View File

@@ -0,0 +1,39 @@
package org.dromara.daxpay.payment.pay.entity.masterdata.product;
import org.dromara.daxpay.platform.common.mybatisplus.base.MpBaseEntity;
import org.dromara.daxpay.platform.common.mybatisplus.function.ToResult;
import org.dromara.daxpay.payment.pay.convert.product.PayProductConfigConvert;
import org.dromara.daxpay.payment.pay.result.masterdata.product.PayProductConfigResult;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 支付产品配置
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName(value = "pay_product_config", autoResultMap = true)
public class PayProductConfig extends MpBaseEntity implements ToResult<PayProductConfigResult> {
/// 产品编码
private String product;
/// 通道编码
private String channel;
/// 生效环境: prod/sandbox
private String activeEnv;
/// 是否已配置参数
private boolean configured;
/// 备注
private String remark;
@Override
public PayProductConfigResult toResult() {
return PayProductConfigConvert.CONVERT.toResult(this);
}
}

View File

@@ -0,0 +1,31 @@
package org.dromara.daxpay.payment.pay.param.masterdata.product;
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 PayProductConfigParam {
@NotBlank(message = "{validation.field.product.notBlank}")
@Schema(description = "产品编码")
private String product;
@NotBlank(message = "{validation.field.channel.notBlank}")
@Schema(description = "通道编码")
private String channel;
@Schema(description = "生效环境: prod/sandbox")
private String activeEnv;
@Schema(description = "是否已配置参数")
private boolean configured;
@Schema(description = "备注")
private String remark;
}

View File

@@ -0,0 +1,37 @@
package org.dromara.daxpay.payment.pay.result.masterdata.product;
import org.dromara.daxpay.platform.core.result.BaseResult;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 支付产品配置结果
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "支付产品配置结果")
public class PayProductConfigResult extends BaseResult {
@Schema(description = "产品编码")
private String product;
@Schema(description = "产品名称")
private String name;
@Schema(description = "通道编码")
private String channel;
@Schema(description = "通道名称")
private String channelName;
@Schema(description = "是否支持沙箱环境")
private boolean sandboxSupport;
@Schema(description = "生效环境: prod/sandbox")
private String activeEnv;
@Schema(description = "是否已配置参数")
private boolean configured;
}

View File

@@ -0,0 +1,132 @@
package org.dromara.daxpay.payment.pay.service.masterdata.product;
import org.dromara.daxpay.payment.common.util.PaymentStrategyFactory;
import org.dromara.daxpay.payment.pay.dao.product.PayProductConfigManager;
import org.dromara.daxpay.payment.pay.entity.masterdata.product.PayProductConfig;
import org.dromara.daxpay.payment.pay.param.masterdata.product.PayProductConfigParam;
import org.dromara.daxpay.payment.pay.result.masterdata.product.PayProductConfigResult;
import org.dromara.daxpay.payment.pay.strategy.AbsProductStrategy;
import org.dromara.daxpay.platform.common.i18n.util.I18nUtil;
import org.dromara.daxpay.platform.core.enums.pay.channel.ChannelEnum;
import org.dromara.daxpay.platform.core.enums.pay.channel.ProductEnum;
import org.dromara.daxpay.platform.core.enums.pay.config.PayEnvEnum;
import org.dromara.daxpay.platform.core.exception.DataNotExistException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/// # 支付产品配置服务
///
@Slf4j
@Service
@RequiredArgsConstructor
public class PayProductConfigService {
private final PayProductConfigManager payProductConfigManager;
/// 查询全部产品配置列表(卡片页使用)
/// 融合 ProductEnum + pay_product_config 表 + 策略信息
public List<PayProductConfigResult> listAll() {
Map<String, PayProductConfig> configMap = payProductConfigManager.lambdaQuery().list().stream()
.collect(Collectors.toMap(PayProductConfig::getProduct, c -> c, (a, b) -> a));
return Arrays.stream(ProductEnum.values())
.map(product -> toConfigResult(product, configMap))
.sorted(Comparator.comparing(PayProductConfigResult::getId,
Comparator.nullsLast(Comparator.naturalOrder())))
.toList();
}
/// 切换产品的生效环境
@Transactional(rollbackFor = Exception.class)
public void switchEnv(String product, boolean sandbox) {
PayProductConfig config = payProductConfigManager.findByProduct(product)
.orElseGet(() -> createDefaultConfig(product));
if (sandbox) {
config.setActiveEnv(PayEnvEnum.SANDBOX.getCode());
} else {
config.setActiveEnv(PayEnvEnum.PROD.getCode());
}
payProductConfigManager.saveOrUpdate(config);
}
/// 保存或更新配置
@Transactional(rollbackFor = Exception.class)
public void saveOrUpdate(PayProductConfigParam param) {
PayProductConfig config = payProductConfigManager.findByProduct(param.getProduct())
.orElseGet(PayProductConfig::new);
config.setProduct(param.getProduct());
config.setChannel(param.getChannel());
config.setActiveEnv(param.getActiveEnv() != null ? param.getActiveEnv() : PayEnvEnum.PROD.getCode());
config.setConfigured(param.isConfigured());
config.setRemark(param.getRemark());
payProductConfigManager.saveOrUpdate(config);
}
/// 更新配置状态
@Transactional(rollbackFor = Exception.class)
public void markConfigured(String product, boolean configured) {
PayProductConfig config = payProductConfigManager.findByProduct(product)
.orElseGet(() -> createDefaultConfig(product));
config.setConfigured(configured);
payProductConfigManager.saveOrUpdate(config);
}
/// 枚举 + 库表 + 策略合并为配置结果
private PayProductConfigResult toConfigResult(ProductEnum product, Map<String, PayProductConfig> configMap) {
PayProductConfigResult result = new PayProductConfigResult()
.setProduct(product.getCode())
.setName(I18nUtil.getEnumName(product))
.setChannel(product.getChannel())
.setChannelName(I18nUtil.getEnumName(ChannelEnum.findByCode(product.getChannel())));
AbsProductStrategy strategy = resolveStrategy(product.getCode());
if (strategy != null) {
result.setSandboxSupport(strategy.isSandbox());
}
PayProductConfig config = configMap.get(product.getCode());
if (config != null) {
result.setId(config.getId());
result.setActiveEnv(config.getActiveEnv());
result.setConfigured(config.isConfigured());
} else {
result.setActiveEnv(PayEnvEnum.PROD.getCode());
result.setConfigured(false);
}
return result;
}
/// 创建默认配置记录
private PayProductConfig createDefaultConfig(String product) {
ProductEnum productEnum = ProductEnum.findByCode(product);
if (productEnum == null) {
throw new DataNotExistException("error.payment.product.notExist");
}
PayProductConfig config = new PayProductConfig();
config.setProduct(product);
config.setChannel(productEnum.getChannel());
config.setActiveEnv(PayEnvEnum.PROD.getCode());
config.setConfigured(false);
payProductConfigManager.save(config);
return config;
}
private AbsProductStrategy resolveStrategy(String productCode) {
if (!PaymentStrategyFactory.existsByProduct(productCode, AbsProductStrategy.class)) {
return null;
}
return PaymentStrategyFactory.createByProduct(productCode, AbsProductStrategy.class);
}
}