This commit is contained in:
韩鹏辉
2024-06-11 10:34:21 +08:00
parent 6bab32173f
commit 7894f47de4
2498 changed files with 442406 additions and 0 deletions

View 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>

View File

@@ -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()));
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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) {
// 本地账单数据转Mapkey -> 商户单号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);
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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) );
}
}

View File

@@ -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;
}
}

View File

@@ -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); //调整白边边距 12,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;
}
}

View File

@@ -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); //调整白边边距 12,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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}