提交
This commit is contained in:
118
jeepay-components/jeepay-components-bizcommons/pom.xml
Normal file
118
jeepay-components/jeepay-components-bizcommons/pom.xml
Normal file
@@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion> <!-- POM模型版本 -->
|
||||
|
||||
<groupId>com.jeequan</groupId> <!-- 组织名, 类似于包名 -->
|
||||
<artifactId>jeepay-components-bizcommons</artifactId> <!-- 项目名称 -->
|
||||
<packaging>jar</packaging> <!-- 项目的最终打包类型/发布形式, 可选[jar, war, pom, maven-plugin]等 -->
|
||||
<version>${isys.version}</version> <!-- 项目当前版本号 -->
|
||||
<description>Jeepay计全支付系统 [jeepay-components-oss]</description> <!-- 项目描述 -->
|
||||
<url>https://www.jeequan.com</url>
|
||||
|
||||
<parent>
|
||||
<groupId>com.jeequan</groupId>
|
||||
<artifactId>jeepay-components</artifactId>
|
||||
<version>Final</version>
|
||||
</parent>
|
||||
|
||||
<!-- 项目属性 -->
|
||||
<properties>
|
||||
<projectRootDir>${basedir}/../../</projectRootDir>
|
||||
</properties>
|
||||
|
||||
<!-- 项目依赖声明 -->
|
||||
<dependencies>
|
||||
|
||||
<!-- 依赖[ service ]包, 会自动传递依赖[ core ]包。 -->
|
||||
<dependency>
|
||||
<groupId>com.jeequan</groupId>
|
||||
<artifactId>jeepay-components-db</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 依赖[ oss ]包 -->
|
||||
<dependency>
|
||||
<groupId>com.jeequan</groupId>
|
||||
<artifactId>jeepay-components-oss</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 添加 spring-webmvc 基础依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<scope>provided</scope> <!-- 仅编译依赖该jar, 运行时存在 -->
|
||||
</dependency>
|
||||
|
||||
<!-- slf4j -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- spring-boot 相关注解 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot</artifactId>
|
||||
<scope>provided</scope> <!-- 仅编译依赖该jar, 运行时存在 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<scope>provided</scope> <!-- 仅编译依赖该jar, 运行时存在 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<scope>provided</scope> <!-- 仅编译依赖该jar, 运行时存在 -->
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里云oss组件 -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun.oss</groupId>
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
<scope>compile</scope> <!-- 当对象存储使用aliyunOSS时,需要改为:compile, 否则使用provided仅用于编译代码 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope> <!-- 仅编译依赖该jar, 运行时存在 -->
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 生成二维码工具包 zxing -->
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>javase</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/java</directory>
|
||||
<includes><include>**/*.xml</include></includes><!-- maven可以将mapper.xml进行打包处理,否则仅对java文件处理 -->
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,157 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.jeequan.jeepay.core.constants.ApiCodeEnum;
|
||||
import com.jeequan.jeepay.core.constants.CS;
|
||||
import com.jeequan.jeepay.core.model.ApiRes;
|
||||
import com.jeequan.jeepay.db.entity.*;
|
||||
import com.jeequan.jeepay.service.impl.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 商户通道配置信息
|
||||
*
|
||||
* @author terrfly
|
||||
* @date 2022/3/30 16:27
|
||||
*/
|
||||
@Component
|
||||
public class MchPayPassageConfigManage {
|
||||
|
||||
@Autowired
|
||||
private MchPayPassageService mchPayPassageService;
|
||||
@Autowired
|
||||
private PayWayService payWayService;
|
||||
@Autowired
|
||||
private MchInfoService mchInfoService;
|
||||
@Autowired
|
||||
private MchAppService mchAppService;
|
||||
@Autowired
|
||||
private RateConfigService rateConfigService;
|
||||
@Autowired
|
||||
private PayInterfaceConfigService payInterfaceConfigService;
|
||||
@Autowired
|
||||
private PayInterfaceDefineService payInterfaceDefineService;
|
||||
|
||||
/**
|
||||
* 支付方式 <--> 通道配置
|
||||
* 左侧列表(支付方式)
|
||||
**/
|
||||
public ApiRes queryPaywayList(String appId, String wayCode, String wayName, String wayType, String unionSearchId, IPage page) {
|
||||
|
||||
//支付方式集合
|
||||
LambdaQueryWrapper<PayWay> wrapper = PayWay.gw();
|
||||
wrapper.eq(StrUtil.isNotBlank(wayType), PayWay::getWayType, wayType);
|
||||
wrapper.eq(StrUtil.isNotBlank(wayCode), PayWay::getWayCode, wayCode);
|
||||
wrapper.like(StrUtil.isNotBlank(wayName), PayWay::getWayName, wayName);
|
||||
|
||||
// app模糊搜索
|
||||
if (StringUtils.isNotBlank(unionSearchId)) {
|
||||
wrapper.and(i -> {
|
||||
i.like(PayWay::getWayCode, unionSearchId)
|
||||
.or().like(PayWay::getWayName, unionSearchId);
|
||||
});
|
||||
}
|
||||
|
||||
// 查询分页数据
|
||||
IPage<PayWay> result = payWayService.page(page, wrapper);
|
||||
|
||||
// 无数据
|
||||
if (result.getRecords().isEmpty()) {
|
||||
return ApiRes.page(result);
|
||||
}
|
||||
|
||||
// 全部的支付方式
|
||||
Set<String> paywayCodeList = new HashSet<>();
|
||||
result.getRecords().stream().forEach(r -> paywayCodeList.add(r.getWayCode()));
|
||||
|
||||
// 查询所有的支付方式, 当前已开通的
|
||||
Map<String, Byte> paywayCodeStateMap = new HashMap<>();
|
||||
|
||||
// 查询当前商户已经开通的数据
|
||||
mchPayPassageService.list(MchPayPassage.gw()
|
||||
.select(MchPayPassage::getWayCode, MchPayPassage::getState)
|
||||
.eq(MchPayPassage::getAppId, appId)
|
||||
.eq(MchPayPassage::getState, CS.YES)
|
||||
.in(MchPayPassage::getWayCode, paywayCodeList)).stream().forEach(r -> {
|
||||
paywayCodeStateMap.put(r.getWayCode(), CS.YES);
|
||||
});
|
||||
|
||||
result.getRecords().stream().forEach(r -> {
|
||||
// 是否已经配置
|
||||
r.addExt("isConfig", paywayCodeStateMap.get(r.getWayCode()) != null ? CS.YES : CS.NO);
|
||||
});
|
||||
|
||||
return ApiRes.page(result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 支付方式 <--> 通道配置
|
||||
* 右侧列表(可用的支付接口)
|
||||
**/
|
||||
public ApiRes availablePayInterface(String appId, String wayCode) {
|
||||
|
||||
MchAppEntity mchAppEntity = mchAppService.getById(appId);
|
||||
if (mchAppEntity == null || mchAppEntity.getState() != CS.YES) {
|
||||
return ApiRes.fail(ApiCodeEnum.SYS_OPERATION_FAIL_SEARCH);
|
||||
}
|
||||
|
||||
MchInfo mchInfo = mchInfoService.getById(mchAppEntity.getMchNo());
|
||||
if (mchInfo == null || mchInfo.getState() != CS.YES) {
|
||||
return ApiRes.fail(ApiCodeEnum.SYS_OPERATION_FAIL_SEARCH);
|
||||
}
|
||||
|
||||
Set<String> ifCodes = new HashSet<>();
|
||||
|
||||
// 查询出 商户配置过费率的支付接口
|
||||
Map<String, RateConfig> rateConfigMap = new HashMap<>();
|
||||
rateConfigService.list(
|
||||
RateConfig.gw()
|
||||
.eq(RateConfig::getInfoId, RateConfig.appendInfoByMchApp(appId))
|
||||
.eq(RateConfig::getInfoType, CS.SYS_ROLE_TYPE.MCH_APP)
|
||||
.eq(RateConfig::getWayCode, wayCode)
|
||||
).stream().forEach(r -> {
|
||||
rateConfigMap.put(r.getIfCode(), r);
|
||||
ifCodes.add(r.getIfCode());
|
||||
});
|
||||
|
||||
if (rateConfigMap.isEmpty()) {
|
||||
return ApiRes.page(new Page());
|
||||
}
|
||||
|
||||
// 查询出 商户配置过 支付接口参数的 列表
|
||||
Map<String, PayInterfaceConfig> payInterfaceConfigMap = new HashMap<>();
|
||||
|
||||
payInterfaceConfigService.list(PayInterfaceConfig.gw().eq(PayInterfaceConfig::getInfoId, appId).eq(PayInterfaceConfig::getInfoType, CS.SYS_ROLE_TYPE.MCH_APP)
|
||||
.in(PayInterfaceConfig::getIfCode, ifCodes)
|
||||
.eq(PayInterfaceConfig::getState, CS.YES)
|
||||
).stream().forEach(r -> payInterfaceConfigMap.put(r.getIfCode(), r));
|
||||
|
||||
if (payInterfaceConfigMap.isEmpty()) {
|
||||
return ApiRes.page(new Page());
|
||||
}
|
||||
|
||||
// 查询当前开启的配置
|
||||
MchPayPassage mchPayPassage = mchPayPassageService.findMchPayPassage(mchInfo.getMchNo(), appId, wayCode);
|
||||
String currentOpenIfCode = mchPayPassage != null ? mchPayPassage.getIfCode() : "";
|
||||
|
||||
// 查询支付接口
|
||||
List<PayInterfaceDefine> result = payInterfaceDefineService.list(PayInterfaceDefine.gw().in(PayInterfaceDefine::getIfCode, ifCodes).eq(PayInterfaceDefine::getState, CS.YES));
|
||||
result.stream().forEach(r -> {
|
||||
|
||||
r.addExt("configState", currentOpenIfCode.equals(r.getIfCode()) ? CS.YES : CS.NO);
|
||||
r.addExt("paywayFee", rateConfigMap.get(r.getIfCode()).getPaywayFeeDetail());
|
||||
|
||||
});
|
||||
|
||||
return ApiRes.page(new Page().setRecords(result).setTotal(result.size()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.jeequan.jeepay.core.constants.CS;
|
||||
import com.jeequan.jeepay.core.exception.BizException;
|
||||
import com.jeequan.jeepay.core.interfaces.paychannel.IIsvmchTerminalService;
|
||||
import com.jeequan.jeepay.core.model.ApiRes;
|
||||
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
|
||||
import com.jeequan.jeepay.core.model.terminal.TerminalChannelModel;
|
||||
import com.jeequan.jeepay.core.utils.JeepayKit;
|
||||
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
|
||||
import com.jeequan.jeepay.db.entity.*;
|
||||
import com.jeequan.jeepay.service.impl.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/***
|
||||
* 终端设备管理
|
||||
*
|
||||
* @author terrfly
|
||||
* @date 2022/4/26 17:34
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MchStoreTerminalManage {
|
||||
|
||||
@Autowired private MchStoreTerminalService mchStoreTerminalService;
|
||||
@Autowired private MchInfoService mchInfoService;
|
||||
@Autowired private MchStoreService mchStoreService;
|
||||
@Autowired
|
||||
private MchApplymentService mchApplymentService;
|
||||
@Autowired private PayInterfaceDefineService payInterfaceDefineService;
|
||||
@Autowired private PayInterfaceConfigService payInterfaceConfigService;
|
||||
|
||||
|
||||
/** add */
|
||||
public ApiRes add(MchStoreTerminal record ) {
|
||||
|
||||
if(mchStoreTerminalService.count(MchStoreTerminal.gw().eq(MchStoreTerminal::getMchNo, record.getMchNo())
|
||||
.eq(MchStoreTerminal::getStoreId, record.getStoreId()).eq(MchStoreTerminal::getTrmNo, record.getTrmNo())
|
||||
.eq(MchStoreTerminal::getAppId, record.getAppId())
|
||||
) > 0 ){
|
||||
throw new BizException("当前设备编号已经存在!");
|
||||
}
|
||||
|
||||
MchInfo mchInfo = mchInfoService.getById(record.getMchNo());
|
||||
|
||||
record.setMchName(mchInfo.getMchShortName());
|
||||
record.setAgentNo(mchInfo.getAgentNo());
|
||||
record.setStoreName(mchStoreService.getById(record.getStoreId()).getStoreName());
|
||||
|
||||
record.setChannelBindInfo(new JSONObject());
|
||||
mchStoreTerminalService.save(record);
|
||||
return ApiRes.ok();
|
||||
}
|
||||
|
||||
|
||||
/** update */
|
||||
public ApiRes update(MchStoreTerminal record) {
|
||||
|
||||
// 不允许更改一下信息
|
||||
record.setMchNo(null);
|
||||
record.setAppId(null);
|
||||
record.setStoreId(null);
|
||||
record.setAgentNo(null);
|
||||
record.setCreatedAt(null);
|
||||
record.setUpdatedAt(null);
|
||||
|
||||
mchStoreTerminalService.updateById(record);
|
||||
return ApiRes.ok();
|
||||
}
|
||||
|
||||
|
||||
/** update */
|
||||
public ApiRes updateDefault(Long trmId, Byte updateFlag) {
|
||||
|
||||
// 查询DB数据
|
||||
MchStoreTerminal dbRecord = mchStoreTerminalService.getById(trmId);
|
||||
|
||||
if(dbRecord == null){
|
||||
throw new BizException("数据不存在");
|
||||
}
|
||||
|
||||
MchStoreTerminal updateRecord = new MchStoreTerminal();
|
||||
updateRecord.setTrmId(dbRecord.getTrmId());
|
||||
updateRecord.setDefaultFlag(updateFlag);
|
||||
|
||||
if(updateFlag == CS.NO){
|
||||
mchStoreTerminalService.updateById(updateRecord);
|
||||
}else{
|
||||
|
||||
//更新所有的为 否
|
||||
mchStoreTerminalService.update(new LambdaUpdateWrapper<MchStoreTerminal>()
|
||||
.eq(MchStoreTerminal::getMchNo, dbRecord.getMchNo())
|
||||
.eq(MchStoreTerminal::getAppId, dbRecord.getAppId())
|
||||
.eq(MchStoreTerminal::getStoreId, dbRecord.getStoreId())
|
||||
.set(MchStoreTerminal::getDefaultFlag, CS.NO)
|
||||
);
|
||||
|
||||
mchStoreTerminalService.updateById(updateRecord);
|
||||
}
|
||||
|
||||
return ApiRes.ok();
|
||||
}
|
||||
|
||||
|
||||
/** 报备列表 */
|
||||
public ApiRes getChannelBindInfos(Long recordId) {
|
||||
|
||||
MchStoreTerminal dbRecord = mchStoreTerminalService.getById(recordId);
|
||||
|
||||
// 查询出所有的支持通道集合
|
||||
List<String> ifCodeList = new ArrayList<>();
|
||||
payInterfaceConfigService.list(PayInterfaceConfig.gw().select(PayInterfaceConfig::getIfCode)
|
||||
.eq(PayInterfaceConfig::getInfoType, CS.SYS_ROLE_TYPE.MCH_APP)
|
||||
.eq(PayInterfaceConfig::getInfoId, dbRecord.getAppId())
|
||||
.eq(PayInterfaceConfig::getState, CS.YES)
|
||||
).forEach(r -> ifCodeList.add(r.getIfCode()));
|
||||
|
||||
if(ifCodeList.isEmpty()){
|
||||
return ApiRes.ok(ifCodeList);
|
||||
}
|
||||
|
||||
|
||||
JSONObject channelBindInfo = dbRecord.getChannelBindInfo();
|
||||
if(channelBindInfo == null){
|
||||
channelBindInfo = new JSONObject();
|
||||
}
|
||||
|
||||
// 查询出所有的应用集合
|
||||
List<PayInterfaceDefine> result = payInterfaceDefineService.list(PayInterfaceDefine.gw().in(PayInterfaceDefine::getIfCode, ifCodeList));
|
||||
for (PayInterfaceDefine payInterfaceDefine : result) {
|
||||
|
||||
TerminalChannelModel model = channelBindInfo.getObject(payInterfaceDefine.getIfCode(), TerminalChannelModel.class);
|
||||
|
||||
// 判断是否为空
|
||||
if(model == null || model.getState() == null){
|
||||
model = new TerminalChannelModel().setState(TerminalChannelModel.STATE_NOT_BIND);
|
||||
}
|
||||
|
||||
payInterfaceDefine.addExt("channelBindInfo", model);
|
||||
}
|
||||
|
||||
return ApiRes.ok(result);
|
||||
}
|
||||
|
||||
|
||||
/** 终端报备 */
|
||||
public ApiRes channelSendup(Long recordId, String ifCode, Byte state) {
|
||||
|
||||
MchStoreTerminal dbRecord = mchStoreTerminalService.getById(recordId);
|
||||
|
||||
JSONObject channelBindInfo = dbRecord.getChannelBindInfo();
|
||||
if(channelBindInfo == null){
|
||||
channelBindInfo = new JSONObject();
|
||||
}
|
||||
|
||||
String storeId = dbRecord.getStoreId();
|
||||
MchStore mchStore = mchStoreService.getById(storeId);
|
||||
String mchApplyId = mchStore.getMchApplyId();
|
||||
MchApplyment mchApplyment = mchApplymentService.getById(mchApplyId);
|
||||
|
||||
// 单独接口的对象
|
||||
TerminalChannelModel ifCodeModel = channelBindInfo.getObject(ifCode, TerminalChannelModel.class);
|
||||
if(ifCodeModel == null){
|
||||
ifCodeModel = new TerminalChannelModel();
|
||||
}
|
||||
|
||||
IIsvmchTerminalService isvmchTerminalService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "IsvmchTerminalService", IIsvmchTerminalService.class);
|
||||
if(isvmchTerminalService == null){
|
||||
ifCodeModel.setState(TerminalChannelModel.STATE_FAIL).setErrInfo(ifCode + "接口不支持报备操作");
|
||||
}else{
|
||||
ChannelRetMsg channelRetMsg = isvmchTerminalService.sendup(dbRecord, mchApplyment.getIsvNo(), dbRecord.getMchNo(), dbRecord.getAppId(), ifCode, state == CS.YES);
|
||||
|
||||
// 操作成功
|
||||
if(channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS){
|
||||
|
||||
ifCodeModel.setErrInfo(null);
|
||||
|
||||
if(state == CS.YES){ // 报备
|
||||
ifCodeModel.setChannelTrmNo(channelRetMsg.getChannelOrderId());
|
||||
}else{ // 取消报备
|
||||
ifCodeModel.setChannelTrmNo(null);
|
||||
}
|
||||
|
||||
ifCodeModel.setState(state);
|
||||
|
||||
}else{ //操作失败
|
||||
ifCodeModel.setState(TerminalChannelModel.STATE_FAIL);
|
||||
ifCodeModel.setErrInfo(channelRetMsg.getChannelErrMsg());
|
||||
}
|
||||
}
|
||||
|
||||
channelBindInfo.put(ifCode, ifCodeModel);
|
||||
|
||||
MchStoreTerminal updateRecord = new MchStoreTerminal().setTrmId(recordId).setChannelBindInfo(channelBindInfo);
|
||||
mchStoreTerminalService.updateById(updateRecord);
|
||||
|
||||
return ApiRes.ok();
|
||||
}
|
||||
|
||||
|
||||
/** 修改终端报备信息 */
|
||||
public ApiRes updChannelBindInfos(Long recordId, String ifCode, String channelTrmNo, Byte state) {
|
||||
|
||||
|
||||
MchStoreTerminal dbRecord = mchStoreTerminalService.getById(recordId);
|
||||
|
||||
JSONObject channelBindInfo = dbRecord.getChannelBindInfo();
|
||||
if(channelBindInfo == null){
|
||||
channelBindInfo = new JSONObject();
|
||||
}
|
||||
|
||||
// 单独接口的对象
|
||||
TerminalChannelModel ifCodeModel = channelBindInfo.getObject(ifCode, TerminalChannelModel.class);
|
||||
if(ifCodeModel == null){
|
||||
ifCodeModel = new TerminalChannelModel();
|
||||
}
|
||||
|
||||
ifCodeModel.setChannelTrmNo(channelTrmNo);
|
||||
ifCodeModel.setState(state);
|
||||
channelBindInfo.put(ifCode, ifCodeModel);
|
||||
|
||||
MchStoreTerminal updateRecord = new MchStoreTerminal().setTrmId(recordId).setChannelBindInfo(channelBindInfo);
|
||||
mchStoreTerminalService.updateById(updateRecord);
|
||||
|
||||
return ApiRes.ok();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.alipay;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.jeequan.jeepay.core.constants.ApiCodeEnum;
|
||||
import com.jeequan.jeepay.core.constants.CS;
|
||||
import com.jeequan.jeepay.core.entity.MchStore;
|
||||
import com.jeequan.jeepay.core.exception.BizException;
|
||||
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
|
||||
import com.jeequan.jeepay.core.interfaces.paychannel.alipaybiz.IAlipayIotDeviceBindService;
|
||||
import com.jeequan.jeepay.core.model.ApiRes;
|
||||
import com.jeequan.jeepay.core.model.alipay.AlipaySpOperationInfo;
|
||||
import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvParams;
|
||||
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
|
||||
import com.jeequan.jeepay.db.entity.MchApplyment;
|
||||
import com.jeequan.jeepay.db.entity.MchConfig;
|
||||
import com.jeequan.jeepay.db.entity.MchInfo;
|
||||
import com.jeequan.jeepay.db.entity.MchStoreDevice;
|
||||
import com.jeequan.jeepay.service.impl.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.MutablePair;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/***
|
||||
* 支付宝Iot设备-店铺绑定ctrl
|
||||
*
|
||||
* @author zx
|
||||
* @date 2023/03/13 15:26
|
||||
*/
|
||||
@Component
|
||||
public class AlipayIotDeviceBindManage {
|
||||
|
||||
@Autowired private MchInfoService mchInfoService;
|
||||
@Autowired private MchStoreService mchStoreService;
|
||||
@Autowired private MchStoreDeviceService mchStoreDeviceService;
|
||||
@Autowired private MchConfigService mchConfigService;
|
||||
@Autowired private IConfigContextQueryService configContextQueryService;
|
||||
@Autowired
|
||||
private MchApplymentService mchApplymentService;
|
||||
|
||||
/** 绑定蚂蚁店铺 **/
|
||||
public ApiRes bind(String mchNo, Long deviceId, Byte alipayBindState, String bindStoreId) {
|
||||
try {
|
||||
if (CS.YES == alipayBindState && bindStoreId == null) {
|
||||
return ApiRes.customFail("请选择要绑定的蚂蚁店铺");
|
||||
}
|
||||
|
||||
// 校验&&获取绑定设备所需信息
|
||||
MutablePair<AlipayIsvParams, MchStoreDevice> pair = checkAndGetAlipaySpOperationInfo(mchNo, bindStoreId, deviceId);
|
||||
MchStoreDevice mchStoreDevice = pair.getRight();
|
||||
String supplierId = MchStoreDevice.ALIPAY_IOT_DEVICE_MAP.get(mchStoreDevice.getProvider());
|
||||
|
||||
IAlipayIotDeviceBindService alipayIotDeviceBindService = SpringBeansUtil.getBean(IAlipayIotDeviceBindService.class);
|
||||
|
||||
// 绑定
|
||||
if (CS.YES == alipayBindState) {
|
||||
// 换绑 需先解绑
|
||||
if (mchStoreDevice.getAlipayBindState() == CS.YES) {
|
||||
alipayIotDeviceBindService.unbind(pair.getLeft(), mchStoreDevice.getMchNo(), mchStoreDevice.extv().getString("alipayMerchantNo"),
|
||||
mchStoreDevice.getAlipayShopId(), supplierId, mchStoreDevice.getDeviceNo());
|
||||
}
|
||||
|
||||
JSONObject deviceJSON = new JSONObject();
|
||||
deviceJSON.put("deviceType", mchStoreDevice.getDeviceType());
|
||||
deviceJSON.put("deviceNo", mchStoreDevice.getDeviceNo());
|
||||
deviceJSON.put("provider", mchStoreDevice.getProvider());
|
||||
|
||||
alipayIotDeviceBindService.bind(pair.getLeft(), mchStoreDevice.getMchNo(), mchStoreDevice.extv().getString("alipayMerchantNo"),
|
||||
mchStoreDevice.extv().getString("alipayShopId"), supplierId, deviceJSON);
|
||||
|
||||
// 蚂蚁店铺设备绑定成功
|
||||
MchStoreDevice updateRecord = new MchStoreDevice();
|
||||
updateRecord.setDeviceId(deviceId);
|
||||
updateRecord.setAlipayBindState(CS.YES);
|
||||
updateRecord.setAlipayShopId(mchStoreDevice.extv().getString("alipayShopId"));
|
||||
mchStoreDeviceService.updateById(updateRecord);
|
||||
}
|
||||
// 解绑
|
||||
else {
|
||||
alipayIotDeviceBindService.unbind(pair.getLeft(), mchStoreDevice.getMchNo(), mchStoreDevice.extv().getString("alipayMerchantNo"),
|
||||
mchStoreDevice.getAlipayShopId(), supplierId, mchStoreDevice.getDeviceNo());
|
||||
|
||||
// 蚂蚁店铺设备解绑成功
|
||||
MchStoreDevice updateRecord = new MchStoreDevice();
|
||||
updateRecord.setDeviceId(deviceId);
|
||||
updateRecord.setAlipayBindState(CS.NO);
|
||||
updateRecord.setAlipayShopId("");
|
||||
mchStoreDeviceService.updateById(updateRecord);
|
||||
}
|
||||
|
||||
return ApiRes.ok();
|
||||
}catch (Exception e) {
|
||||
throw new BizException(StringUtils.defaultString(e.getMessage(), "系统异常!"));
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询设备信息 **/
|
||||
public ApiRes query(String mchNo, String storeId, Long deviceId) {
|
||||
try {
|
||||
MutablePair<AlipayIsvParams, MchStoreDevice> pair = checkAndGetAlipaySpOperationInfo(mchNo, storeId, deviceId);
|
||||
MchStoreDevice mchStoreDevice = pair.getRight();
|
||||
|
||||
IAlipayIotDeviceBindService alipayIotDeviceBindService = SpringBeansUtil.getBean(IAlipayIotDeviceBindService.class);
|
||||
JSONObject resJSON = alipayIotDeviceBindService.query(pair.getLeft(), mchStoreDevice.getDeviceNo());
|
||||
|
||||
return ApiRes.ok(resJSON);
|
||||
}catch (Exception e) {
|
||||
throw new BizException(StringUtils.defaultString(e.getMessage(), "系统异常!"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 查询当前商户支付宝授权信息 **/
|
||||
private MutablePair<AlipayIsvParams, MchStoreDevice> checkAndGetAlipaySpOperationInfo(String mchNo, String bindStoreId, Long deviceId) {
|
||||
// 查询绑定设备信息
|
||||
MchStoreDevice storeDevice = mchStoreDeviceService.getById(deviceId);
|
||||
if (storeDevice == null || storeDevice.getState() != CS.YES || storeDevice.getBindState() != CS.YES || storeDevice.getDeviceType() != MchStoreDevice.DEVICE_TYPE_RUYI) {
|
||||
throw new BizException("设备不存在或未绑定");
|
||||
}
|
||||
// 运营平台只传deviceId,商户系统传递mchNo并校验权限
|
||||
if (StringUtils.isNotBlank(mchNo) && !mchNo.equals(storeDevice.getMchNo())) {
|
||||
throw new BizException(ApiCodeEnum.SYS_PERMISSION_ERROR);
|
||||
}
|
||||
mchNo = StringUtils.defaultIfBlank(mchNo, storeDevice.getMchNo());
|
||||
com.jeequan.jeepay.db.entity.MchStore mchStore2 = mchStoreService.getById(storeDevice.getStoreId());
|
||||
MchApplyment mchApplyment = mchApplymentService.getById(mchStore2.getMchApplyId());
|
||||
|
||||
MchInfo mchInfo = mchInfoService.getById(mchNo);
|
||||
if (MchInfo.TYPE_ISVSUB != mchInfo.getType()) {
|
||||
throw new BizException("仅支持特约商户");
|
||||
}
|
||||
|
||||
AlipayIsvParams alipayIsvParams = (AlipayIsvParams) configContextQueryService.queryIsvParams(mchApplyment.getIsvNo(), CS.IF_CODE.ALIPAY);
|
||||
if (alipayIsvParams == null) {
|
||||
throw new BizException("服务商未配置参数,请联系平台处理!");
|
||||
}
|
||||
|
||||
// 查询授权商户号
|
||||
MchConfig mchConfig = mchConfigService.getByMchNoAndConfigKey(mchNo, AlipaySpOperationInfo.MCH_CONFIG_KEY);
|
||||
if (mchConfig == null || StringUtils.isBlank(mchConfig.getConfigVal())) {
|
||||
throw new BizException("请先发起支付宝代运营授权");
|
||||
}
|
||||
|
||||
AlipaySpOperationInfo operationInfo = JSON.parseObject(mchConfig.getConfigVal(), AlipaySpOperationInfo.class);
|
||||
if (!AlipaySpOperationInfo.HANDLE_STATUS_SUCCESS.equals(operationInfo.getHandleStatus()) || StringUtils.isBlank(operationInfo.getMerchantNo())) {
|
||||
throw new BizException("请先发起支付宝代运营授权");
|
||||
}
|
||||
|
||||
// 查询蚂蚁店铺ID
|
||||
if (bindStoreId != null) {
|
||||
com.jeequan.jeepay.db.entity.MchStore mchStore = mchStoreService.getById(bindStoreId);
|
||||
if (!MchStore.ALIPAY_SHOP_STATUS_SUCCESS.equals(mchStore.getAlipayShopStatus()) || StringUtils.isBlank(mchStore.getAlipayShopId())) {
|
||||
throw new BizException("当前店铺未同步至蚂蚁店铺");
|
||||
}
|
||||
storeDevice.addExt("alipayShopId", mchStore.getAlipayShopId());
|
||||
}
|
||||
|
||||
storeDevice.addExt("alipayMerchantNo", operationInfo.getMerchantNo());
|
||||
|
||||
return MutablePair.of(alipayIsvParams, storeDevice);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.alipay;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.jeequan.jeepay.core.constants.CS;
|
||||
import com.jeequan.jeepay.core.exception.BizException;
|
||||
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
|
||||
import com.jeequan.jeepay.core.interfaces.paychannel.alipaybiz.IAlipayOpenSpOperationService;
|
||||
import com.jeequan.jeepay.core.model.ApiRes;
|
||||
import com.jeequan.jeepay.core.model.alipay.AlipaySpOperationInfo;
|
||||
import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvParams;
|
||||
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
|
||||
import com.jeequan.jeepay.db.entity.MchConfig;
|
||||
import com.jeequan.jeepay.db.entity.MchInfo;
|
||||
import com.jeequan.jeepay.db.entity.MchStore;
|
||||
import com.jeequan.jeepay.db.entity.MchStoreDevice;
|
||||
import com.jeequan.jeepay.service.impl.MchConfigService;
|
||||
import com.jeequan.jeepay.service.impl.MchInfoService;
|
||||
import com.jeequan.jeepay.service.impl.MchStoreDeviceService;
|
||||
import com.jeequan.jeepay.service.impl.MchStoreService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/***
|
||||
* 支付宝服务商代运营授权
|
||||
*
|
||||
* @author zx
|
||||
* @date 2023/03/13 15:26
|
||||
*/
|
||||
@Component
|
||||
public class AlipayOpenSpOperationManage {
|
||||
|
||||
@Autowired private MchInfoService mchInfoService;
|
||||
@Autowired private MchConfigService mchConfigService;
|
||||
@Autowired private MchStoreService mchStoreService;
|
||||
@Autowired private MchStoreDeviceService mchStoreDeviceService;
|
||||
@Autowired private IConfigContextQueryService configContextQueryService;
|
||||
|
||||
/** 获取授权码 **/
|
||||
public ApiRes queryQrcode(String mchNo, String alipayAccount) {
|
||||
try {
|
||||
AlipayIsvParams alipayIsvParams = applyCheck(mchNo);
|
||||
|
||||
IAlipayOpenSpOperationService alipayOpenSpOperationService = SpringBeansUtil.getBean(IAlipayOpenSpOperationService.class);
|
||||
JSONObject resJSON = alipayOpenSpOperationService.querySpOperationQrcode(alipayIsvParams, alipayAccount);
|
||||
|
||||
// 保存或更新发起授权的支付宝登录账号
|
||||
mchInfoService.saveOrUpdateAlipaySpOperationAccount(mchNo, alipayAccount, "qrcode");
|
||||
|
||||
return ApiRes.ok(resJSON);
|
||||
}catch (Exception e) {
|
||||
return ApiRes.customFail(StringUtils.defaultString(e.getMessage(), "系统异常!"));
|
||||
}
|
||||
}
|
||||
|
||||
/** 发起授权 **/
|
||||
public ApiRes apply(String mchNo, String alipayAccount) {
|
||||
try {
|
||||
AlipayIsvParams alipayIsvParams = applyCheck(mchNo);
|
||||
|
||||
IAlipayOpenSpOperationService alipayOpenSpOperationService = SpringBeansUtil.getBean(IAlipayOpenSpOperationService.class);
|
||||
JSONObject resJSON = alipayOpenSpOperationService.applySpOperation(alipayIsvParams, alipayAccount);
|
||||
|
||||
// 保存或更新发起授权的支付宝登录账号
|
||||
mchInfoService.saveOrUpdateAlipaySpOperationAccount(mchNo, alipayAccount, "apply");
|
||||
|
||||
return ApiRes.ok(resJSON);
|
||||
}catch (Exception e) {
|
||||
return ApiRes.customFail(StringUtils.defaultString(e.getMessage(), "系统异常!"));
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询支付宝授权结果 **/
|
||||
public ApiRes queryResult(String mchNo) {
|
||||
try {
|
||||
MchInfo mchInfo = mchInfoService.getById(mchNo);
|
||||
if (MchInfo.TYPE_ISVSUB != mchInfo.getType()) {
|
||||
throw new BizException("仅支持特约商户");
|
||||
}
|
||||
AlipayIsvParams alipayIsvParams = (AlipayIsvParams) configContextQueryService.queryIsvParams(mchInfo.getIsvNo(), CS.IF_CODE.ALIPAY);
|
||||
if (alipayIsvParams == null) {
|
||||
throw new BizException("服务商未配置参数,请联系平台处理!");
|
||||
}
|
||||
|
||||
MchConfig mchConfig = mchConfigService.getByMchNoAndConfigKey(mchNo, AlipaySpOperationInfo.MCH_CONFIG_KEY);
|
||||
if (mchConfig == null || StringUtils.isBlank(mchConfig.getConfigVal())) {
|
||||
throw new BizException("请先发起授权");
|
||||
}
|
||||
AlipaySpOperationInfo dbOperationInfo = JSON.parseObject(mchConfig.getConfigVal(), AlipaySpOperationInfo.class);
|
||||
if (AlipaySpOperationInfo.HANDLE_STATUS_SUCCESS.equals(dbOperationInfo.getHandleStatus())) {
|
||||
throw new BizException("已授权成功,无需再次查询");
|
||||
}
|
||||
|
||||
// 查询支付宝授权状态
|
||||
IAlipayOpenSpOperationService alipayOpenSpOperationService = SpringBeansUtil.getBean(IAlipayOpenSpOperationService.class);
|
||||
AlipaySpOperationInfo operationResult = alipayOpenSpOperationService.querySpOperationResult(alipayIsvParams, dbOperationInfo.getAlipayAccount());
|
||||
|
||||
// 更新授权状态
|
||||
if (AlipaySpOperationInfo.HANDLE_STATUS_SUCCESS.equals(operationResult.getHandleStatus())) {
|
||||
dbOperationInfo.setHandleStatus(operationResult.getHandleStatus());
|
||||
dbOperationInfo.setMerchantNo(operationResult.getMerchantNo());
|
||||
|
||||
MchConfig updateRecord = new MchConfig();
|
||||
updateRecord.setConfigVal(JSON.toJSONString(dbOperationInfo));
|
||||
mchConfigService.update(updateRecord,
|
||||
new LambdaUpdateWrapper<MchConfig>().eq(MchConfig::getConfigKey, AlipaySpOperationInfo.MCH_CONFIG_KEY).eq(MchConfig::getMchNo, mchNo));
|
||||
}
|
||||
|
||||
return ApiRes.ok(operationResult);
|
||||
}catch (Exception e) {
|
||||
return ApiRes.customFail(StringUtils.defaultString(e.getMessage(), "系统异常!"));
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询当前商户支付宝授权信息 **/
|
||||
public ApiRes authInfo(String mchNo) {
|
||||
MchConfig mchConfig = mchConfigService.getByMchNoAndConfigKey(mchNo, AlipaySpOperationInfo.MCH_CONFIG_KEY);
|
||||
if (mchConfig == null || StringUtils.isBlank(mchConfig.getConfigVal())) {
|
||||
return ApiRes.ok();
|
||||
}
|
||||
|
||||
return ApiRes.ok(JSON.parseObject(mchConfig.getConfigVal()));
|
||||
}
|
||||
|
||||
|
||||
private AlipayIsvParams applyCheck(String mchNo) {
|
||||
MchInfo mchInfo = mchInfoService.getById(mchNo);
|
||||
if (MchInfo.TYPE_ISVSUB != mchInfo.getType()) {
|
||||
throw new BizException("仅支持特约商户");
|
||||
}
|
||||
|
||||
AlipayIsvParams alipayIsvParams = (AlipayIsvParams) configContextQueryService.queryIsvParams(mchInfo.getIsvNo(), CS.IF_CODE.ALIPAY);
|
||||
if (alipayIsvParams == null) {
|
||||
throw new BizException("服务商未配置参数,请联系平台处理!");
|
||||
}
|
||||
|
||||
// 查询是否存在绑定的设备
|
||||
long count = mchStoreDeviceService.count(MchStoreDevice.gw()
|
||||
.eq(MchStoreDevice::getMchNo, mchNo)
|
||||
.eq(MchStoreDevice::getDeviceType, MchStoreDevice.DEVICE_TYPE_RUYI)
|
||||
.eq(MchStoreDevice::getAlipayBindState, CS.YES)
|
||||
);
|
||||
if (count > 0) {
|
||||
throw new BizException("存在绑定的如意设备,请先将如意设备从蚂蚁店铺解绑!");
|
||||
}
|
||||
|
||||
// 查询是否存在绑定蚂蚁店铺
|
||||
count = mchStoreService.count(MchStore.gw().
|
||||
eq(MchStore::getMchNo, mchNo)
|
||||
.in(MchStore::getAlipayShopStatus, Arrays.asList(MchStore.ALIPAY_SHOP_STATUS_SUCCESS, MchStore.ALIPAY_SHOP_STATUS_AUDITING))
|
||||
);
|
||||
if (count > 0) {
|
||||
throw new BizException("已创建蚂蚁店铺,请先关闭蚂蚁店铺!");
|
||||
}
|
||||
return alipayIsvParams;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.alipay;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.jeequan.jeepay.core.constants.ApiCodeEnum;
|
||||
import com.jeequan.jeepay.core.constants.CS;
|
||||
import com.jeequan.jeepay.core.entity.MchStore;
|
||||
import com.jeequan.jeepay.core.exception.BizException;
|
||||
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
|
||||
import com.jeequan.jeepay.core.interfaces.paychannel.alipaybiz.IAlipayShopService;
|
||||
import com.jeequan.jeepay.core.model.ApiRes;
|
||||
import com.jeequan.jeepay.core.model.alipay.AlipaySpOperationInfo;
|
||||
import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvParams;
|
||||
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
|
||||
import com.jeequan.jeepay.db.entity.MchConfig;
|
||||
import com.jeequan.jeepay.db.entity.MchInfo;
|
||||
import com.jeequan.jeepay.db.entity.MchStoreDevice;
|
||||
import com.jeequan.jeepay.service.impl.MchConfigService;
|
||||
import com.jeequan.jeepay.service.impl.MchInfoService;
|
||||
import com.jeequan.jeepay.service.impl.MchStoreDeviceService;
|
||||
import com.jeequan.jeepay.service.impl.MchStoreService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.MutablePair;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/***
|
||||
* 蚂蚁门店
|
||||
*
|
||||
* @author zx
|
||||
* @date 2023/03/13 15:26
|
||||
*/
|
||||
@Component
|
||||
public class AlipayShopManage {
|
||||
|
||||
@Autowired private MchInfoService mchInfoService;
|
||||
@Autowired private MchStoreService mchStoreService;
|
||||
@Autowired private MchConfigService mchConfigService;
|
||||
@Autowired private MchStoreDeviceService mchStoreDeviceService;
|
||||
@Autowired private IConfigContextQueryService configContextQueryService;
|
||||
|
||||
/** 查询店铺详情 **/
|
||||
public ApiRes query(String mchNo, String storeId) {
|
||||
try {
|
||||
MutablePair<AlipayIsvParams, AlipaySpOperationInfo> mutablePair = checkAndGetAlipaySpOperationInfo(mchNo, storeId);
|
||||
|
||||
IAlipayShopService alipayShopService = SpringBeansUtil.getBean(IAlipayShopService.class);
|
||||
MchStore result = alipayShopService.query(mutablePair.getLeft(), String.valueOf(storeId), mutablePair.getRight().getMerchantNo());
|
||||
|
||||
MchStore dbRecord = mchStoreService.getById(storeId);
|
||||
result.setStoreId(storeId);
|
||||
result.setAlipayShopStatus(dbRecord.getAlipayShopStatus());
|
||||
|
||||
return ApiRes.ok(result);
|
||||
}catch (Exception e) {
|
||||
return ApiRes.customFail(StringUtils.defaultString(e.getMessage(), "系统异常!"));
|
||||
}
|
||||
}
|
||||
|
||||
/** 创建店铺 **/
|
||||
public ApiRes create(String mchNo, String storeId, MchStore mchStore) {
|
||||
try {
|
||||
MutablePair<AlipayIsvParams, AlipaySpOperationInfo> mutablePair = checkAndGetAlipaySpOperationInfo(mchNo, storeId);
|
||||
|
||||
IAlipayShopService alipayShopService = SpringBeansUtil.getBean(IAlipayShopService.class);
|
||||
MchStore result = alipayShopService.create(mutablePair.getLeft(), mchStore, mutablePair.getRight().getMerchantNo());
|
||||
|
||||
// 蚂蚁店铺申请创建成功,保存申请单号,用于查询申请状态
|
||||
com.jeequan.jeepay.db.entity.MchStore updateRecord = new com.jeequan.jeepay.db.entity.MchStore();
|
||||
updateRecord.setStoreId(storeId);
|
||||
updateRecord.setAlipayShopCreateId(result.getAlipayShopCreateId());
|
||||
updateRecord.setAlipayShopStatus(MchStore.ALIPAY_SHOP_STATUS_AUDITING); // 审核中
|
||||
mchStoreService.updateById(updateRecord);
|
||||
|
||||
return ApiRes.ok();
|
||||
}catch (Exception e) {
|
||||
return ApiRes.customFail(StringUtils.defaultString(e.getMessage(), "系统异常!"));
|
||||
}
|
||||
}
|
||||
|
||||
/** 修改店铺 **/
|
||||
public ApiRes update(String mchNo, String storeId, MchStore mchStore) {
|
||||
try {
|
||||
// 查询商户门店是否已成功创建蚂蚁店铺
|
||||
com.jeequan.jeepay.db.entity.MchStore dbRecord = mchStoreService.getById(storeId);
|
||||
if (dbRecord == null || !MchStore.ALIPAY_SHOP_STATUS_SUCCESS.equals(dbRecord.getAlipayShopStatus()) || StringUtils.isBlank(dbRecord.getAlipayShopId())) {
|
||||
throw new BizException("门店不存在或未成功创建蚂蚁店铺!");
|
||||
}
|
||||
mchStore.setAlipayShopId(dbRecord.getAlipayShopId()); // 蚂蚁店铺ID
|
||||
|
||||
MutablePair<AlipayIsvParams, AlipaySpOperationInfo> mutablePair = checkAndGetAlipaySpOperationInfo(mchNo, storeId);
|
||||
|
||||
IAlipayShopService alipayShopService = SpringBeansUtil.getBean(IAlipayShopService.class);
|
||||
MchStore result = alipayShopService.update(mutablePair.getLeft(), mchStore);
|
||||
|
||||
// 蚂蚁店铺申请创建成功,保存申请单号,用于查询申请状态
|
||||
com.jeequan.jeepay.db.entity.MchStore updateRecord = new com.jeequan.jeepay.db.entity.MchStore();
|
||||
updateRecord.setStoreId(storeId);
|
||||
updateRecord.setAlipayShopCreateId(result.getAlipayShopCreateId());
|
||||
updateRecord.setAlipayShopStatus(MchStore.ALIPAY_SHOP_STATUS_AUDITING); // 审核中
|
||||
mchStoreService.updateById(updateRecord);
|
||||
|
||||
return ApiRes.ok(updateRecord);
|
||||
}catch (Exception e) {
|
||||
return ApiRes.customFail(StringUtils.defaultString(e.getMessage(), "系统异常!"));
|
||||
}
|
||||
}
|
||||
|
||||
/** 关闭店铺 **/
|
||||
public ApiRes close(String mchNo, String storeId) {
|
||||
try {
|
||||
MutablePair<AlipayIsvParams, AlipaySpOperationInfo> mutablePair = checkAndGetAlipaySpOperationInfo(mchNo, storeId);
|
||||
|
||||
// 查询商户门店是否已成功创建蚂蚁店铺
|
||||
com.jeequan.jeepay.db.entity.MchStore dbRecord = mchStoreService.getById(storeId);
|
||||
if (dbRecord == null || !MchStore.ALIPAY_SHOP_STATUS_SUCCESS.equals(dbRecord.getAlipayShopStatus()) || StringUtils.isBlank(dbRecord.getAlipayShopId())) {
|
||||
throw new BizException("门店不存在或未成功创建蚂蚁店铺!");
|
||||
}
|
||||
|
||||
// 查询门店下是否绑定了设备
|
||||
long count = mchStoreDeviceService.count(MchStoreDevice.gw()
|
||||
.eq(MchStoreDevice::getAlipayBindState, CS.YES)
|
||||
.eq(MchStoreDevice::getAlipayShopId, dbRecord.getAlipayShopId()));
|
||||
if (count > 0) {
|
||||
throw new BizException("当前门店下存在绑定的设备,请先解绑");
|
||||
}
|
||||
|
||||
IAlipayShopService alipayShopService = SpringBeansUtil.getBean(IAlipayShopService.class);
|
||||
alipayShopService.close(mutablePair.getLeft(), dbRecord.getAlipayShopId());
|
||||
|
||||
// 蚂蚁店铺关闭成功,将本地蚂蚁门店信息改为初始未创建状态
|
||||
com.jeequan.jeepay.db.entity.MchStore updateRecord = new com.jeequan.jeepay.db.entity.MchStore();
|
||||
updateRecord.setStoreId(storeId);
|
||||
updateRecord.setAlipayShopId("");
|
||||
updateRecord.setAlipayShopCreateId("");
|
||||
updateRecord.setAlipayShopStatus(MchStore.ALIPAY_SHOP_STATUS_NOT_EXISTS); // 未创建
|
||||
mchStoreService.updateById(updateRecord);
|
||||
|
||||
return ApiRes.ok();
|
||||
}catch (Exception e) {
|
||||
return ApiRes.customFail(StringUtils.defaultString(e.getMessage(), "系统异常!"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 根据申请单查询店铺创建结果 **/
|
||||
public ApiRes createResultQuery(String mchNo, String storeId) {
|
||||
try {
|
||||
// 查询商户门店下 蚂蚁店铺是否为审核中
|
||||
com.jeequan.jeepay.db.entity.MchStore dbRecord = mchStoreService.getById(storeId);
|
||||
if (dbRecord == null) {
|
||||
throw new BizException("门店不存在!");
|
||||
}
|
||||
if (MchStore.ALIPAY_SHOP_STATUS_SUCCESS.equals(dbRecord.getAlipayShopStatus())) {
|
||||
return ApiRes.ok(dbRecord);
|
||||
}
|
||||
if (StringUtils.isBlank(dbRecord.getAlipayShopCreateId())) {
|
||||
throw new BizException("未发起创建蚂蚁店铺!");
|
||||
}
|
||||
|
||||
MutablePair<AlipayIsvParams, AlipaySpOperationInfo> mutablePair = checkAndGetAlipaySpOperationInfo(mchNo, storeId);
|
||||
|
||||
IAlipayShopService alipayShopService = SpringBeansUtil.getBean(IAlipayShopService.class);
|
||||
MchStore result = alipayShopService.createResultQuery(mutablePair.getLeft(), dbRecord.getAlipayShopCreateId());
|
||||
|
||||
// 更新蚂蚁店铺授权状态
|
||||
com.jeequan.jeepay.db.entity.MchStore updateRecord = new com.jeequan.jeepay.db.entity.MchStore();
|
||||
updateRecord.setStoreId(storeId);
|
||||
updateRecord.setAlipayShopId(result.getAlipayShopId());
|
||||
updateRecord.setAlipayShopStatus(result.getAlipayShopStatus());
|
||||
mchStoreService.updateById(updateRecord);
|
||||
|
||||
return ApiRes.ok(updateRecord);
|
||||
}catch (Exception e) {
|
||||
return ApiRes.customFail(StringUtils.defaultString(e.getMessage(), "系统异常!"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 查询当前商户支付宝授权信息 **/
|
||||
private MutablePair<AlipayIsvParams, AlipaySpOperationInfo> checkAndGetAlipaySpOperationInfo(String mchNo, String storeId) {
|
||||
MchStore mchStore = mchStoreService.getById(storeId);
|
||||
if (mchStore == null) {
|
||||
throw new BizException("门店不存在");
|
||||
}
|
||||
// 运营平台只传storeId,商户系统传递mchNo并校验权限
|
||||
if (StringUtils.isNotBlank(mchNo) && !mchNo.equals(mchStore.getMchNo())) {
|
||||
throw new BizException(ApiCodeEnum.SYS_PERMISSION_ERROR);
|
||||
}
|
||||
mchNo = StringUtils.defaultIfBlank(mchNo, mchStore.getMchNo());
|
||||
|
||||
MchInfo mchInfo = mchInfoService.getById(mchNo);
|
||||
if (MchInfo.TYPE_ISVSUB != mchInfo.getType()) {
|
||||
throw new BizException("仅支持特约商户");
|
||||
}
|
||||
|
||||
AlipayIsvParams alipayIsvParams = (AlipayIsvParams) configContextQueryService.queryIsvParams(mchInfo.getIsvNo(), CS.IF_CODE.ALIPAY);
|
||||
if (alipayIsvParams == null) {
|
||||
throw new BizException("服务商未配置参数,请联系平台处理!");
|
||||
}
|
||||
|
||||
// 查询授权商户号
|
||||
MchConfig mchConfig = mchConfigService.getByMchNoAndConfigKey(mchNo, AlipaySpOperationInfo.MCH_CONFIG_KEY);
|
||||
if (mchConfig == null || StringUtils.isBlank(mchConfig.getConfigVal())) {
|
||||
throw new BizException("请先发起授权");
|
||||
}
|
||||
|
||||
AlipaySpOperationInfo operationInfo = JSONObject.parseObject(mchConfig.getConfigVal(), AlipaySpOperationInfo.class);
|
||||
if (!AlipaySpOperationInfo.HANDLE_STATUS_SUCCESS.equals(operationInfo.getHandleStatus()) || StringUtils.isBlank(operationInfo.getMerchantNo())) {
|
||||
throw new BizException("支付宝代运营授权未通过,无法操作蚂蚁店铺");
|
||||
}
|
||||
|
||||
return MutablePair.of(alipayIsvParams, operationInfo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.auth;
|
||||
|
||||
import com.jeequan.jeepay.core.cache.RedisUtil;
|
||||
import com.jeequan.jeepay.core.exception.BizException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/***
|
||||
* 扫码登录
|
||||
*
|
||||
* @author zx
|
||||
* @date 2022/5/17 10:34
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AuthByQrcodeManage {
|
||||
|
||||
public final static String QRCODE_STATUS_WAITING = "waiting"; // 等待扫码
|
||||
public final static String QRCODE_STATUS_SCANED = "scaned"; // 已扫码
|
||||
public final static String QRCODE_STATUS_EXPRIED = "expried"; // 过期
|
||||
public final static String QRCODE_STATUS_CONFIRMED = "confirmed"; // 确认登录
|
||||
public final static String QRCODE_STATUS_CANCELED = "canceled"; // 取消登录
|
||||
|
||||
// 设置web端二维码被扫状态,等待扫码
|
||||
public static void setQrcodeStatusWaiting(String qrcodeNo) {
|
||||
RedisUtil.setString(qrcodeNo, AuthByQrcodeManage.QRCODE_STATUS_WAITING, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// 清空web端二维码被扫状态
|
||||
public static void delQrcodeStatus(String qrcodeNo) {
|
||||
RedisUtil.del(qrcodeNo);
|
||||
}
|
||||
|
||||
// 更新web端二维码被扫状态为:等待扫码 --> 已扫
|
||||
public static void updateQrcodeStatusWaiting2Scaned(String qrcodeNo) {
|
||||
|
||||
String qrCodeStatus = RedisUtil.getString(qrcodeNo);
|
||||
if (StringUtils.isBlank(qrCodeStatus) || !qrCodeStatus.equals(AuthByQrcodeManage.QRCODE_STATUS_WAITING)) {
|
||||
throw new BizException("二维码无效,请刷新二维码后重新扫描");
|
||||
}
|
||||
|
||||
RedisUtil.setString(qrcodeNo, AuthByQrcodeManage.QRCODE_STATUS_SCANED, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// 更新web端二维码被扫状态,已扫 --> 确认登录
|
||||
public static void updateQrcodeStatusScaned2Confirmed(String qrcodeNo, String tokenStr) {
|
||||
|
||||
String dbQrCodeStatus = RedisUtil.getString(qrcodeNo);
|
||||
if (StringUtils.isBlank(dbQrCodeStatus) || !dbQrCodeStatus.equals(AuthByQrcodeManage.QRCODE_STATUS_SCANED)) {
|
||||
throw new BizException("二维码无效,请刷新二维码后重新扫描");
|
||||
}
|
||||
|
||||
RedisUtil.setString(qrcodeNo, AuthByQrcodeManage.QRCODE_STATUS_CONFIRMED + "_" + tokenStr);
|
||||
}
|
||||
|
||||
// 更新web端二维码被扫状态,已扫 --> 取消扫码登录
|
||||
public static void updateQrcodeStatusScaned2Canceled(String sysType, String qrcodeNo) {
|
||||
|
||||
String dbQrCodeStatus = RedisUtil.getString(qrcodeNo);
|
||||
if (StringUtils.isBlank(dbQrCodeStatus) || !dbQrCodeStatus.equals(AuthByQrcodeManage.QRCODE_STATUS_SCANED)) {
|
||||
throw new BizException("二维码无效,请刷新二维码后重新扫描");
|
||||
}
|
||||
|
||||
RedisUtil.setString(qrcodeNo, AuthByQrcodeManage.QRCODE_STATUS_CANCELED);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.auth;
|
||||
|
||||
import com.jeequan.jeepay.core.cache.RedisUtil;
|
||||
import com.jeequan.jeepay.core.constants.ApiCodeEnum;
|
||||
import com.jeequan.jeepay.core.constants.CS;
|
||||
import com.jeequan.jeepay.core.exception.BizException;
|
||||
import com.jeequan.jeepay.core.jwt.JWTPayload;
|
||||
import com.jeequan.jeepay.core.model.ApiRes;
|
||||
import com.jeequan.jeepay.core.model.DBsecurityConfig;
|
||||
import com.jeequan.jeepay.core.model.security.JeeUserDetails;
|
||||
import com.jeequan.jeepay.db.entity.AgentInfo;
|
||||
import com.jeequan.jeepay.service.impl.SysConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/***
|
||||
* 登录校验管理
|
||||
*
|
||||
* @author zx
|
||||
* @date 2022/5/17 10:34
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AuthManage {
|
||||
|
||||
@Autowired private SysConfigService sysConfigService;
|
||||
|
||||
/** 防穷举验证 **/
|
||||
public void loginErrValidate(String sysType, String username) {
|
||||
|
||||
DBsecurityConfig dbSecurityConfig = sysConfigService.getDBSecurityConfig();
|
||||
if (dbSecurityConfig.getLoginErrorMaxLimit().getErrMax() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String failPassCacheKey = CS.getCacheKeyLoginErr(sysType, username);
|
||||
String failCount = RedisUtil.getString(failPassCacheKey);
|
||||
// if(StringUtils.isNotEmpty(failCount) && Integer.parseInt(failCount) >= dbSecurityConfig.getLoginErrorMaxLimit().getErrMax()){ //已经 临时锁定
|
||||
// throw new BizException("密码输入错误次数超限,请稍后再试");
|
||||
// }
|
||||
}
|
||||
|
||||
/** 防穷举累加 还可尝试x次,失败将锁定x分账 **/
|
||||
public void loginErrCount(String sysType, String username) {
|
||||
|
||||
DBsecurityConfig dbSecurityConfig = sysConfigService.getDBSecurityConfig();
|
||||
if (dbSecurityConfig.getLoginErrorMaxLimit().getErrMax() == 0) {
|
||||
throw new BizException("用户名/密码错误");
|
||||
}
|
||||
|
||||
String failPassCacheKey = CS.getCacheKeyLoginErr(sysType, username);
|
||||
|
||||
//累加缓存统计数据
|
||||
long failCountL = RedisUtil.increment(failPassCacheKey, 1);
|
||||
RedisUtil.expire(failPassCacheKey, dbSecurityConfig.getLoginErrorMaxLimit().getLimitMinute(), TimeUnit.MINUTES); //缓存x分钟
|
||||
|
||||
if(failCountL >= dbSecurityConfig.getLoginErrorMaxLimit().getErrMax()){ //超过xx次
|
||||
throw new BizException("密码输入错误次数超限,请稍后再试");
|
||||
}else{
|
||||
throw new BizException("用户名/密码错误,还可尝试" + ( dbSecurityConfig.getLoginErrorMaxLimit().getErrMax() - failCountL ) + "次,失败将锁定"
|
||||
+ dbSecurityConfig.getLoginErrorMaxLimit().getLimitMinute() + "分钟");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 密码过期的过滤器, 返回: true: 已过期,请直接return, false,正常请求 */
|
||||
public static boolean passwordExpiredFilter(JeeUserDetails jeeUserDetails, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
|
||||
// 系统不强制更改
|
||||
if(!SysConfigService.PWD_EXPIRED_MUST_RESET){
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果密码认证 && 密码已过期
|
||||
if( JWTPayload.CREDENTIAL_AUTH_TYPE.PASSWD.equals(jeeUserDetails.getAuthType()) &&
|
||||
jeeUserDetails.getSysUser().getPwdExpiredTime() != null && jeeUserDetails.getSysUser().getPwdExpiredTime().compareTo(new Date()) <= 0) {
|
||||
|
||||
String requestURI = request.getRequestURI();
|
||||
|
||||
// 查询用户信息 修改密码,可操作
|
||||
if(requestURI.indexOf("/api/current/user") >= 0 || requestURI.indexOf("/api/current/modifyPwd") >= 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().println(ApiRes.fail(ApiCodeEnum.SYS_USER_PWD_EXPIRED).toJSONString());
|
||||
response.getWriter().flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 服务商未审核过滤器, 返回: true: 未过审,请直接return, false,正常请求 */
|
||||
public static boolean agentUnAuditFilter(JeeUserDetails jeeUserDetails, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
|
||||
// 如果当前服务商状态为待审核/审核驳回 跳转至资料
|
||||
if(jeeUserDetails.getInfoState() == AgentInfo.AUDIT_STATE_ING || jeeUserDetails.getInfoState() == AgentInfo.AUDIT_STATE_FALSE
|
||||
|| jeeUserDetails.getInfoState() == AgentInfo.AUDIT_STATE_UN_AUTH) {
|
||||
|
||||
String requestURI = request.getRequestURI();
|
||||
|
||||
// 查询用户信息、退出登录、修改密码、文件上传、信息修改->可操作
|
||||
if(requestURI.indexOf("/api/current/user") >= 0 || requestURI.indexOf("/api/current/logout") >= 0 ||requestURI.indexOf("/api/mainChart") >= 0
|
||||
|| requestURI.indexOf("/api/ossFiles/form") >= 0 || requestURI.indexOf("/api/ossFiles/singleFile") >= 0 || requestURI.indexOf("/api/sysUsers/audit") >= 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().println(ApiRes.fail(ApiCodeEnum.SYS_USER_UN_AUDIT).toJSONString());
|
||||
response.getWriter().flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.bill;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.jeequan.jeepay.core.constants.CS;
|
||||
import com.jeequan.jeepay.core.exception.BizException;
|
||||
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
|
||||
import com.jeequan.jeepay.core.interfaces.bill.IReconciliationService;
|
||||
import com.jeequan.jeepay.core.interfaces.paychannel.IBillDownloadService;
|
||||
import com.jeequan.jeepay.core.model.bill.ChannelBill;
|
||||
import com.jeequan.jeepay.core.model.bill.ChannelBillRQ;
|
||||
import com.jeequan.jeepay.core.model.bill.ChannelBillRS;
|
||||
import com.jeequan.jeepay.core.model.bill.LocalBill;
|
||||
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
|
||||
import com.jeequan.jeepay.core.utils.JeepayKit;
|
||||
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
|
||||
import com.jeequan.jeepay.db.config.dynamic.DataSourceSwitch;
|
||||
import com.jeequan.jeepay.db.config.dynamic.DynamicDataSource;
|
||||
import com.jeequan.jeepay.db.entity.*;
|
||||
import com.jeequan.jeepay.service.impl.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/***
|
||||
* 对账接口
|
||||
*
|
||||
* @author zx
|
||||
* @date 2022/3/12 14:54
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ReconciliationService implements IReconciliationService {
|
||||
|
||||
private static final long CHECK_PAGE_SIZE = 1; // 本地订单对账分页数量
|
||||
|
||||
@Autowired private PayInterfaceConfigService payInterfaceConfigService;
|
||||
@Autowired private IConfigContextQueryService configContextQueryService;
|
||||
@Autowired private CheckBatchService checkBatchService;
|
||||
@Autowired private CheckChannelBillService channelBillService;
|
||||
@Autowired private CheckChannelBillService checkChannelBillService;
|
||||
@Autowired private CheckDiffService checkDiffService;
|
||||
@Autowired private PayOrderService payOrderService;
|
||||
@Autowired private RefundOrderService refundOrderService;
|
||||
|
||||
/**
|
||||
* 对账规则:ifCode_channelMchNo_billDate为一个批次,一批一批的对账,即相当于根据 channelMchNo 对账,channelMchNo来自于 PayInterfaceConfig
|
||||
* 每个PayInterfaceConfig 对应着一个 商户应用/服务商 和一个channelMchNo,但它们是多对一的关系,多个商户应用可能配置相同的channelMchNo
|
||||
* 根据channelMchNo查询渠道账单,确保了渠道账单只查询一次,不会出现重复
|
||||
* 根据商户应用查询本地订单会有漏洞:只查询了某一个商户应用的订单,所以使用去重的channelMchNo对账会出现漏掉本地订单的情况
|
||||
* 解决漏洞:匹配到 channelMchNo 对应的所有 商户应用,查询本地订单时使用
|
||||
* */
|
||||
@Override
|
||||
public void queryChannelBillAndCheck(String ifCode, Date billDate) {
|
||||
|
||||
IBillDownloadService billDownloadService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "BillDownloadService", IBillDownloadService.class);
|
||||
if (billDownloadService == null) {
|
||||
log.info("支付接口:{},暂不支持对账!", ifCode);
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询去重的渠道商户号 和 本地支付配置
|
||||
List<ChannelBillRQ> channelBillRQList = buildDistinctIfParams(ifCode);
|
||||
if(CollUtil.isEmpty(channelBillRQList)){
|
||||
log.info("支付接口:{},无支付参数配置!", ifCode);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 支付接口ifCode={},开始对账 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓", ifCode);
|
||||
checkBills4SingleChannel(billDownloadService, ifCode, billDate, channelBillRQList);
|
||||
log.info("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 支付接口ifCode={},对账结束 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑", ifCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadBill4Batch(com.jeequan.jeepay.core.entity.CheckBatch checkBatch, String reloadType) {
|
||||
|
||||
String ifCode = checkBatch.getIfCode();
|
||||
|
||||
IBillDownloadService billDownloadService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "BillDownloadService", IBillDownloadService.class);
|
||||
if (billDownloadService == null) {
|
||||
log.info("支付接口:{},暂不支持对账!", ifCode);
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析失败的批次,无渠道账单,本地对应账单会存入差异表,因此需要清掉差异表相关数据
|
||||
if (reloadType.equals("release") && checkBatch.getReleaseState() == CS.NO) {
|
||||
checkDiffService.removeDiffByBatchNo(checkBatch.getBatchNo());
|
||||
}
|
||||
// 解析成功并且处理状态未完成的批次,此时不再下载对账单,应保留渠道账单,需清空差异账单
|
||||
else if (reloadType.equals("check") && checkBatch.getReleaseState() == CS.YES && checkBatch.getState() == CS.NO) {
|
||||
checkDiffService.removeDiffByBatchNo(checkBatch.getBatchNo());
|
||||
}else {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ChannelBillRQ> channelBillRQList = buildDistinctIfParams(ifCode);
|
||||
if (CollUtil.isEmpty(channelBillRQList)) {
|
||||
return;
|
||||
}
|
||||
List<ChannelBillRQ> collect = channelBillRQList.stream().filter(channelBillRQ -> channelBillRQ.getChannelMchNo().equals(checkBatch.getChannelMchNo())).collect(Collectors.toList());
|
||||
if (CollUtil.isEmpty(collect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelBillRQ channelBillRQ = collect.get(0);
|
||||
|
||||
checkBills4Batch(billDownloadService, ifCode, checkBatch.getBillDate(), channelBillRQ);
|
||||
}
|
||||
|
||||
// 处理差异
|
||||
@Override
|
||||
public void processCheckDiffBills(Date billDate) {
|
||||
|
||||
// 处理开始时间:对账日期前三天
|
||||
Date beginDate = DateUtil.offsetDay(billDate, -3);
|
||||
|
||||
List<CheckDiff> checkDiffList = checkDiffService.list(CheckDiff.gw()
|
||||
.in(CheckDiff::getHandleState, Arrays.asList(CheckDiff.HANDLE_STATE_WAITING, CheckDiff.HANDLE_STATE_HANG_UP))
|
||||
.in(CheckDiff::getDiffType, Arrays.asList(CheckDiff.DIFF_TYPE_LOCAL, CheckDiff.DIFF_TYPE_CHANNEL))
|
||||
.ge(CheckDiff::getBillDate, DateUtil.beginOfDay(beginDate))
|
||||
.le(CheckDiff::getBillDate, DateUtil.endOfDay(billDate))
|
||||
);
|
||||
|
||||
Map<String, CheckDiff> localDiffMap = new HashMap<>(); // 本地多帐数据
|
||||
Map<String, CheckDiff> channelDiffMap = new HashMap<>(); // 渠道多帐数据
|
||||
|
||||
checkDiffList.forEach(checkDiff -> {
|
||||
if (CheckDiff.DIFF_TYPE_LOCAL.equals(checkDiff.getDiffType())) {
|
||||
localDiffMap.put(checkDiff.getOrderId(), checkDiff);
|
||||
}else {
|
||||
channelDiffMap.put(checkDiff.getOrderId(), checkDiff);
|
||||
}
|
||||
});
|
||||
|
||||
// 平账数据
|
||||
List<CheckDiff> checkSuccessList = new ArrayList<>();
|
||||
|
||||
// 核对本地多帐和渠道多帐数据,将平账数据取出
|
||||
localDiffMap.forEach((key, localCheckDiff) -> {
|
||||
if (channelDiffMap.containsKey(key)) {
|
||||
|
||||
CheckDiff channelCheckDiff = channelDiffMap.get(key);
|
||||
if (localCheckDiff.getAmount().equals(channelCheckDiff.getChannelAmount()) && localCheckDiff.getFeeAmount().equals(channelCheckDiff.getChannelFeeAmount())) {
|
||||
checkSuccessList.add(localCheckDiff);
|
||||
checkSuccessList.add(channelCheckDiff);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 更新平账数据为已处理
|
||||
checkDiffService.updateHandleStateSuccess(checkSuccessList);
|
||||
|
||||
// 三天前的差异对账单,状态由挂单改为未处理
|
||||
checkDiffService.updateHandleStateHangUp2Waiting(beginDate);
|
||||
|
||||
// 更新三天内的批次统计数据
|
||||
checkBatchService.updateUnHandleCountAndState4CheckDiff(checkSuccessList);
|
||||
}
|
||||
|
||||
/** 渠道对账 */
|
||||
public void checkBills4SingleChannel(IBillDownloadService billDownloadService, String ifCode, Date billDate, List<ChannelBillRQ> channelBillRQList) {
|
||||
// 查询渠道对账单 && 入库
|
||||
for (ChannelBillRQ channelBillRQ : channelBillRQList) {
|
||||
try {
|
||||
checkBills4Batch(billDownloadService, ifCode, billDate, channelBillRQ);
|
||||
}catch (Exception e) {
|
||||
log.error("对账单处理失败,支付接口:{},渠道商户号:{},对账日期:{}", ifCode, channelBillRQ.getChannelMchNo(), billDate, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 构建全部服务商、商户的渠道商户号与服务商或应用的配置关系 */
|
||||
public List<ChannelBillRQ> buildDistinctIfParams(String ifCode) {
|
||||
List<ChannelBillRQ> channelBillRQList = payInterfaceConfigService.selectDistinctIsvIfParams(ifCode); // 查询服务商渠道号 与 服务商号及配置参数对应关系
|
||||
channelBillRQList.addAll(payInterfaceConfigService.selectDistinctIsvSubMchIfParams(ifCode)); // 查询特约商户 与 商户应用及配置参数对应关系
|
||||
channelBillRQList.addAll(payInterfaceConfigService.selectDistinctNormalMchIfParams(ifCode)); // 查询普通商户 与 商户应用及配置参数对应关系
|
||||
return channelBillRQList;
|
||||
}
|
||||
|
||||
public void checkBills4Batch(IBillDownloadService billDownloadService, String ifCode, Date billDate, ChannelBillRQ channelBillRQ) {
|
||||
|
||||
String channelMchNo = channelBillRQ.getChannelMchNo(); // 渠道商户号
|
||||
List<String> infoIdList = channelBillRQ.getInfoIdList(); // channelMchNo 对应的 全部商户应用appId或服务商号集合
|
||||
|
||||
String batchNo = checkBatchService.getBatchNo(ifCode, channelMchNo, billDate);
|
||||
log.info("批次对账开始,对账批次号:{}", batchNo);
|
||||
|
||||
// 查询当前批次信息
|
||||
CheckBatch dbCheckBatch = checkBatchService.getById(batchNo);
|
||||
|
||||
// 当前批次已解析成功,直接进行对账
|
||||
if (dbCheckBatch != null && dbCheckBatch.getReleaseState() == CS.YES) {
|
||||
checkBills4Batch(dbCheckBatch, infoIdList, channelBillRQ.getInfoType());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 解析对账单 && 对账
|
||||
MchAppConfigContext mchAppConfigContext = null;
|
||||
if (ChannelBillRQ.INFO_TYPE_SUB.equals(channelBillRQ.getInfoType()) || ChannelBillRQ.INFO_TYPE_NORMAL.equals(channelBillRQ.getInfoType())) {
|
||||
mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfo(channelBillRQ.getInfoId());
|
||||
if (mchAppConfigContext == null) {
|
||||
throw new BizException("获取商户应用信息失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 下载并转为系统的标准对账单
|
||||
ChannelBillRS channelBillRes = billDownloadService.convertStandardBill(channelBillRQ, mchAppConfigContext, billDate, ifCode);
|
||||
if (channelBillRes == null || channelBillRes.getCheckBatch() == null) {
|
||||
throw new BizException("对账失败,对账批次为空!");
|
||||
}
|
||||
|
||||
// 保存对账批次,对账批次下载/解析失败的,直接结束
|
||||
CheckBatch checkBatch = checkBatchService.saveOrUpdateCheckBatch(channelBillRes.getCheckBatch());
|
||||
if (checkBatch.getReleaseState() == CS.NO) {
|
||||
throw new BizException(checkBatch.getReleaseErrMsg());
|
||||
}
|
||||
|
||||
// 服务商维度、服务商子商户维度,不对账的渠道子商户号集合
|
||||
if (StringUtils.equalsAny(channelBillRQ.getInfoType(), ChannelBillRQ.INFO_TYPE_ISV, ChannelBillRQ.INFO_TYPE_SUB)) {
|
||||
Set<String> ignoreCheckBillMchNos = channelBillRQ.getIgnoreCheckBillMchNos();
|
||||
if (CollUtil.isNotEmpty(ignoreCheckBillMchNos) && CollUtil.isNotEmpty(channelBillRes.getChannelBillList())) {
|
||||
List<ChannelBill> filterList = channelBillRes.getChannelBillList().stream().filter(channelBill -> !ignoreCheckBillMchNos.contains(channelBill.getChannelMchNo())).collect(Collectors.toList());
|
||||
channelBillRes.setChannelBillList(filterList);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存渠道对账单
|
||||
channelBillService.saveChannelBills(channelBillRes.getChannelBillList());
|
||||
|
||||
// 当前批次对账
|
||||
checkBills4Batch(checkBatch, infoIdList, channelBillRQ.getInfoType());
|
||||
}
|
||||
|
||||
/** 单个批次对账 */
|
||||
public void checkBills4Batch(CheckBatch checkBatch, List<String> infoIdList, String infoType) {
|
||||
|
||||
// 查询当前批次渠道对账单
|
||||
List<CheckChannelBill> channelList = channelBillService.selectListByCheckBatchNo(checkBatch.getBatchNo());
|
||||
|
||||
long pageNo = 1; // 页码: 默认第一页
|
||||
long[] totalPayOrderPage = { 1 }; // 支付订单总页码
|
||||
List<LocalBill> totalLocalDiffBills = new ArrayList<>(); // 所有本地多帐差异
|
||||
List<CheckDiff> totalChannelDiffBills = new ArrayList<>(); // 所有渠道多帐差异
|
||||
List<CheckDiff> totalOrderDiffBills = new ArrayList<>(); // 所有金额差异
|
||||
|
||||
// 本地账单数据统计
|
||||
CheckBatch updateDataRecord = checkBatchService.initLocalCheckBatchData(checkBatch);
|
||||
|
||||
while (true) {
|
||||
// 2、处理当前支付接口本地订单数据
|
||||
List<LocalBill> localBillList = getLocalOrderBills(pageNo++, totalPayOrderPage, updateDataRecord, infoIdList, infoType);
|
||||
|
||||
// 本地账单为空,渠道也为空,对账结束
|
||||
if (CollUtil.isEmpty(localBillList) && CollUtil.isEmpty(channelList)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 本地账单为空,渠道不为空,渠道账单记录为差异订单,类型为渠道多帐
|
||||
if (CollUtil.isEmpty(localBillList) && CollUtil.isNotEmpty(channelList)) {
|
||||
totalChannelDiffBills.addAll(checkDiffService.covertChannelBillToCheckDiff(channelList));
|
||||
break;
|
||||
}
|
||||
|
||||
// 本地账单不为空,渠道为空,本地账单记录为差异订单,类型为本地多帐,继续循环
|
||||
if (CollUtil.isNotEmpty(localBillList) && CollUtil.isEmpty(channelList)) {
|
||||
totalLocalDiffBills.addAll(localBillList);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ↓↓↓↓↓↓↓↓↓↓ 本地账单不为空,渠道不为空 ↓↓↓↓↓↓↓↓↓↓↓
|
||||
// 3、核对渠道、本地数据,得到差异订单
|
||||
checkBills(channelList, localBillList, totalLocalDiffBills, totalOrderDiffBills);
|
||||
}
|
||||
|
||||
// 差异订单 存入差异表
|
||||
checkDiffService.saveBatchDiffBills(totalLocalDiffBills, totalChannelDiffBills, totalOrderDiffBills, updateDataRecord);
|
||||
log.info("批次对账结束,对账批次号:{},本地多账总数={},渠道多账总数={},订单差异总数={}",
|
||||
checkBatch.getBatchNo(), totalLocalDiffBills.size(), totalChannelDiffBills.size(), totalOrderDiffBills.size());
|
||||
}
|
||||
|
||||
|
||||
// 对账,差异存入差异表
|
||||
private void checkBills(List<CheckChannelBill> channelBillList, List<LocalBill> localBillList,
|
||||
List<LocalBill> totalLocalDiffBills, List<CheckDiff> totalOrderDiffBills) {
|
||||
|
||||
// 本地账单数据转Map,key -> 商户单号,value:账单对象
|
||||
Map<String, LocalBill> localBillMap = localBillList.stream().collect(Collectors.toMap(LocalBill::getOrderId, o -> o));
|
||||
|
||||
// 遍历渠道账单
|
||||
Iterator<CheckChannelBill> it = channelBillList.iterator();
|
||||
while (it.hasNext()) {
|
||||
CheckChannelBill channelBill = it.next(); // 渠道对账单
|
||||
|
||||
// 支付订单有平台订单号,退款也有平台退款商户号的情况,其他情况见了再优化
|
||||
if (StringUtils.isNotBlank(channelBill.getOrderId())) {
|
||||
if (localBillMap.containsKey(channelBill.getOrderId())) {
|
||||
LocalBill localBill = localBillMap.get(channelBill.getOrderId()); // 本地对账单
|
||||
|
||||
// 校验订单金额(订单号一致,金额不等,本地、渠道两笔账单记录为订单差异并保存至差异表)
|
||||
// 校验手续费,只有支付订单校验手续费
|
||||
if (!localBill.getAmount().equals(channelBill.getChannelAmount()) ||
|
||||
(CheckChannelBill.BILL_TYPE_PAY.equals(localBill.getBillType()) && !localBill.getFeeAmount().equals(channelBill.getChannelFeeAmount()))) {
|
||||
// 金额不一致,记录为订单差异类型,本地、渠道数据需删除当前账单
|
||||
totalOrderDiffBills.add(checkDiffService.covertToOrderCheckDiff(channelBill, localBill, CheckDiff.DIFF_TYPE_ORDER));
|
||||
}
|
||||
|
||||
// 金额一致,本地、渠道数据删除当前账单
|
||||
localBillMap.remove(localBill.getOrderId());
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 当前循环本地多帐订单存入总差异列表
|
||||
if (CollUtil.isNotEmpty(localBillMap)) {
|
||||
totalLocalDiffBills.addAll(new ArrayList<>(localBillMap.values()));
|
||||
}
|
||||
}
|
||||
|
||||
// 生成本地对账数据
|
||||
@DataSourceSwitch(DynamicDataSource.DataSourceTypeEnum.SLAVE)
|
||||
private List<LocalBill> getLocalOrderBills(long pageNo, long[] totalPayOrderPage, CheckBatch updateDataRecord, List<String> infoIdList, String infoType) {
|
||||
|
||||
String ifCode = updateDataRecord.getIfCode();
|
||||
Date billDate = updateDataRecord.getBillDate();
|
||||
|
||||
if (pageNo <= totalPayOrderPage[0]) {
|
||||
// 查询支付成功的,部分退款和全额退款均认为交易成功记录
|
||||
IPage<PayOrder> payOrderPage = payOrderService.page(getIPage(pageNo), PayOrder.gw()
|
||||
.select(PayOrder::getPayOrderId, PayOrder::getMchNo, PayOrder::getMchName, PayOrder::getAppId, PayOrder::getMchOrderNo, PayOrder::getAmount,
|
||||
PayOrder::getMchOrderFeeAmount, PayOrder::getRefundAmount, PayOrder::getState, PayOrder::getSuccessTime, PayOrder::getCreatedAt)
|
||||
.and(i -> i.eq(PayOrder::getState, PayOrder.STATE_SUCCESS).or().in(PayOrder::getRefundState, Arrays.asList(PayOrder.REFUND_STATE_SUB, PayOrder.REFUND_STATE_ALL)))
|
||||
.in(ChannelBillRQ.INFO_TYPE_ISV.equals(infoType), PayOrder::getIsvNo, infoIdList)
|
||||
.in(!ChannelBillRQ.INFO_TYPE_ISV.equals(infoType), PayOrder::getAppId, infoIdList)
|
||||
.eq(PayOrder::getIfCode, ifCode)
|
||||
.ge(PayOrder::getCreatedAt, billDate)
|
||||
.le(PayOrder::getCreatedAt, DateUtil.endOfDay(billDate))
|
||||
);
|
||||
|
||||
totalPayOrderPage[0] = payOrderPage.getTotal();
|
||||
|
||||
// 当前页码 <= 订单表总页码
|
||||
if (CollUtil.isNotEmpty(payOrderPage.getRecords())) {
|
||||
return covertPayOrderToLocalBill(payOrderPage.getRecords(), updateDataRecord);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 当前页码 > 订单总页码,开始查询退款订单表,退款页码=当前页码 - 支付订单总页码
|
||||
long refundPageNo = pageNo - totalPayOrderPage[0];
|
||||
|
||||
IPage<RefundOrder> refundOrderPage = refundOrderService.page(getIPage(refundPageNo), RefundOrder.gw()
|
||||
.select(RefundOrder::getRefundOrderId, RefundOrder::getMchNo, RefundOrder::getMchName, RefundOrder::getAppId, RefundOrder::getMchRefundNo,
|
||||
RefundOrder::getPayAmount, RefundOrder::getRefundAmount, RefundOrder::getState, RefundOrder::getSuccessTime, RefundOrder::getCreatedAt)
|
||||
.eq(RefundOrder::getState, RefundOrder.STATE_SUCCESS)
|
||||
.in(ChannelBillRQ.INFO_TYPE_ISV.equals(infoType), RefundOrder::getIsvNo, infoIdList)
|
||||
.in(!ChannelBillRQ.INFO_TYPE_ISV.equals(infoType), RefundOrder::getAppId, infoIdList)
|
||||
.eq(RefundOrder::getIfCode, ifCode)
|
||||
.ge(RefundOrder::getCreatedAt, billDate)
|
||||
.le(RefundOrder::getCreatedAt, DateUtil.endOfDay(billDate))
|
||||
);
|
||||
|
||||
return covertRefundOrderToLocalBill(refundOrderPage.getRecords(), updateDataRecord);
|
||||
}
|
||||
|
||||
|
||||
// 支付订单 转换为 本地标准账单
|
||||
private List<LocalBill> covertPayOrderToLocalBill(List<PayOrder> payOrderList, CheckBatch updateDataRecord) {
|
||||
if (CollUtil.isEmpty(payOrderList)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 统计数组,数据分别为:订单总数 总金额 总手续费
|
||||
final long[] totalOrderData = { 0, 0, 0 };
|
||||
|
||||
List<LocalBill> localBillList = payOrderList.stream().map(payOrder -> {
|
||||
LocalBill localOrderBill = new LocalBill();
|
||||
localOrderBill.setIfCode(updateDataRecord.getIfCode());
|
||||
localOrderBill.setBillDate(updateDataRecord.getBillDate());
|
||||
localOrderBill.setChannelMchNo(updateDataRecord.getChannelMchNo());
|
||||
localOrderBill.setOrderId(payOrder.getPayOrderId());
|
||||
localOrderBill.setBillType(CheckChannelBill.BILL_TYPE_PAY);
|
||||
localOrderBill.setMchNo(payOrder.getMchNo());
|
||||
localOrderBill.setMchName(payOrder.getMchName());
|
||||
localOrderBill.setMchAppId(payOrder.getAppId());
|
||||
localOrderBill.setMchOrderNo(payOrder.getMchOrderNo());
|
||||
localOrderBill.setAmount(payOrder.getAmount());
|
||||
localOrderBill.setFeeAmount(payOrder.getMchOrderFeeAmount());
|
||||
localOrderBill.setOrderState(payOrder.getState());
|
||||
localOrderBill.setOrderSuccessAt(payOrder.getSuccessTime());
|
||||
localOrderBill.setOrderCreateAt(payOrder.getCreatedAt());
|
||||
|
||||
totalOrderData[0]++;
|
||||
totalOrderData[1] += payOrder.getAmount();
|
||||
totalOrderData[2] += payOrder.getMchOrderFeeAmount();
|
||||
|
||||
return localOrderBill;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
updateDataRecord.setTotalCount(updateDataRecord.getTotalCount() + Math.toIntExact(totalOrderData[0]));
|
||||
updateDataRecord.setTotalAmount(updateDataRecord.getTotalAmount() + totalOrderData[1]);
|
||||
updateDataRecord.setTotalFee(updateDataRecord.getTotalFee() + totalOrderData[2]);
|
||||
|
||||
return localBillList;
|
||||
}
|
||||
|
||||
// 退款订单 转换为 本地标准账单
|
||||
private List<LocalBill> covertRefundOrderToLocalBill(List<RefundOrder> refundOrderList, CheckBatch updateDataRecord) {
|
||||
if (CollUtil.isEmpty(refundOrderList)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 统计数组,数据分别为:订单总数 总金额 总手续费
|
||||
final long[] totalRefundOrderData = { 0, 0, 0 };
|
||||
|
||||
List<LocalBill> localBillList = refundOrderList.stream().map(refundOrder -> {
|
||||
LocalBill localOrderBill = new LocalBill();
|
||||
localOrderBill.setIfCode(updateDataRecord.getIfCode());
|
||||
localOrderBill.setBillDate(updateDataRecord.getBillDate());
|
||||
localOrderBill.setChannelMchNo(updateDataRecord.getChannelMchNo());
|
||||
localOrderBill.setOrderId(refundOrder.getRefundOrderId());
|
||||
localOrderBill.setBillType(CheckChannelBill.BILL_TYPE_REFUND);
|
||||
localOrderBill.setMchNo(refundOrder.getMchNo());
|
||||
localOrderBill.setMchName(refundOrder.getMchName());
|
||||
localOrderBill.setMchAppId(refundOrder.getAppId());
|
||||
localOrderBill.setMchOrderNo(refundOrder.getMchRefundNo());
|
||||
localOrderBill.setAmount(refundOrder.getRefundAmount());
|
||||
localOrderBill.setRefundState(refundOrder.getState());
|
||||
localOrderBill.setOrderSuccessAt(refundOrder.getSuccessTime());
|
||||
localOrderBill.setOrderCreateAt(refundOrder.getCreatedAt());
|
||||
|
||||
totalRefundOrderData[0]++;
|
||||
totalRefundOrderData[1] += refundOrder.getRefundAmount();
|
||||
totalRefundOrderData[2] += refundOrder.getRefundFeeAmount();
|
||||
|
||||
return localOrderBill;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
updateDataRecord.setTotalRefundCount(updateDataRecord.getTotalRefundCount() + Math.toIntExact(totalRefundOrderData[0]));
|
||||
updateDataRecord.setTotalRefundAmount(updateDataRecord.getTotalRefundAmount() + totalRefundOrderData[1]);
|
||||
updateDataRecord.setTotalFee(updateDataRecord.getTotalFee() - totalRefundOrderData[2]);
|
||||
|
||||
return localBillList;
|
||||
}
|
||||
|
||||
protected IPage getIPage(long pageIndex){
|
||||
return new Page(pageIndex, CHECK_PAGE_SIZE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.email;
|
||||
|
||||
import com.jeequan.jeepay.core.exception.BizException;
|
||||
import com.jeequan.jeepay.core.model.DBEmailConfig;
|
||||
import com.jeequan.jeepay.service.impl.SysConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.mail.Authenticator;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.PasswordAuthentication;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeUtility;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 发送电子邮件相关的功能service
|
||||
* @author nb2020
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class EmailManager {
|
||||
|
||||
@Autowired
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
public JavaMailSender mailSender;
|
||||
|
||||
public DBEmailConfig getInstance(){
|
||||
DBEmailConfig emailConfig = sysConfigService.getEmailConfig();
|
||||
if(emailConfig == null){
|
||||
throw new BizException("未开启邮箱配置");
|
||||
}
|
||||
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
|
||||
javaMailSender.setHost(emailConfig.getEmailHost());
|
||||
javaMailSender.setDefaultEncoding("utf-8");
|
||||
javaMailSender.setPort(emailConfig.getEmailPort());
|
||||
javaMailSender.setUsername(emailConfig.getEmailAccountNo());
|
||||
javaMailSender.setPassword(emailConfig.getEmailPassword());
|
||||
Properties props = System.getProperties();
|
||||
props.put("mail.smtp.host", emailConfig.getEmailHost());
|
||||
props.put("mail.username", emailConfig.getEmailAccountNo());
|
||||
props.put("mail.password", emailConfig.getEmailPassword());
|
||||
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
|
||||
props.put("mail.smtp.socketFactory.fallback", "false");
|
||||
props.put("mail.smtp.port", emailConfig.getEmailPort());
|
||||
props.put("mail.smtp.socketFactory.port", emailConfig.getEmailPort());
|
||||
props.put("mail.smtp.auth", "true");
|
||||
Session session = Session.getDefaultInstance(props, new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(props.getProperty("mail.username"), props.getProperty("mail.password"));
|
||||
}
|
||||
});
|
||||
javaMailSender.setSession(session);
|
||||
mailSender = javaMailSender;
|
||||
return emailConfig;
|
||||
}
|
||||
|
||||
|
||||
public EmailManager() {
|
||||
System.setProperty("mail.mime.splitlongparameters", "false");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件
|
||||
* @param tos 接收人邮箱数组
|
||||
* @param ccs 抄送人邮箱数组
|
||||
* @param title 邮件主题
|
||||
* @param content 邮件内容
|
||||
* @param workBook 附件excel
|
||||
*/
|
||||
public void sendEmail(String[] tos, String[] ccs, String title, String content, XSSFWorkbook workBook, String workBookName) {
|
||||
|
||||
DBEmailConfig config = getInstance();
|
||||
|
||||
MimeMessage mimeMessage = mailSender.createMimeMessage();
|
||||
|
||||
MimeMessageHelper mimeMessageHelper;
|
||||
|
||||
try {
|
||||
mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "utf-8");
|
||||
mimeMessageHelper.setTo(tos);
|
||||
if (ccs != null) {
|
||||
mimeMessageHelper.setCc(ccs);
|
||||
}
|
||||
mimeMessageHelper.setFrom(config.getEmailName() + "<" + config.getEmailAccountNo() + ">");
|
||||
mimeMessageHelper.setSubject(title);
|
||||
mimeMessageHelper.setText(content);
|
||||
if(workBook != null){
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
workBook.write(out);
|
||||
byte[] bookByteArr = out.toByteArray();
|
||||
InputStream in = new ByteArrayInputStream(bookByteArr);
|
||||
String newFileName = MimeUtility.encodeWord(workBookName, "utf-8", "B");
|
||||
mimeMessageHelper.addAttachment(newFileName, new ByteArrayResource(IOUtils.toByteArray(in), "application/vnd.ms-excel;charset=UTF-8"));
|
||||
}
|
||||
} catch (IOException | MessagingException e) {
|
||||
log.error("邮件发送失败");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
mailSender.send(mimeMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.qrshell;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.jeequan.jeepay.components.oss.config.OssYmlConfig;
|
||||
import com.jeequan.jeepay.components.oss.model.OssFileConfig;
|
||||
import com.jeequan.jeepay.core.utils.FileKit;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/***
|
||||
* 抽象类: 模板生成器
|
||||
*
|
||||
* @author terrfly
|
||||
* @date 2022/1/18 12:33
|
||||
*/
|
||||
public abstract class AbstractGenerator {
|
||||
|
||||
public static final String DEFAULT_FONT = "微软雅黑"; // 微软雅黑 宋体
|
||||
|
||||
@Autowired
|
||||
private OssYmlConfig ossYmlConfig;
|
||||
|
||||
/**
|
||||
* 功能描述:生成图片的 BufferedImage
|
||||
*
|
||||
* @param configModelStr 自行转换
|
||||
* @param qrUrlContent 二维码内容
|
||||
* @param qrcId 码牌ID
|
||||
* @param isViewFlag 是否预览模式(将等比例缩小图片)
|
||||
* @Return: java.awt.image.BufferedImage
|
||||
* @Author: terrfly
|
||||
* @Date: 2022/1/18 15:52
|
||||
*/
|
||||
|
||||
public abstract BufferedImage genQrImgBuffer(String configModelStr, String qrUrlContent, Long qrcId, String mchStoreName, boolean isViewFlag) throws IOException, WriterException;
|
||||
|
||||
|
||||
protected BufferedImage getStaticImg(String img) throws IOException {
|
||||
return ImageIO.read(new ClassPathResource("static/images/qrshell" + img).getInputStream()); //
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件进行缓存 & 获取图片
|
||||
**/
|
||||
protected File downloadAndGetCacheFile(String url) {
|
||||
|
||||
// 下载地址为空
|
||||
if (StringUtils.isEmpty(url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String filePath = ossYmlConfig.getOss().getFilePublicPath() + File.separator + OssFileConfig.BIZ_TYPE.QRC + File.separator + FileKit.getUrlFileName(url);
|
||||
|
||||
// 存在
|
||||
if (new File(filePath).exists()) {
|
||||
return new File(filePath);
|
||||
}
|
||||
|
||||
// 下载
|
||||
HttpUtil.downloadFile(url, filePath);
|
||||
return new File(filePath);
|
||||
}
|
||||
|
||||
|
||||
public static String convertImgBase64(BufferedImage bufferedImage) throws IOException {
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();//io流
|
||||
ImageIO.write(bufferedImage, "jpg", baos);//写入流中
|
||||
byte[] bytes = baos.toByteArray();//转换成字节
|
||||
String imgBase64 = Base64.encode(bytes);
|
||||
return imgBase64.replace("\n", "").replace("\r", "");//删除 \r\n
|
||||
}
|
||||
|
||||
public static ByteArrayInputStream convertInputStream(BufferedImage bufferedImage) throws IOException {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ImageIO.write(bufferedImage, "png", os);
|
||||
return new ByteArrayInputStream(os.toByteArray());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.qrshell;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 通用配置项
|
||||
*
|
||||
* @author terrfly
|
||||
* @date 2022/3/3 9:54
|
||||
*/
|
||||
@Data
|
||||
public class CommonConfigModel {
|
||||
|
||||
/** 是否显示ID号, 初始化model为false **/
|
||||
protected boolean showIdFlag;
|
||||
|
||||
/** 是否显示门店名称 **/
|
||||
protected boolean showStoreNameFlag;
|
||||
|
||||
/** 支付方式列表 **/
|
||||
protected List<PayType> payTypeList;
|
||||
|
||||
/** 背景颜色 */
|
||||
protected String bgColor;
|
||||
|
||||
/** 描述文本 */
|
||||
protected String descText;
|
||||
|
||||
/** 二维码中间图片 */
|
||||
protected String qrInnerImgUrl;
|
||||
|
||||
/** logo图片 */
|
||||
protected String logoImgUrl;
|
||||
|
||||
/** 二维码图片的支付方式 **/
|
||||
@Data
|
||||
public static class PayType{
|
||||
|
||||
/** 支付方式 name **/
|
||||
private String name;
|
||||
|
||||
/** 支付方式名称 自定义 **/
|
||||
private String alias;
|
||||
|
||||
/** 图片地址, 若内置类型 为: alipay, wxpay, unionpay , ysfpay 其他为url地址 **/
|
||||
private String imgUrl;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.qrshell;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* 来源:
|
||||
* https://www.coder.work/article/388801
|
||||
* http://www.camick.com/java/source/HSLColor.java
|
||||
* 新增: 颜色加深代码
|
||||
*
|
||||
* The HSLColor class provides methods to manipulate HSL (Hue, Saturation
|
||||
* Luminance) values to create a corresponding Color object using the RGB
|
||||
* ColorSpace.
|
||||
*
|
||||
* The HUE is the color, the Saturation is the purity of the color (with
|
||||
* respect to grey) and Luminance is the brightness of the color (with respect
|
||||
* to black and white)
|
||||
*
|
||||
* The Hue is specified as an angel between 0 - 360 degrees where red is 0,
|
||||
* green is 120 and blue is 240. In between you have the colors of the rainbow.
|
||||
* Saturation is specified as a percentage between 0 - 100 where 100 is fully
|
||||
* saturated and 0 approaches gray. Luminance is specified as a percentage
|
||||
* between 0 - 100 where 0 is black and 100 is white.
|
||||
*
|
||||
* In particular the HSL color space makes it easier change the Tone or Shade
|
||||
* of a color by adjusting the luminance value.
|
||||
*/
|
||||
public class HSLColor
|
||||
{
|
||||
private Color rgb;
|
||||
private float[] hsl;
|
||||
private float alpha;
|
||||
|
||||
/**
|
||||
* Create a HSLColor object using an RGB Color object.
|
||||
*
|
||||
* @param rgb the RGB Color object
|
||||
*/
|
||||
public HSLColor(Color rgb)
|
||||
{
|
||||
this.rgb = rgb;
|
||||
hsl = fromRGB( rgb );
|
||||
alpha = rgb.getAlpha() / 255.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a HSLColor object using individual HSL values and a default
|
||||
* alpha value of 1.0.
|
||||
*
|
||||
* @param h is the Hue value in degrees between 0 - 360
|
||||
* @param s is the Saturation percentage between 0 - 100
|
||||
* @param l is the Lumanance percentage between 0 - 100
|
||||
*/
|
||||
public HSLColor(float h, float s, float l)
|
||||
{
|
||||
this(h, s, l, 1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a HSLColor object using individual HSL values.
|
||||
*
|
||||
* @param h the Hue value in degrees between 0 - 360
|
||||
* @param s the Saturation percentage between 0 - 100
|
||||
* @param l the Lumanance percentage between 0 - 100
|
||||
* @param alpha the alpha value between 0 - 1
|
||||
*/
|
||||
public HSLColor(float h, float s, float l, float alpha)
|
||||
{
|
||||
hsl = new float[] {h, s, l};
|
||||
this.alpha = alpha;
|
||||
rgb = toRGB(hsl, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a HSLColor object using an an array containing the
|
||||
* individual HSL values and with a default alpha value of 1.
|
||||
*
|
||||
* @param hsl array containing HSL values
|
||||
*/
|
||||
public HSLColor(float[] hsl)
|
||||
{
|
||||
this(hsl, 1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a HSLColor object using an an array containing the
|
||||
* individual HSL values.
|
||||
*
|
||||
* @param hsl array containing HSL values
|
||||
* @param alpha the alpha value between 0 - 1
|
||||
*/
|
||||
public HSLColor(float[] hsl, float alpha)
|
||||
{
|
||||
this.hsl = hsl;
|
||||
this.alpha = alpha;
|
||||
rgb = toRGB(hsl, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a RGB Color object based on this HSLColor with a different
|
||||
* Hue value. The degrees specified is an absolute value.
|
||||
*
|
||||
* @param degrees - the Hue value between 0 - 360
|
||||
* @return the RGB Color object
|
||||
*/
|
||||
public Color adjustHue(float degrees)
|
||||
{
|
||||
return toRGB(degrees, hsl[1], hsl[2], alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a RGB Color object based on this HSLColor with a different
|
||||
* Luminance value. The percent specified is an absolute value.
|
||||
*
|
||||
* @param percent - the Luminance value between 0 - 100
|
||||
* @return the RGB Color object
|
||||
*/
|
||||
public Color adjustLuminance(float percent)
|
||||
{
|
||||
return toRGB(hsl[0], hsl[1], percent, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a RGB Color object based on this HSLColor with a different
|
||||
* Saturation value. The percent specified is an absolute value.
|
||||
*
|
||||
* @param percent - the Saturation value between 0 - 100
|
||||
* @return the RGB Color object
|
||||
*/
|
||||
public Color adjustSaturation(float percent)
|
||||
{
|
||||
return toRGB(hsl[0], percent, hsl[2], alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a RGB Color object based on this HSLColor with a different
|
||||
* Shade. Changing the shade will return a darker color. The percent
|
||||
* specified is a relative value.
|
||||
*
|
||||
* @param percent - the value between 0 - 100
|
||||
* @return the RGB Color object
|
||||
*/
|
||||
public Color adjustShade(float percent)
|
||||
{
|
||||
float multiplier = (100.0f - percent) / 100.0f;
|
||||
float l = Math.max(0.0f, hsl[2] * multiplier);
|
||||
|
||||
return toRGB(hsl[0], hsl[1], l, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a RGB Color object based on this HSLColor with a different
|
||||
* Tone. Changing the tone will return a lighter color. The percent
|
||||
* specified is a relative value.
|
||||
*
|
||||
* @param percent - the value between 0 - 100
|
||||
* @return the RGB Color object
|
||||
*/
|
||||
public Color adjustTone(float percent)
|
||||
{
|
||||
float multiplier = (100.0f + percent) / 100.0f;
|
||||
float l = Math.min(100.0f, hsl[2] * multiplier);
|
||||
|
||||
return toRGB(hsl[0], hsl[1], l, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Alpha value.
|
||||
*
|
||||
* @return the Alpha value.
|
||||
*/
|
||||
public float getAlpha()
|
||||
{
|
||||
return alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a RGB Color object that is the complementary color of this
|
||||
* HSLColor. This is a convenience method. The complementary color is
|
||||
* determined by adding 180 degrees to the Hue value.
|
||||
* @return the RGB Color object
|
||||
*/
|
||||
public Color getComplementary()
|
||||
{
|
||||
float hue = (hsl[0] + 180.0f) % 360.0f;
|
||||
return toRGB(hue, hsl[1], hsl[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Hue value.
|
||||
*
|
||||
* @return the Hue value.
|
||||
*/
|
||||
public float getHue()
|
||||
{
|
||||
return hsl[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HSL values.
|
||||
*
|
||||
* @return the HSL values.
|
||||
*/
|
||||
public float[] getHSL()
|
||||
{
|
||||
return hsl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Luminance value.
|
||||
*
|
||||
* @return the Luminance value.
|
||||
*/
|
||||
public float getLuminance()
|
||||
{
|
||||
return hsl[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RGB Color object represented by this HDLColor.
|
||||
*
|
||||
* @return the RGB Color object.
|
||||
*/
|
||||
public Color getRGB()
|
||||
{
|
||||
return rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Saturation value.
|
||||
*
|
||||
* @return the Saturation value.
|
||||
*/
|
||||
public float getSaturation()
|
||||
{
|
||||
return hsl[1];
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
String toString =
|
||||
"HSLColor[h=" + hsl[0] +
|
||||
",s=" + hsl[1] +
|
||||
",l=" + hsl[2] +
|
||||
",alpha=" + alpha + "]";
|
||||
|
||||
return toString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a RGB Color to it corresponding HSL values.
|
||||
*
|
||||
* @return an array containing the 3 HSL values.
|
||||
*/
|
||||
public static float[] fromRGB(Color color)
|
||||
{
|
||||
// Get RGB values in the range 0 - 1
|
||||
|
||||
float[] rgb = color.getRGBColorComponents( null );
|
||||
float r = rgb[0];
|
||||
float g = rgb[1];
|
||||
float b = rgb[2];
|
||||
|
||||
// Minimum and Maximum RGB values are used in the HSL calculations
|
||||
|
||||
float min = Math.min(r, Math.min(g, b));
|
||||
float max = Math.max(r, Math.max(g, b));
|
||||
|
||||
// Calculate the Hue
|
||||
|
||||
float h = 0;
|
||||
|
||||
if (max == min)
|
||||
h = 0;
|
||||
else if (max == r)
|
||||
h = ((60 * (g - b) / (max - min)) + 360) % 360;
|
||||
else if (max == g)
|
||||
h = (60 * (b - r) / (max - min)) + 120;
|
||||
else if (max == b)
|
||||
h = (60 * (r - g) / (max - min)) + 240;
|
||||
|
||||
// Calculate the Luminance
|
||||
|
||||
float l = (max + min) / 2;
|
||||
|
||||
// Calculate the Saturation
|
||||
|
||||
float s = 0;
|
||||
|
||||
if (max == min)
|
||||
s = 0;
|
||||
else if (l <= .5f)
|
||||
s = (max - min) / (max + min);
|
||||
else
|
||||
s = (max - min) / (2 - max - min);
|
||||
|
||||
return new float[] {h, s * 100, l * 100};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSL values to a RGB Color with a default alpha value of 1.
|
||||
* H (Hue) is specified as degrees in the range 0 - 360.
|
||||
* S (Saturation) is specified as a percentage in the range 1 - 100.
|
||||
* L (Lumanance) is specified as a percentage in the range 1 - 100.
|
||||
*
|
||||
* @param hsl an array containing the 3 HSL values
|
||||
*
|
||||
* @returns the RGB Color object
|
||||
*/
|
||||
public static Color toRGB(float[] hsl)
|
||||
{
|
||||
return toRGB(hsl, 1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSL values to a RGB Color.
|
||||
* H (Hue) is specified as degrees in the range 0 - 360.
|
||||
* S (Saturation) is specified as a percentage in the range 1 - 100.
|
||||
* L (Lumanance) is specified as a percentage in the range 1 - 100.
|
||||
*
|
||||
* @param hsl an array containing the 3 HSL values
|
||||
* @param alpha the alpha value between 0 - 1
|
||||
*
|
||||
* @returns the RGB Color object
|
||||
*/
|
||||
public static Color toRGB(float[] hsl, float alpha)
|
||||
{
|
||||
return toRGB(hsl[0], hsl[1], hsl[2], alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSL values to a RGB Color with a default alpha value of 1.
|
||||
*
|
||||
* @param h Hue is specified as degrees in the range 0 - 360.
|
||||
* @param s Saturation is specified as a percentage in the range 1 - 100.
|
||||
* @param l Lumanance is specified as a percentage in the range 1 - 100.
|
||||
*
|
||||
* @returns the RGB Color object
|
||||
*/
|
||||
public static Color toRGB(float h, float s, float l)
|
||||
{
|
||||
return toRGB(h, s, l, 1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSL values to a RGB Color.
|
||||
*
|
||||
* @param h Hue is specified as degrees in the range 0 - 360.
|
||||
* @param s Saturation is specified as a percentage in the range 1 - 100.
|
||||
* @param l Lumanance is specified as a percentage in the range 1 - 100.
|
||||
* @param alpha the alpha value between 0 - 1
|
||||
*
|
||||
* @returns the RGB Color object
|
||||
*/
|
||||
public static Color toRGB(float h, float s, float l, float alpha)
|
||||
{
|
||||
if (s <0.0f || s > 100.0f)
|
||||
{
|
||||
String message = "Color parameter outside of expected range - Saturation";
|
||||
throw new IllegalArgumentException( message );
|
||||
}
|
||||
|
||||
if (l <0.0f || l > 100.0f)
|
||||
{
|
||||
String message = "Color parameter outside of expected range - Luminance";
|
||||
throw new IllegalArgumentException( message );
|
||||
}
|
||||
|
||||
if (alpha <0.0f || alpha > 1.0f)
|
||||
{
|
||||
String message = "Color parameter outside of expected range - Alpha";
|
||||
throw new IllegalArgumentException( message );
|
||||
}
|
||||
|
||||
// Formula needs all values between 0 - 1.
|
||||
|
||||
h = h % 360.0f;
|
||||
h /= 360f;
|
||||
s /= 100f;
|
||||
l /= 100f;
|
||||
|
||||
float q = 0;
|
||||
|
||||
if (l < 0.5)
|
||||
q = l * (1 + s);
|
||||
else
|
||||
q = (l + s) - (s * l);
|
||||
|
||||
float p = 2 * l - q;
|
||||
|
||||
float r = Math.max(0, HueToRGB(p, q, h + (1.0f / 3.0f)));
|
||||
float g = Math.max(0, HueToRGB(p, q, h));
|
||||
float b = Math.max(0, HueToRGB(p, q, h - (1.0f / 3.0f)));
|
||||
|
||||
r = Math.min(r, 1.0f);
|
||||
g = Math.min(g, 1.0f);
|
||||
b = Math.min(b, 1.0f);
|
||||
|
||||
return new Color(r, g, b, alpha);
|
||||
}
|
||||
|
||||
private static float HueToRGB(float p, float q, float h)
|
||||
{
|
||||
if (h < 0) h += 1;
|
||||
|
||||
if (h > 1 ) h -= 1;
|
||||
|
||||
if (6 * h < 1)
|
||||
{
|
||||
return p + ((q - p) * 6 * h);
|
||||
}
|
||||
|
||||
if (2 * h < 1 )
|
||||
{
|
||||
return q;
|
||||
}
|
||||
|
||||
if (3 * h < 2)
|
||||
{
|
||||
return p + ( (q - p) * 6 * ((2.0f / 3.0f) - h) );
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
/** 颜色加深, 比例: 如加深 20%传入 20 ( 若为负数则取绝对值 ) **/
|
||||
public static final Color deepen(Color color, int scale){
|
||||
HSLColor hslColor = new HSLColor(color);
|
||||
return HSLColor.toRGB(hslColor.getHue(), hslColor.getSaturation(), Math.abs(hslColor.getLuminance() - scale) );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.qrshell;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* shellA 图片生成器
|
||||
*
|
||||
* @author terrfly
|
||||
* @date 2022/1/17 17:36
|
||||
*/
|
||||
@Data
|
||||
@Service
|
||||
public class ShellAGenerator extends AbstractGenerator {
|
||||
|
||||
/** 生成二维码图片的 buffer缓冲值 **/
|
||||
@Override
|
||||
public BufferedImage genQrImgBuffer(String configModelStr, String qrUrlContent, Long qrcId, String mchStoreName, boolean isViewFlag) throws IOException, WriterException {
|
||||
|
||||
CommonConfigModel configModel = JSON.parseObject(configModelStr, CommonConfigModel.class);
|
||||
|
||||
String qrId = "No." + qrcId.toString();
|
||||
|
||||
//图片的宽和高
|
||||
int imgWidth = 308 * 3; //924
|
||||
int imgHeight = 450 * 3; //1350
|
||||
|
||||
BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
//设置: 全局背景颜色为白色
|
||||
Graphics2D graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor(Color.white);//背景设置为白色
|
||||
graphic.fillRect(0, 0, imgWidth, imgHeight);
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
|
||||
//设置: 二维码背景颜色
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor( new Color(Integer.parseInt(StringUtils.defaultIfEmpty(configModel.getBgColor(), "#5094d5").substring(1),16)) );//背景色的设置
|
||||
graphic.fillRect(0, 330, imgWidth, 825);
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
|
||||
//设置:二维码上的文字
|
||||
if(StringUtils.isNotEmpty(configModel.getDescText())){
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor(Color.black);//背景设置为白色
|
||||
|
||||
Font font = new Font(AbstractGenerator.DEFAULT_FONT, Font.PLAIN, 48);
|
||||
FontMetrics metrics = graphic.getFontMetrics(font);
|
||||
int x = (imgWidth - metrics.stringWidth(configModel.getDescText())) / 2;
|
||||
|
||||
graphic.setFont(font); //字体、字型、字号
|
||||
graphic.drawString(configModel.getDescText(), x, 300); //画文字
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
|
||||
|
||||
if(configModel.isShowIdFlag()){
|
||||
//设置:二维码编号
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor(Color.white);//背景设置为白色
|
||||
|
||||
Font qrIdFont = new Font(AbstractGenerator.DEFAULT_FONT, Font.PLAIN, 27);
|
||||
FontMetrics metrics = graphic.getFontMetrics(qrIdFont);
|
||||
int x = (imgWidth - metrics.stringWidth(qrId)) / 2;
|
||||
|
||||
graphic.setFont(qrIdFont); //字体、字型、字号
|
||||
graphic.drawString(qrId , x, 1100); //画文字
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
|
||||
//设置:二维码背景图
|
||||
BufferedImage waterImg = getStaticImg("/shellA/i_bg.png"); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterImg, 120, 400, waterImg.getWidth(), waterImg.getHeight(), null);
|
||||
graphic.dispose();
|
||||
|
||||
//顶部 logo
|
||||
List<BufferedImage> topImgArray = new ArrayList<>();
|
||||
if(configModel.getPayTypeList() != null){
|
||||
for (CommonConfigModel.PayType payType : configModel.payTypeList) {
|
||||
if("unionpay".equals(payType.getName()) || "ysfpay".equals(payType.getName()) ||"wxpay".equals(payType.getName()) || "alipay".equals(payType.getName()) ){
|
||||
topImgArray.add(getStaticImg("/commons/" + "t_" + payType.getName() + ".png"));
|
||||
}else{
|
||||
File imgFile = downloadAndGetCacheFile(payType.getImgUrl()); // 下载图片
|
||||
if(imgFile != null){
|
||||
topImgArray.add(ImageIO.read(imgFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<int[]> areaArrays = getPayTypeLocation(topImgArray.size());
|
||||
if(areaArrays != null && !areaArrays.isEmpty()){
|
||||
for (int i = 0; i < areaArrays.size() ;i++) {
|
||||
BufferedImage waterImg1 = topImgArray.get(i); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterImg1, areaArrays.get(i)[0], areaArrays.get(i)[1], waterImg1.getWidth(), waterImg1.getHeight(), null);
|
||||
graphic.dispose();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//设置:二维码 190X190
|
||||
//生成真实的二维码图片
|
||||
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
|
||||
BitMatrix bitMatrix = new MultiFormatWriter().encode(qrUrlContent, BarcodeFormat.QR_CODE, 570, 570, hints);// 生成矩阵
|
||||
BufferedImage waterQrImg = MatrixToImageWriter.toBufferedImage(bitMatrix);
|
||||
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterQrImg, 178, 466, waterQrImg.getWidth(), waterQrImg.getHeight(), null);
|
||||
graphic.dispose();
|
||||
|
||||
|
||||
//二维码中间logo
|
||||
if(StringUtils.isNotEmpty(configModel.getQrInnerImgUrl() )){
|
||||
|
||||
File imgFile = downloadAndGetCacheFile(configModel.getQrInnerImgUrl());
|
||||
if(imgFile != null){
|
||||
//设置:底部logo
|
||||
BufferedImage waterLogo = ImageIO.read(imgFile); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterLogo, 420, 666, 80, 80, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
//底部logo
|
||||
if(StringUtils.isNotEmpty(configModel.getLogoImgUrl() )){
|
||||
|
||||
File imgFile = downloadAndGetCacheFile(configModel.getLogoImgUrl());
|
||||
if(imgFile != null){
|
||||
//设置:底部logo
|
||||
BufferedImage waterLogo = ImageIO.read(imgFile); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
|
||||
int bottomHeight = imgHeight - 330 - 825; //底部背景高度
|
||||
int graphicHeight = 330 + 825 + ((bottomHeight - waterLogo.getHeight()) / 2); //画图的高度
|
||||
|
||||
graphic.drawImage(waterLogo, ((imgWidth-waterLogo.getWidth()) / 2) , graphicHeight, waterLogo.getWidth(), waterLogo.getHeight(), null);
|
||||
graphic.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// 预览需要缩小三倍
|
||||
return isViewFlag ? zoomOutImage(bufferedImage, 2) : bufferedImage;
|
||||
}
|
||||
|
||||
|
||||
public static BufferedImage zoomOutImage(BufferedImage originalImage, Integer times){
|
||||
|
||||
int width = originalImage.getWidth()/times;
|
||||
if(width < 0){
|
||||
width=originalImage.getWidth();
|
||||
}
|
||||
int height = originalImage.getHeight()/times;
|
||||
if(height < 0){
|
||||
height=originalImage.getHeight();
|
||||
}
|
||||
BufferedImage newImage = new BufferedImage(width,height,originalImage.getType());
|
||||
|
||||
Graphics g = newImage.getGraphics();
|
||||
|
||||
g.drawImage(originalImage, 0,0,width,height,null);
|
||||
|
||||
g.dispose();
|
||||
|
||||
return newImage;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @Title: 构造图片
|
||||
* @Description: 生成水印并返回java.awt.image.BufferedImage
|
||||
* @param file
|
||||
* 源文件(图片)
|
||||
* @param waterFile
|
||||
* 水印文件(图片)
|
||||
* @param x
|
||||
* 距离右下角的X偏移量
|
||||
* @param y
|
||||
* 距离右下角的Y偏移量
|
||||
* @param alpha
|
||||
* 透明度, 选择值从0.0~1.0: 完全透明~完全不透明
|
||||
* @return BufferedImage
|
||||
* @throws IOException
|
||||
*/
|
||||
public static BufferedImage watermark(File file, File waterFile, int x, int y, float alpha) throws IOException {
|
||||
// 获取底图
|
||||
BufferedImage buffImg = ImageIO.read(file);
|
||||
// 获取层图
|
||||
BufferedImage waterImg = ImageIO.read(waterFile);
|
||||
// 创建Graphics2D对象,用在底图对象上绘图
|
||||
Graphics2D g2d = buffImg.createGraphics();
|
||||
int waterImgWidth = waterImg.getWidth();// 获取层图的宽度
|
||||
int waterImgHeight = waterImg.getHeight();// 获取层图的高度
|
||||
// 在图形和图像中实现混合和透明效果
|
||||
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
|
||||
// 绘制
|
||||
g2d.drawImage(waterImg, x, y, waterImgWidth, waterImgHeight, null);
|
||||
g2d.dispose();// 释放图形上下文使用的系统资源
|
||||
return buffImg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private List<int[]> getPayTypeLocation(int size){
|
||||
|
||||
if(size == 1){
|
||||
return Arrays.asList(new int[]{402, 72});
|
||||
}
|
||||
|
||||
if(size == 2){
|
||||
return Arrays.asList(new int[]{252, 72}, new int[]{552, 72});
|
||||
}
|
||||
|
||||
if(size == 3){
|
||||
return Arrays.asList(new int[]{162, 72}, new int[]{402, 72}, new int[]{642, 72});
|
||||
}
|
||||
|
||||
if(size == 4){
|
||||
return Arrays.asList(new int[]{138, 72}, new int[]{318, 72}, new int[]{513, 72}, new int[]{693, 72});
|
||||
}
|
||||
|
||||
if(size == 5){
|
||||
return Arrays.asList(new int[]{62, 72}, new int[]{232, 72}, new int[]{402, 72}, new int[]{572, 72}, new int[]{742, 72});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.qrshell;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.MutablePair;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/***
|
||||
* ShellC生成器
|
||||
*
|
||||
* @author terrfly
|
||||
* @date 2023/6/26 15:44
|
||||
*/
|
||||
@Data
|
||||
@Service
|
||||
public class ShellBGenerator extends AbstractGenerator {
|
||||
|
||||
/** 生成二维码图片的 buffer缓冲值 **/
|
||||
@Override
|
||||
public BufferedImage genQrImgBuffer(String configModelStr, String qrUrlContent, Long qrcId, String mchStoreName, boolean isViewFlag) throws IOException, WriterException {
|
||||
|
||||
CommonConfigModel configModel = JSON.parseObject(configModelStr, CommonConfigModel.class);
|
||||
|
||||
String qrId = "No." + qrcId.toString();
|
||||
|
||||
//图片的宽和高
|
||||
int imgWidth = 308 * 3; //924
|
||||
int imgHeight = 450 * 3; //1350
|
||||
|
||||
BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
//设置: 二维码背景颜色
|
||||
Graphics2D graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor( new Color(Integer.parseInt(StringUtils.defaultIfEmpty(configModel.getBgColor(), "#5094d5").substring(1),16)) );//背景色的设置
|
||||
graphic.fillRect(0, 0, imgWidth, imgHeight);
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
|
||||
graphic.dispose();
|
||||
|
||||
|
||||
//logo
|
||||
if(StringUtils.isNotEmpty(configModel.getLogoImgUrl() )){
|
||||
|
||||
File imgFile = downloadAndGetCacheFile(configModel.getLogoImgUrl());
|
||||
if(imgFile != null){
|
||||
//设置:底部logo
|
||||
BufferedImage waterLogo = ImageIO.read(imgFile); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
|
||||
graphic.drawImage(waterLogo, 0 , 0, 924, 282, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//设置:二维码背景图
|
||||
BufferedImage waterImg = getStaticImg("/shellB/div1.png"); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterImg, 98, 282, waterImg.getWidth(), waterImg.getHeight(), null);
|
||||
graphic.dispose();
|
||||
|
||||
//设置:二维码背景图
|
||||
BufferedImage waterImg2 = getStaticImg("/shellB/div2.png"); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterImg2, 98, 1002, waterImg2.getWidth(), waterImg2.getHeight(), null);
|
||||
graphic.dispose();
|
||||
|
||||
|
||||
//设置:二维码 190X190
|
||||
//生成真实的二维码图片
|
||||
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
|
||||
hints.put(EncodeHintType.MARGIN, 1); //调整白边边距 1,2,3,4 四个档位
|
||||
BitMatrix bitMatrix = new MultiFormatWriter().encode(qrUrlContent, BarcodeFormat.QR_CODE, 540, 540, hints);// 生成矩阵
|
||||
BufferedImage waterQrImg = MatrixToImageWriter.toBufferedImage(bitMatrix);
|
||||
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterQrImg, 192, 372, waterQrImg.getWidth(), waterQrImg.getHeight(), null);
|
||||
graphic.dispose();
|
||||
|
||||
|
||||
//二维码中间logo
|
||||
if(StringUtils.isNotEmpty(configModel.getQrInnerImgUrl() )){
|
||||
|
||||
File imgFile = downloadAndGetCacheFile(configModel.getQrInnerImgUrl());
|
||||
if(imgFile != null){
|
||||
|
||||
|
||||
//设置:二维码白框
|
||||
BufferedImage waterImg3 = getStaticImg("/shellB/div3.png"); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterImg3, 417, 597, waterImg3.getWidth(), waterImg3.getHeight(), null);
|
||||
graphic.dispose();
|
||||
|
||||
|
||||
//设置:底部logo
|
||||
BufferedImage waterLogo = ImageIO.read(imgFile); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterLogo, 422, 602, 80, 80, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//支付方式
|
||||
List<MutablePair<String, BufferedImage>> payTypeImgList = new ArrayList<>();
|
||||
if(configModel.getPayTypeList() != null){
|
||||
for (CommonConfigModel.PayType payType : configModel.payTypeList) {
|
||||
if("unionpay".equals(payType.getName()) || "ysfpay".equals(payType.getName()) ||"wxpay".equals(payType.getName()) || "alipay".equals(payType.getName()) ){
|
||||
payTypeImgList.add(MutablePair.of(payType.getAlias(), getStaticImg("/commons/" + "t_" + payType.getName() + ".png")));
|
||||
}else{
|
||||
File imgFile = downloadAndGetCacheFile(payType.getImgUrl()); // 下载图片
|
||||
if(imgFile != null){
|
||||
payTypeImgList.add(MutablePair.of(payType.getAlias(), ImageIO.read(imgFile)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<int[]> areaArrays = getPayTypeLocation(payTypeImgList.size());
|
||||
if(areaArrays != null && !areaArrays.isEmpty()){
|
||||
for (int i = 0; i < areaArrays.size() ;i++) {
|
||||
BufferedImage waterImg1 = payTypeImgList.get(i).right; //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterImg1, areaArrays.get(i)[0], areaArrays.get(i)[1], 60, 60, null);
|
||||
graphic.dispose();
|
||||
|
||||
String alias = payTypeImgList.get(i).left;
|
||||
if(StringUtils.isNotEmpty(alias)){
|
||||
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor(Color.black);//背景设置为白色
|
||||
|
||||
Font font = new Font(AbstractGenerator.DEFAULT_FONT, Font.PLAIN, 24);
|
||||
FontMetrics metrics = graphic.getFontMetrics(font);
|
||||
// 字体居中: 文字中间位置(图标位置+20) - 一半的文字正中间位置
|
||||
int x = ( areaArrays.get(i)[0] + 20 ) - (metrics.stringWidth(alias) / 2) + 10;
|
||||
|
||||
graphic.setFont(font); //字体、字型、字号
|
||||
graphic.drawString(alias, x, areaArrays.get(i)[1] + 60 + 24 + 16 ); //画文字
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(configModel.isShowIdFlag()){
|
||||
//设置:二维码编号
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor(Color.black);//背景设置为白色
|
||||
|
||||
Font qrIdFont = new Font(AbstractGenerator.DEFAULT_FONT, Font.PLAIN, 27);
|
||||
FontMetrics metrics = graphic.getFontMetrics(qrIdFont);
|
||||
int x = (imgWidth - metrics.stringWidth(qrId)) / 2;
|
||||
|
||||
graphic.setFont(qrIdFont); //字体、字型、字号
|
||||
graphic.drawString(qrId , x, 955); //画文字
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
|
||||
|
||||
if(configModel.isShowStoreNameFlag() && StringUtils.isNotEmpty(mchStoreName) ){
|
||||
//设置:二维码编号
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor(Color.black);//背景设置为白色
|
||||
|
||||
Font qrIdFont = new Font(AbstractGenerator.DEFAULT_FONT, Font.PLAIN, 42);
|
||||
FontMetrics metrics = graphic.getFontMetrics(qrIdFont);
|
||||
int x = (imgWidth - metrics.stringWidth(mchStoreName)) / 2;
|
||||
|
||||
graphic.setFont(qrIdFont); //字体、字型、字号
|
||||
graphic.drawString(mchStoreName , x, 355); //画文字
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
|
||||
// 预览需要缩小三倍
|
||||
return isViewFlag ? zoomOutImage(bufferedImage, 2) : bufferedImage;
|
||||
}
|
||||
|
||||
|
||||
public static BufferedImage zoomOutImage(BufferedImage originalImage, Integer times){
|
||||
|
||||
int width = originalImage.getWidth()/times;
|
||||
if(width < 0){
|
||||
width=originalImage.getWidth();
|
||||
}
|
||||
int height = originalImage.getHeight()/times;
|
||||
if(height < 0){
|
||||
height=originalImage.getHeight();
|
||||
}
|
||||
BufferedImage newImage = new BufferedImage(width,height,originalImage.getType());
|
||||
|
||||
Graphics g = newImage.getGraphics();
|
||||
|
||||
g.drawImage(originalImage, 0,0,width,height,null);
|
||||
|
||||
g.dispose();
|
||||
|
||||
return newImage;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @Title: 构造图片
|
||||
* @Description: 生成水印并返回java.awt.image.BufferedImage
|
||||
* @param file
|
||||
* 源文件(图片)
|
||||
* @param waterFile
|
||||
* 水印文件(图片)
|
||||
* @param x
|
||||
* 距离右下角的X偏移量
|
||||
* @param y
|
||||
* 距离右下角的Y偏移量
|
||||
* @param alpha
|
||||
* 透明度, 选择值从0.0~1.0: 完全透明~完全不透明
|
||||
* @return BufferedImage
|
||||
* @throws IOException
|
||||
*/
|
||||
public static BufferedImage watermark(File file, File waterFile, int x, int y, float alpha) throws IOException {
|
||||
// 获取底图
|
||||
BufferedImage buffImg = ImageIO.read(file);
|
||||
// 获取层图
|
||||
BufferedImage waterImg = ImageIO.read(waterFile);
|
||||
// 创建Graphics2D对象,用在底图对象上绘图
|
||||
Graphics2D g2d = buffImg.createGraphics();
|
||||
int waterImgWidth = waterImg.getWidth();// 获取层图的宽度
|
||||
int waterImgHeight = waterImg.getHeight();// 获取层图的高度
|
||||
// 在图形和图像中实现混合和透明效果
|
||||
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
|
||||
// 绘制
|
||||
g2d.drawImage(waterImg, x, y, waterImgWidth, waterImgHeight, null);
|
||||
g2d.dispose();// 释放图形上下文使用的系统资源
|
||||
return buffImg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private List<int[]> getPayTypeLocation(int size){
|
||||
|
||||
if(size == 1){
|
||||
return Arrays.asList(new int[]{432, 1066});
|
||||
}
|
||||
|
||||
if(size == 2){
|
||||
return Arrays.asList(new int[]{296, 1066}, new int[]{568, 1066});
|
||||
}
|
||||
|
||||
if(size == 3){
|
||||
return Arrays.asList(new int[]{207, 1066}, new int[]{432, 1066}, new int[]{657, 1066});
|
||||
}
|
||||
|
||||
if(size == 4){
|
||||
return Arrays.asList(new int[]{159, 1066}, new int[]{341, 1066}, new int[]{523, 1066}, new int[]{705, 1066});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.qrshell;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.MutablePair;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* shellB 图片生成器
|
||||
*
|
||||
* @author terrfly
|
||||
* @date 2022/1/17 17:36
|
||||
*/
|
||||
@Data
|
||||
@Service
|
||||
public class ShellCGenerator extends AbstractGenerator {
|
||||
|
||||
|
||||
/** 图片转换为圆角 **/
|
||||
public static BufferedImage setClip(BufferedImage srcImage,int radius){
|
||||
|
||||
int width = srcImage.getWidth();
|
||||
|
||||
int height = srcImage.getHeight();
|
||||
|
||||
BufferedImage image =new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
Graphics2D gs = image.createGraphics();
|
||||
|
||||
gs.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
gs.setClip(new RoundRectangle2D.Double(0,0, width, height, radius, radius));
|
||||
|
||||
gs.drawImage(srcImage,0,0,null);
|
||||
|
||||
gs.dispose();
|
||||
|
||||
return image;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** 生成二维码图片的 buffer缓冲值 **/
|
||||
@Override
|
||||
public BufferedImage genQrImgBuffer(String configModelStr, String qrUrlContent, Long qrcId, String mchStoreName, boolean isViewFlag) throws IOException, WriterException {
|
||||
|
||||
CommonConfigModel configModel = JSON.parseObject(configModelStr, CommonConfigModel.class);
|
||||
|
||||
String qrId = "No." + qrcId.toString();
|
||||
|
||||
//图片的宽和高
|
||||
int imgWidth = 308 * 3; //924
|
||||
int imgHeight = 450 * 3; //1350
|
||||
|
||||
BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
//设置: 二维码背景颜色
|
||||
Graphics2D graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor( new Color(Integer.parseInt(StringUtils.defaultIfEmpty(configModel.getBgColor(), "#5094d5").substring(1),16)) );//背景色的设置
|
||||
graphic.fillRect(0, 0, imgWidth, imgHeight);
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
|
||||
//设置:底部白色背景
|
||||
BufferedImage waterImg2 = getStaticImg("/shellC/bg2.png"); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterImg2, 0, 702, waterImg2.getWidth(), waterImg2.getHeight(), null);
|
||||
graphic.dispose();
|
||||
|
||||
//logo
|
||||
if(StringUtils.isNotEmpty(configModel.getLogoImgUrl() )){
|
||||
|
||||
File imgFile = downloadAndGetCacheFile(configModel.getLogoImgUrl());
|
||||
if(imgFile != null){
|
||||
//设置:底部logo
|
||||
BufferedImage waterLogo = ImageIO.read(imgFile); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
|
||||
graphic.drawImage(waterLogo, 0 , 0, 924, 282, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//设置:二维码 190X190
|
||||
//生成真实的二维码图片
|
||||
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
|
||||
hints.put(EncodeHintType.MARGIN, 0); //调整白边边距 1,2,3,4 四个档位
|
||||
BitMatrix bitMatrix = new MultiFormatWriter().encode(qrUrlContent, BarcodeFormat.QR_CODE, 580, 580, hints);// 生成矩阵
|
||||
BufferedImage waterQrImg = MatrixToImageWriter.toBufferedImage(bitMatrix);
|
||||
|
||||
|
||||
/** ---- 外部圆角矩形 ---- */
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
|
||||
int radiusImgWidth = waterQrImg.getWidth() + 30;
|
||||
int radiusImgHeight = waterQrImg.getHeight() + 30;
|
||||
|
||||
BufferedImage bufferedImageByOuter = new BufferedImage(radiusImgWidth, radiusImgHeight, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D img2DByOuter = (Graphics2D)bufferedImageByOuter.getGraphics();
|
||||
|
||||
// 颜色加深20%
|
||||
Color colorBYOrigin = new Color(Integer.parseInt(StringUtils.defaultIfEmpty(configModel.getBgColor(), "#5094d5").substring(1),16));
|
||||
img2DByOuter.setColor( HSLColor.deepen(colorBYOrigin, 20));
|
||||
img2DByOuter.fillRect(0, 0, radiusImgWidth, radiusImgHeight);
|
||||
img2DByOuter.drawRenderedImage(bufferedImageByOuter, null);
|
||||
img2DByOuter.dispose();
|
||||
|
||||
bufferedImageByOuter = setClip( bufferedImageByOuter, 20);
|
||||
|
||||
graphic.drawImage(bufferedImageByOuter, 158 , 346, radiusImgWidth, radiusImgHeight, null);
|
||||
graphic.dispose();
|
||||
/** ---- 外部圆角矩形 ---- */
|
||||
|
||||
/** ---- 内部圆角矩形 ---- */
|
||||
|
||||
graphic = bufferedImage.createGraphics();
|
||||
int radiusImgWidthByInner = waterQrImg.getWidth() + 15;
|
||||
int radiusImgHeightByInner = waterQrImg.getHeight() + 15;
|
||||
|
||||
BufferedImage bufferedImageByInner = new BufferedImage(radiusImgWidthByInner, radiusImgHeightByInner, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D img2DByInner = (Graphics2D)bufferedImageByInner.getGraphics();
|
||||
img2DByInner.setColor( new Color(Integer.parseInt("#FFFFFF".substring(1),16)));
|
||||
img2DByInner.fillRect(0, 0, radiusImgWidthByInner, radiusImgHeightByInner);
|
||||
img2DByInner.drawRenderedImage(bufferedImageByInner, null);
|
||||
img2DByInner.dispose();
|
||||
bufferedImageByInner = setClip( bufferedImageByInner, 20);
|
||||
|
||||
graphic.drawImage(bufferedImageByInner, 164 , 354, radiusImgWidthByInner, radiusImgHeightByInner, null);
|
||||
graphic.dispose();
|
||||
|
||||
/** ---- 内部圆角矩形 ---- */
|
||||
|
||||
// 二维码图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterQrImg, 172, 360, waterQrImg.getWidth(), waterQrImg.getHeight(), null);
|
||||
graphic.dispose();
|
||||
|
||||
//二维码中间logo
|
||||
if(StringUtils.isNotEmpty(configModel.getQrInnerImgUrl() )){
|
||||
|
||||
File imgFile = downloadAndGetCacheFile(configModel.getQrInnerImgUrl());
|
||||
if(imgFile != null){
|
||||
|
||||
//设置:二维码白框
|
||||
BufferedImage waterImg3 = getStaticImg("/shellB/div3.png"); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterImg3, 417, 597, waterImg3.getWidth(), waterImg3.getHeight(), null);
|
||||
graphic.dispose();
|
||||
|
||||
|
||||
//设置:底部logo
|
||||
BufferedImage waterLogo = ImageIO.read(imgFile); //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterLogo, 422, 602, 80, 80, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//支付方式
|
||||
List<MutablePair<String, BufferedImage>> payTypeImgList = new ArrayList<>();
|
||||
if(configModel.getPayTypeList() != null){
|
||||
for (CommonConfigModel.PayType payType : configModel.payTypeList) {
|
||||
if("unionpay".equals(payType.getName()) || "ysfpay".equals(payType.getName()) ||"wxpay".equals(payType.getName()) || "alipay".equals(payType.getName()) ){
|
||||
payTypeImgList.add(MutablePair.of(payType.getAlias(), getStaticImg("/commons/" + "t_" + payType.getName() + ".png")));
|
||||
}else{
|
||||
File imgFile = downloadAndGetCacheFile(payType.getImgUrl()); // 下载图片
|
||||
if(imgFile != null){
|
||||
payTypeImgList.add(MutablePair.of(payType.getAlias(), ImageIO.read(imgFile)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<int[]> areaArrays = getPayTypeLocation(payTypeImgList.size());
|
||||
if(areaArrays != null && !areaArrays.isEmpty()){
|
||||
for (int i = 0; i < areaArrays.size() ;i++) {
|
||||
BufferedImage waterImg1 = payTypeImgList.get(i).right; //水印图片
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1)); //alpha 水印的透明度 0完全透明, 1不透明
|
||||
graphic.drawImage(waterImg1, areaArrays.get(i)[0], areaArrays.get(i)[1], 120, 120, null);
|
||||
graphic.dispose();
|
||||
|
||||
String alias = payTypeImgList.get(i).left;
|
||||
if(StringUtils.isNotEmpty(alias)){
|
||||
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor(Color.black);//背景设置为白色
|
||||
|
||||
Font font = new Font(AbstractGenerator.DEFAULT_FONT, Font.PLAIN, 40);
|
||||
FontMetrics metrics = graphic.getFontMetrics(font);
|
||||
// 字体居中: 文字中间位置(图标位置+20) - 一半的文字正中间位置
|
||||
int x = ( areaArrays.get(i)[0] + 50 ) - (metrics.stringWidth(alias) / 2) + 10;
|
||||
|
||||
graphic.setFont(font); //字体、字型、字号
|
||||
graphic.drawString(alias, x, areaArrays.get(i)[1] + 130 + 24 + 16 ); //画文字
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(configModel.isShowIdFlag()){
|
||||
//设置:二维码编号
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor(Color.black);//背景设置为白色
|
||||
|
||||
Font qrIdFont = new Font(AbstractGenerator.DEFAULT_FONT, Font.PLAIN, 27);
|
||||
FontMetrics metrics = graphic.getFontMetrics(qrIdFont);
|
||||
int x = (imgWidth - metrics.stringWidth(qrId)) / 2;
|
||||
|
||||
graphic.setFont(qrIdFont); //字体、字型、字号
|
||||
graphic.drawString(qrId , x, 1005); //画文字
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
|
||||
|
||||
if(configModel.isShowStoreNameFlag() && StringUtils.isNotEmpty(mchStoreName)){
|
||||
//设置:二维码编号
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor(Color.white);//背景设置为白色
|
||||
|
||||
Font storeNameFont = new Font(AbstractGenerator.DEFAULT_FONT, Font.PLAIN, 58);
|
||||
FontMetrics metrics = graphic.getFontMetrics(storeNameFont);
|
||||
int x = (imgWidth - metrics.stringWidth(mchStoreName)) / 2;
|
||||
|
||||
graphic.setFont(storeNameFont); //字体、字型、字号
|
||||
graphic.drawString(mchStoreName , x, 270); //画文字
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
|
||||
// 预览需要缩小三倍
|
||||
return isViewFlag ? zoomOutImage(bufferedImage, 2) : bufferedImage;
|
||||
}
|
||||
|
||||
|
||||
public static BufferedImage zoomOutImage(BufferedImage originalImage, Integer times){
|
||||
|
||||
int width = originalImage.getWidth()/times;
|
||||
if(width < 0){
|
||||
width=originalImage.getWidth();
|
||||
}
|
||||
int height = originalImage.getHeight()/times;
|
||||
if(height < 0){
|
||||
height=originalImage.getHeight();
|
||||
}
|
||||
BufferedImage newImage = new BufferedImage(width,height,originalImage.getType());
|
||||
|
||||
Graphics g = newImage.getGraphics();
|
||||
|
||||
g.drawImage(originalImage, 0,0,width,height,null);
|
||||
|
||||
g.dispose();
|
||||
|
||||
return newImage;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @Title: 构造图片
|
||||
* @Description: 生成水印并返回java.awt.image.BufferedImage
|
||||
* @param file
|
||||
* 源文件(图片)
|
||||
* @param waterFile
|
||||
* 水印文件(图片)
|
||||
* @param x
|
||||
* 距离右下角的X偏移量
|
||||
* @param y
|
||||
* 距离右下角的Y偏移量
|
||||
* @param alpha
|
||||
* 透明度, 选择值从0.0~1.0: 完全透明~完全不透明
|
||||
* @return BufferedImage
|
||||
* @throws IOException
|
||||
*/
|
||||
public static BufferedImage watermark(File file, File waterFile, int x, int y, float alpha) throws IOException {
|
||||
// 获取底图
|
||||
BufferedImage buffImg = ImageIO.read(file);
|
||||
// 获取层图
|
||||
BufferedImage waterImg = ImageIO.read(waterFile);
|
||||
// 创建Graphics2D对象,用在底图对象上绘图
|
||||
Graphics2D g2d = buffImg.createGraphics();
|
||||
int waterImgWidth = waterImg.getWidth();// 获取层图的宽度
|
||||
int waterImgHeight = waterImg.getHeight();// 获取层图的高度
|
||||
// 在图形和图像中实现混合和透明效果
|
||||
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
|
||||
// 绘制
|
||||
g2d.drawImage(waterImg, x, y, waterImgWidth, waterImgHeight, null);
|
||||
g2d.dispose();// 释放图形上下文使用的系统资源
|
||||
return buffImg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private List<int[]> getPayTypeLocation(int size){
|
||||
|
||||
if(size == 1){
|
||||
return Arrays.asList(new int[]{402, 1086});
|
||||
}
|
||||
|
||||
if(size == 2){
|
||||
return Arrays.asList(new int[]{266, 1086}, new int[]{538, 1086});
|
||||
}
|
||||
|
||||
if(size == 3){
|
||||
return Arrays.asList(new int[]{177, 1086}, new int[]{402, 1086}, new int[]{627, 1086});
|
||||
}
|
||||
|
||||
if(size == 4){
|
||||
return Arrays.asList(new int[]{77, 1086}, new int[]{294, 1086}, new int[]{510, 1086}, new int[]{727, 1086});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.qrshell;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 仅二维码图片生成器
|
||||
*
|
||||
* @author terrfly
|
||||
* @date 2022/1/17 17:36
|
||||
*/
|
||||
@Data
|
||||
@Service
|
||||
public class ShellQRGenerator extends AbstractGenerator {
|
||||
|
||||
/** 生成二维码图片的 buffer缓冲值 **/
|
||||
@Override
|
||||
public BufferedImage genQrImgBuffer(String configModelStr, String qrUrlContent, Long qrcId, String mchStoreName, boolean isViewFlag) throws IOException, WriterException {
|
||||
|
||||
String qrId = qrcId.toString();
|
||||
|
||||
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
|
||||
BitMatrix bitMatrix = new MultiFormatWriter().encode(qrUrlContent, BarcodeFormat.QR_CODE, 570, 570, hints);// 生成矩阵
|
||||
BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(bitMatrix);
|
||||
|
||||
|
||||
CommonConfigModel configModel = StringUtils.isNotBlank(configModelStr) ? JSON.parseObject(configModelStr, CommonConfigModel.class) : null;
|
||||
|
||||
// 默认全部显示 二维码ID , 除非明确不显示
|
||||
boolean isShowIdFlag = true;
|
||||
if(configModel != null){
|
||||
isShowIdFlag = configModel.showIdFlag;
|
||||
}
|
||||
|
||||
if(isShowIdFlag){
|
||||
|
||||
Graphics2D graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor(Color.black);//黑色
|
||||
//设置:二维码编号
|
||||
graphic = bufferedImage.createGraphics();
|
||||
graphic.setColor(Color.black);//背景设置为白色
|
||||
|
||||
Font qrIdFont = new Font(AbstractGenerator.DEFAULT_FONT, Font.PLAIN, 19);
|
||||
FontMetrics metrics = graphic.getFontMetrics(qrIdFont);
|
||||
int x = (bufferedImage.getWidth() - metrics.stringWidth(qrcId + "")) / 2;
|
||||
|
||||
graphic.setFont(qrIdFont); //字体、字型、字号
|
||||
graphic.drawString(qrId , x, 550); //画文字
|
||||
graphic.drawRenderedImage(bufferedImage, null);
|
||||
graphic.dispose();
|
||||
}
|
||||
|
||||
return bufferedImage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package com.jeequan.jeepay.bizcommons.manage.sms;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.jeequan.jeepay.core.cache.RedisUtil;
|
||||
import com.jeequan.jeepay.core.constants.CS;
|
||||
import com.jeequan.jeepay.core.exception.BizException;
|
||||
import com.jeequan.jeepay.core.model.smsconfig.AbstractSmsConfig;
|
||||
import com.jeequan.jeepay.core.model.smsconfig.MocktestSmsConfig;
|
||||
import com.jeequan.jeepay.core.model.smsconfig.SmsBizDiyContentModel;
|
||||
import com.jeequan.jeepay.core.model.smsconfig.SmsBizVercodeModel;
|
||||
import com.jeequan.jeepay.core.sms.ISmsHandler;
|
||||
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
|
||||
import com.jeequan.jeepay.service.impl.SysConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.MutablePair;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/***
|
||||
* 短信相关
|
||||
*
|
||||
* @author terrfly
|
||||
* @date 2023/8/18 10:05
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SmsManager {
|
||||
|
||||
@Autowired
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
/**
|
||||
* 获取短信验证码
|
||||
**/
|
||||
public String genSmsCode() {
|
||||
return String.valueOf(RandomUtil.getSecureRandom().nextInt(899999) + 100000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能描述: 发送短信验证码 (默认5分钟)
|
||||
*
|
||||
* @param phoneNo
|
||||
* @param smsBizType 业务类型
|
||||
* @Return: void
|
||||
* @Author: terrfly
|
||||
* @Date: 2023/8/18 10:06
|
||||
*/
|
||||
public void sendSmsVercode(String phoneNo, String smsBizType) {
|
||||
|
||||
// 默认 5分钟
|
||||
this.sendSmsVercode(phoneNo, smsBizType, 5);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能描述: 发送短信验证码
|
||||
*
|
||||
* @param phoneNo
|
||||
* @param smsBizType 业务类型
|
||||
* @param expiredMin 过期时间: 分钟
|
||||
* @Return: void
|
||||
* @Author: terrfly
|
||||
* @Date: 2023/8/18 10:06
|
||||
*/
|
||||
public void sendSmsVercode(String phoneNo, String smsBizType, int expiredMin) {
|
||||
|
||||
// 0. 验证手机号是否正确, 如果不正确将抛业务异常!
|
||||
if (!AbstractSmsConfig.checkMobileNumber(phoneNo)) {
|
||||
throw new BizException("手机号格式有误");
|
||||
}
|
||||
|
||||
// 1. 获取配置的哪个通道
|
||||
MutablePair<String, String> smsConfigInfo = sysConfigService.getSmsConfigInfo();
|
||||
|
||||
String smsProviderType = smsConfigInfo.getLeft();
|
||||
|
||||
// 2. 获取发送短信接口
|
||||
ISmsHandler iSmsHandler = SpringBeansUtil.getBean(smsProviderType + "SmsHandler", ISmsHandler.class);
|
||||
|
||||
if (iSmsHandler == null) {
|
||||
throw new BizException("短信渠道不存在");
|
||||
}
|
||||
|
||||
// 3. 生成短信验证码
|
||||
AbstractSmsConfig smsConfig = AbstractSmsConfig.getSmsConfig(smsProviderType, smsConfigInfo.getRight());
|
||||
|
||||
String verifyCode = this.genSmsCode();
|
||||
|
||||
// mock通道
|
||||
if (CS.SMS_PROVIDER_TYPE_API_ENUM.SMS_PROVIDE_KEY_MOCKTEST.equals(smsProviderType)) {
|
||||
verifyCode = ((MocktestSmsConfig) smsConfig).getMockCode();
|
||||
}
|
||||
|
||||
// 4. 构建短信信息
|
||||
SmsBizVercodeModel smsVercodeModel = SmsBizVercodeModel.builder()
|
||||
.phoneNo(phoneNo).smsVercode(verifyCode).smsBizType(smsBizType).expiredMin(expiredMin)
|
||||
.build();
|
||||
|
||||
log.info("即将发送手机号:{},短信验证码:{}", phoneNo, verifyCode);
|
||||
|
||||
iSmsHandler.sendVercode(smsVercodeModel, smsConfig);
|
||||
|
||||
// 5. 放置Redis 缓存 短信验证码缓存时间: xx 分钟
|
||||
RedisUtil.setString(CS.getCacheKeySmsCode(phoneNo), smsVercodeModel.toJSONString(), expiredMin * 60);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义内容的发送
|
||||
**/
|
||||
public void sendDiyContentSms(SmsBizDiyContentModel smsBizDiyContentModel) {
|
||||
|
||||
try {
|
||||
|
||||
// 0. 验证手机号是否正确, 如果不正确将抛业务异常!
|
||||
if (!AbstractSmsConfig.checkMobileNumber(smsBizDiyContentModel.getPhoneNo())) {
|
||||
throw new BizException("手机号格式有误");
|
||||
}
|
||||
|
||||
// 1. 获取配置的哪个通道
|
||||
MutablePair<String, String> smsConfigInfo = sysConfigService.getSmsConfigInfo();
|
||||
String smsProviderType = smsConfigInfo.getLeft();
|
||||
|
||||
|
||||
// 2. 调用发送短信接口
|
||||
ISmsHandler iSmsHandler = SpringBeansUtil.getBean(smsProviderType + "SmsHandler", ISmsHandler.class);
|
||||
|
||||
if (iSmsHandler == null) {
|
||||
throw new BizException("短信渠道不存在");
|
||||
}
|
||||
|
||||
iSmsHandler.sendDiyContent(smsBizDiyContentModel, AbstractSmsConfig.getSmsConfig(smsConfigInfo.getLeft(), smsConfigInfo.getRight()));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("短信发送失败", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断验证码是否正确
|
||||
**/
|
||||
public void checkSmsVercodeThrowBizEx(String phoneNo, String smsVercode, String smsBizType) {
|
||||
|
||||
String codeJsonStr = RedisUtil.getString(CS.getCacheKeySmsCode(phoneNo));
|
||||
if (StringUtils.isEmpty(codeJsonStr)) {
|
||||
throw new BizException("验证码已过期,请重新点击发送验证码!");
|
||||
}
|
||||
SmsBizVercodeModel smsVercodeModel = JSONObject.parseObject(codeJsonStr, SmsBizVercodeModel.class);
|
||||
|
||||
if (smsVercodeModel == null) {
|
||||
throw new BizException("验证码已过期,请重新点击发送验证码!");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(smsVercodeModel.getSmsVercode()) || !smsVercodeModel.getSmsVercode().equalsIgnoreCase(smsVercode)) {
|
||||
throw new BizException("验证码错误!");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(smsVercodeModel.getSmsBizType()) || !smsVercodeModel.getSmsBizType().equalsIgnoreCase(smsBizType)) {
|
||||
throw new BizException("验证码类型错误!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 功能描述: 查询短信相关内容(比如余额等)
|
||||
*
|
||||
* @param bizQueryType 查询的业务类型
|
||||
* @Return: String
|
||||
* @Author: yr
|
||||
* @Date: 2023/8/18 10:06
|
||||
*/
|
||||
public String querySmsInfo(String bizQueryType) {
|
||||
// 1. 获取配置的哪个通道
|
||||
MutablePair<String, String> smsConfigInfo = sysConfigService.getSmsConfigInfo();
|
||||
String smsProviderType = smsConfigInfo.getLeft();
|
||||
|
||||
// 2. 调用短信查询接口
|
||||
ISmsHandler iSmsHandler = SpringBeansUtil.getBean(smsProviderType + "SmsHandler", ISmsHandler.class);
|
||||
|
||||
if (iSmsHandler == null) {
|
||||
throw new BizException("短信渠道不存在");
|
||||
}
|
||||
return iSmsHandler.querySmsInfo(AbstractSmsConfig.getSmsConfig(smsConfigInfo.getLeft(), smsConfigInfo.getRight()), bizQueryType);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user