文章目录
- 前言
- 一、参数校验
- 1. MessageDescription内容
- 2. 参数验证执行器
- 3. 字段验证处理器
- 4. 验证器工厂
- 5. style校验器
- 6. 验证器接口
- 7. 长度校验器
- 8. 枚举类型校验器
- 9. yml配置
- 9.1 deduct.yml
- 9.2 style.yml
- 二、幂等校验
- 总结
前言
在《支付系统设计三:渠道网关设计02-客户端报文解析》之后需要对上送参数进行校验、交易幂等校验。
本篇将讲解paygw对渠道(内部渠道paycore)请求参数的校验,即MessageDescription中datas值的校验。即如何对Map中的值进行校验:是否存在某值,即是否存在某key,对应的值是否合法,即value值长度、类型等是否合法。
fieldValidateExecutor.validate(transCode, messageDescription.getDatas());
一、参数校验
1. MessageDescription内容
经过客户端报文解析处理后paycore上送的报文内容经过解析脚本处理被解析填充到MessageDescription中的datas属性中,参数校验即根据上送的transCode获取到校验器对datas中的参数进行校验,是否满足接口定义。
交易时paycore请求paygw上送报文如下,下面要对header和body中的字段进行验证。
{
"header": {
"channelCode": "paycore",
"transCode": "deduct",
"channelDateTime": "20230510221050",
"channelTransNo": "2023051000001"
},
"body": {
"deptCode": "0308",
"transAmount": 100,
"acctNo": "622002324354354645665",
"acctName": "张三",
"mediaType": "01",
"certType": "00",
"certNo": "320390199909091234",
"purpose": "代扣还款",
"bizOrderNo": "20230510274235345001",
"systemId": "1010",
"productCode": "001",
"dcFlag":"D",
"orgId":"123"
}
}
2. 参数验证执行器
/**
* @author Kkk
* @Describe: 参数验证执行器
* 后台管理系统接口:transCode = transCode + transType
* 运行时接口:transCode = transCode
*/
@Component
public class FieldValidateExecutor {
/**
* 字段验证处理器
*/
@Autowired
private FieldValidateYamlProcessor fieldValidateYamlProcessor;
/**
* 参数校验
* @param transCode
* @param data
*/
public void validate(String transCode, Map<String, Object> data) {
Map<String, List<FieldPolicy>> fieldPolicyListMap = this.fieldValidateYamlProcessor.getFieldPolicyListMap();
if (!CollectionUtils.isEmpty(fieldPolicyListMap)) {
List<FieldPolicy> fieldPolicyList = fieldPolicyListMap.get(transCode);
if (!CollectionUtils.isEmpty(fieldPolicyList)) {
for (FieldPolicy fieldPolicy : fieldPolicyList) {
fieldPolicy.validate(StringUtils.valueOf(data.get(fieldPolicy.getField())));
}
}
}
}
}
3. 字段验证处理器
/**
* @author Kkk
* @Describe: 字段验证处理器
*/
@Component
public class FieldValidateYamlProcessor extends YamlProcessor implements InitializingBean {
/**
* 字段验证yaml文件路径配置
*/
private static final String FIELD_VALIDATE_YAML_CONFIG_PATH = "field.validate.yaml.config.path";
/**
* 应用环境
*/
@Autowired
private Environment env;
/**
* style校验器缓存(key为styleCode,value为Style对象)
*/
private Map<String, Style> styleMap;
/**
* 字段策略缓存(key为交易码/交易码+交易类型,value为field策略列表)
*/
private Map<String, List<FieldPolicy>> fieldPolicyListMap;
private static final String STYLE = "style";
private static final String FIELD = "field";
private static final String FIELD_NAME = "fieldName";
private static final String TYPE = "type";
private static final String OPTION = "option";
@Override
public void afterPropertiesSet() throws InvocationTargetException, IllegalAccessException, IOException {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(env.getProperty(FIELD_VALIDATE_YAML_CONFIG_PATH));
super.setResources(resources);
Map<String, Object> yamlMap = createMap();//yaml配置
this.styleMap = initStyleMap(yamlMap);
this.fieldPolicyListMap = initFieldPolicyListMap(yamlMap);
}
/**
* 初始化style
* @param yamlMap
* @return
*/
private Map<String, Style> initStyleMap(Map<String, Object> yamlMap) {
Map<String, Style> returnMap = new HashMap<String, Style>();
if (!CollectionUtils.isEmpty(yamlMap)) {
Map<String, Map<String, Object>> styleMap = (Map<String, Map<String, Object>>) yamlMap.get(STYLE);
if (!CollectionUtils.isEmpty(styleMap)) {
for (Map.Entry<String, Map<String, Object>> entry : styleMap.entrySet()) {
returnMap.put(entry.getKey(), initStyle(entry.getValue()));
}
}
}
return returnMap;
}
/**
* 初始化style
* @param validateMap
* @return
*/
private Style initStyle(Map<String, Object> validateMap) {
Map<String, Validate> validateMapWithStyle = initValidateMap(validateMap);
Style style = new Style();
style.setValidateMap(validateMapWithStyle);
return style;
}
/**
* @Description 初始化字段策略缓存
* @Params
* @Return
* @Exceptions
*/
private Map<String, List<FieldPolicy>> initFieldPolicyListMap(Map<String, Object> yamlMap) throws InvocationTargetException, IllegalAccessException {
Map<String, List<FieldPolicy>> fieldPolicyListMap = new LinkedHashMap<String, List<FieldPolicy>>();
if (!CollectionUtils.isEmpty(yamlMap)) {
for (Map.Entry<String, Object> entry : yamlMap.entrySet()) {
if (StringUtils.equals(STYLE, entry.getKey())) {
continue;
}
List<Map<String, Object>> fieldMapList = (List<Map<String, Object>>) entry.getValue();//交易字段列表
if (!CollectionUtils.isEmpty(fieldMapList)) {
List<FieldPolicy> fieldPolicyList = new ArrayList<FieldPolicy>();
for (Map<String, Object> fieldMap : fieldMapList) {
String field = StringUtils.valueOf(fieldMap.get(FIELD));
String fieldName = StringUtils.valueOf(fieldMap.get(FIELD_NAME));
Style style = this.styleMap.get(StringUtils.valueOf(fieldMap.get(STYLE)));
Map<String, Validate> extendValidatorMap = getExtendValidatorMap(fieldMap);
fieldPolicyList.add(new FieldPolicy(field, fieldName, style, extendValidatorMap));
}
fieldPolicyListMap.put(entry.getKey(), fieldPolicyList);
}
}
}
return fieldPolicyListMap;
}
/**
* 获取扩展验证器
*
* @param fieldMap
* @return
*/
private Map<String, Validate> getExtendValidatorMap(Map<String, Object> fieldMap) {
Map<String, Object> map = MapUtils.paraFilter(fieldMap, FIELD, FIELD_NAME, STYLE);//扩展验证器
return initValidateMap(map);
}
/**
* 初始化校验器
* @param map
* @return
*/
private Map<String, Validate> initValidateMap(Map<String, Object> map) {
Map<String, Validate> extendValidateMap = new LinkedHashMap<String, Validate>();
if (!CollectionUtils.isEmpty(map)) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String validateName = entry.getKey();
if (StringUtils.equals(TYPE, validateName)) {
validateName = StringUtils.valueOf(entry.getValue());
}
Validate validate = ValidatorFactory.getValidator(validateName).init(StringUtils.valueOf(entry.getValue()));
extendValidateMap.put(entry.getKey(), validate);
}
}
return extendValidateMap;
}
/**
* 解析yaml为map
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
private Map<String, Object> createMap() throws InvocationTargetException, IllegalAccessException {
final Map<String, Object> result = new LinkedHashMap<String, Object>();
process(new MatchCallback() {
@Override
public void process(Properties properties, Map<String, Object> map) {
merge(result, map);
}
});
return result;
}
/**
* 合并yaml中相同的key
* @param output
* @param map
*/
private void merge(Map<String, Object> output, Map<String, Object> map) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
Object existing = output.get(key);
if (value instanceof Map && existing instanceof Map) {
Map<String, Object> result = new LinkedHashMap<String, Object>((Map) existing);
merge(result, (Map) value);
output.put(key, result);
} else {
output.put(key, value);
}
}
}
/**
* 获取style
* @param styleName
* @return
*/
public Style getStyle(String styleName) {
if (CollectionUtils.isEmpty(this.styleMap)) {
return null;
}
return this.styleMap.get(styleName);
}
public Map<String, Style> getStyleMap() {
return styleMap;
}
public void setStyleMap(Map<String, Style> styleMap) {
this.styleMap = styleMap;
}
public Map<String, List<FieldPolicy>> getFieldPolicyListMap() {
return fieldPolicyListMap;
}
public void setFieldPolicyListMap(Map<String, List<FieldPolicy>> fieldPolicyListMap) {
this.fieldPolicyListMap = fieldPolicyListMap;
}
}
4. 验证器工厂
/**
* @author Kkk
* @Describe: 参数/字段验证器工厂
*/
public class ValidatorFactory {
/**
* 校验器容器
*/
private static Map<String, Class<? extends Validate>> validatorMap = new HashMap<String, Class<? extends Validate>>();
static {
ValidatorFactory.regist("option", OptionValidator.class);
ValidatorFactory.regist("length", LengthValidator.class);
ValidatorFactory.regist("amount", AmountValidator.class);
ValidatorFactory.regist("date", DateValidator.class);
ValidatorFactory.regist("regex", RegexValidator.class);
ValidatorFactory.regist("enums", EnumValidator.class);
}
private ValidatorFactory() {
super();
}
/**
* 注册校验器
* @param validatorName
* @param validateClass
*/
public static void regist(String validatorName, Class<? extends Validate> validateClass) {
validatorMap.put(validatorName, validateClass);
}
/**
* 获取校验器
* @param validatorName
* @return
*/
public static Validate getValidator(String validatorName) {
try {
return validatorMap.get(validatorName).newInstance();
} catch (Exception e) {
throw new PayGwException(SystemErrorCode.SYSTEM_ERROR);
}
}
}
5. style校验器
/**
* @author Kkk
* @Describe: style校验器
*/
public class Style {
private Map<String, Validate> validateMap;
/**
* style校验
* @param field
* @param fieldName
* @param value
*/
public void validate(String field, String fieldName, String value) {
if (!CollectionUtils.isEmpty(this.validateMap)) {
for (Map.Entry<String, Validate> entry : this.validateMap.entrySet()) {
Validate validator = entry.getValue();
if (!validator.validate(value)) {
throw new PayCoreException(validator.getMessage(), new Object[]{fieldName});
}
}
}
}
public Map<String, Validate> getValidateMap() {
return validateMap;
}
public void setValidateMap(Map<String, Validate> validateMap) {
this.validateMap = validateMap;
}
}
6. 验证器接口
/**
* @author Kkk
* @Describe: 参数/字段验证器接口
*/
public interface Validate {
/**
* 初始化验证器
* @param value
* @return
*/
Validate init(String value);
/**
* 参数/字段验证
*
* @param value 参数值
* @return true:验证通过 false:验证未通过
*/
boolean validate(String value);
/**
* 获取提示信息
* @return
*/
SystemErrorCode getMessage();
}
7. 长度校验器
/**
* @author Kkk
* @Describe: 长度校验器
*/
public class LengthValidator extends AbstractValidator {
/**
* 长度
*/
private String length;
@Override
public boolean validate(String value) {
return StringUtils.valueOf(value).matches("[\\s\\S]{" + length + "}");
}
@Override
public SystemErrorCode getMessage() {
return SystemErrorCode.LENGTH_VALIDATE_ERROR;
}
@Override
public Validate init(String value) {
this.length = value;
return this;
}
}
8. 枚举类型校验器
在com.kkk.common.enums路径下定义接口参数枚举类,对接口上送的参数是否符合枚举值进行校验。
/**
* @author Kkk
* @Describe: 枚举类型校验器
*/
public class EnumValidator extends TypeValidator {
private static final Logger logger = LoggerFactory.getLogger(EnumValidator.class);
private String enumPackge = "com.kkk.common.enums.";
private Class enumClass;
/**
* 参数/字段验证
* @param value 参数值
* @return true:验证通过 false:验证未通过
*/
@Override
public boolean validate(String value) {
try {
Object[] enums = enumClass.getEnumConstants();
Method targetMethod = enumClass.getDeclaredMethod("getCode");
for (Object enumObj : enums) {
if (StringUtils.equalsIgnoreCase(value, targetMethod.invoke(enumObj).toString())) {
return true;
}
}
} catch (Exception e) {
logger.error("枚举验证不通过{}", value,e);
}
return false;
}
/**
* 获取提示信息
* @return
*/
@Override
public SystemErrorCode getMessage() {
return SystemErrorCode.TYPE_VALIDATE_ERROR;
}
/**
* 初始化验证器
* @param value
* @return
*/
@Override
public Validate init(String value) {
try {
enumClass = Class.forName(enumPackge + value);
} catch (ClassNotFoundException e) {
logger.error("枚举验证器初始化失败{}", value,e);
}
return this;
}
}
9. yml配置
在field.validate.yaml.config.path路径下配置yml进行参数校验:
9.1 deduct.yml
#单笔代扣
deduct:
- field: acctNo
fieldName: 账号
option: false
style: acctNoStyle
- field: acctName
fieldName: 账户名称
style: acctNameStyle
- field: deptCode
fieldName: 开户机构编码
option: false
style: deptCodeStyle
- field: transAmount
fieldName: 交易金额
option: false
style: transAmountStyle
- field: certType
fieldName: 证件类型
style: certTypeStyle
- field: certNo
fieldName: 证件号码
style: certNoStyle
- field: mediaType
fieldName: 介质类型
style: mediaTypeStyle
- field: provinceCode
fieldName: 省编码
style: areaCodeStyle
- field: provinceName
fieldName: 省名称
style: areaNameStyle
- field: cityCode
fieldName: 市编码
style: areaCodeStyle
- field: cityName
fieldName: 市名称
style: areaNameStyle
- field: goodsName
fieldName: 商品名称
style: goodsNameStyle
- field: notifyKey
fieldName: 异步通知地址
style: urlStyle
- field: instCode
fieldName: 支付渠道编码
style: instCodeStyle
- field: productCode
fieldName: 产品码
option: false
style: productCodeStyle
- field: contractNo
fieldName: 合同号
style: contractNoStyle
- field: purpose
fieldName: 用途
style: purposeStyle
- field: remark
fieldName: 备注
style: remarkStyle
deductQry:
- field: channelTransNo
fieldName: 支付系统流水号
option: false
style: transNoStyle
9.2 style.yml
#此文件中定义字段校验style,在交易参数配置中引用相应的style进行校验
style:
acctNoStyle:
length: 1,32
regex: ^[\w\-\.]{1,32}|(\w)+(\.\w+)*@(\w)+((\.\w+)+)$
acctNameStyle:
length: 1,64
regex: ^[\w\-\.\u00b7\u2022\uff08\uff09\u0028\u0029\u201C\u201D\u4E00-\u9FA5]{1,64}$
deptCodeStyle:
length: 1,16
regex: ^[\w\-]{1,16}$
certTypeStyle:
length: 2,2
regex: ^\d{2,2}$
certNoStyle:
length: 1,32
regex: ^\w{1,32}$
dcFlagStyle:
length: 1,1
regex: ^D|C$
二、幂等校验
/**
* redis幂等性校验(交易码+业务渠道+业务渠道流水号)
* @param transCode
* @param channelCode
* @param channelTransNo
*/
public void uniqueCheck(String transCode, String channelCode, String channelTransNo) {
//1、获取redisKey
String redisKey = transCode + channelCode + channelTransNo;
//2、使用redis进行唯一性判断;
boolean isExist = false;
try {
isExist = redisService.exists(redisKey);
} catch (Exception e) {
LoggerUtil.error(logger, "幂等性校验-交易码({})-支付系统编码({})-支付系统流水号({})-redis幂等性校验异常", transCode, channelCode, channelTransNo, e);
}
if (isExist) {
LoggerUtil.warn(logger, "幂等性校验-redisKey({})已经存在-value({})", redisKey, redisService.getByKey(redisKey));
throw new PayGwException(SystemErrorCode.HAS_EXIST, new Object[]{"支付系统流水号(" + channelTransNo + ")"});
}
}