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

157
jeepay-member/pom.xml Normal file
View File

@@ -0,0 +1,157 @@
<?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">
<parent>
<artifactId>jeepay</artifactId>
<groupId>com.jeequan</groupId>
<version>Final</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jeepay-9221-member</artifactId>
<packaging>jar</packaging> <!-- 项目的最终打包类型/发布形式, 可选[jar, war, pom, maven-plugin]等 -->
<version>${isys.version}</version> <!-- 项目当前版本号 -->
<description>Jeepay计全支付系统 [会员模块]</description> <!-- 项目描述 -->
<url>https://www.jeequan.com</url>
<!-- 项目属性 -->
<properties>
<projectRootDir>${basedir}/../</projectRootDir>
</properties>
<!-- 项目依赖声明 -->
<dependencies>
<!-- 显式 依赖[ service ]包, 会自动传递依赖[ core ]包。 -->
<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-components-db</artifactId>
</dependency>
<!-- 依赖[ rpc-thirdparty ]包, 会自动传递依赖[ core , service ]包。 -->
<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-components-3rd</artifactId>
</dependency>
<!-- 依赖[ oss ]包 -->
<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-components-oss</artifactId>
</dependency>
<!-- 依赖[ bizcommons ]包 -->
<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-components-bizcommons</artifactId>
</dependency>
<!-- 依赖[ mq ]包 -->
<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-components-mq</artifactId>
</dependency>
<!-- 依赖 sping-boot-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion> <!-- 删除spring boot默认json映射器 Jackson 引入fastJSON -->
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- spring-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!-- 添加redis支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
<!-- 引入 jeepay-sdk-java -->
<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-sdk-java</artifactId>
</dependency>
<!--阿里云短信依赖-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
<!-- 阿里大于 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
</dependency>
</dependencies>
<!-- 作为可执行jar -->
<build>
<finalName>${project.artifactId}</finalName>
<!-- resources资源配置项 -->
<resources>
<!-- 通用资源文件 -->
<resource><directory>src/main/resources</directory><includes><include>**/*.*</include></includes></resource>
<!-- 放置通用配置yml文件 开发时仅配置一套参数即可。 实际生产环境下应在每个项目下 与jar同级目录下新建application.yml覆写对应参数。 -->
<resource>
<directory>../conf/devCommons</directory>
<includes><include>**/*.yml</include></includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
<outputDirectory>${session.executionRootDirectory}/target/</outputDirectory>
</configuration> <!-- 包含本地jar文件 -->
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,61 @@
package com.jeequan.jeepay.member.bootstrap;
import cn.hutool.core.date.DatePattern;
import cn.hutool.crypto.SmUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.SimpleDateFormatSerializer;
import com.jeequan.jeepay.member.config.SystemYmlConfig;
import com.jeequan.jeepay.service.impl.SysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Date;
/*
* 项目初始化操作
* 比如初始化配置文件, 读取基础数据, 资源初始化等。 避免在Main函数中写业务代码。
* CommandLineRunner / ApplicationRunner都可以达到要求 只是调用参数有所不同。
*
*
* @author terrfly
*
* @date 2021/6/8 17:17
*/
@Component
public class InitRunner implements CommandLineRunner {
@Autowired private SystemYmlConfig systemYmlConfig;
@Autowired private SysConfigService sysConfigService;
@Override
public void run(String... args) throws Exception {
// 配置是否使用缓存模式
SysConfigService.IS_USE_CACHE = systemYmlConfig.getCacheConfig();
// 初始化系统秘钥
SysConfigService.DB_ENCRYPT_SECRET = systemYmlConfig.getDbEncryptSecret();
SysConfigService.DB_ENCRYPT_SM4 = SmUtil.sm4(SysConfigService.DB_ENCRYPT_SECRET.getBytes());
SysConfigService.HTTP_MESSAGE_ENCRYPT_SECRET = systemYmlConfig.getHttpMessageEncryptSecret();
SysConfigService.HTTP_MESSAGE_ENCRYPT_SM4 = SmUtil.sm4(SysConfigService.HTTP_MESSAGE_ENCRYPT_SECRET.getBytes());
// 配置是否通信加密 和 密码修改
SysConfigService.HTTP_MSG_IS_ENCRYPT = sysConfigService.getDBSecurityConfig().httpMsgIsEncrypt();
SysConfigService.PWD_EXPIRED_MUST_RESET = sysConfigService.getDBSecurityConfig().passwordExpiredIsMustModify();
// 配置 平台通信秘钥
SysConfigService.PLATFORM_API_SECRET = sysConfigService.getDBSecurityConfig().getPlatformApiSecret();
//初始化处理fastjson格式
SerializeConfig serializeConfig = SerializeConfig.getGlobalInstance();
serializeConfig.put(Date.class, new SimpleDateFormatSerializer(DatePattern.NORM_DATETIME_PATTERN));
//解决json 序列化时候的 $ref问题
JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask();
}
}

View File

@@ -0,0 +1,107 @@
package com.jeequan.jeepay.member.bootstrap;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.jeequan.jeepay.core.task.XxlJobExecutorProp;
import com.jeequan.jeepay.member.config.SystemYmlConfig;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.Arrays;
/**
* @Author terrfly
* @Date 2019/11/7 15:19
* @Description spring-boot 主启动程序
**/
@SpringBootApplication
@EnableScheduling
@MapperScan("com.jeequan.jeepay.service.mapper") //Mybatis mapper接口路径
@ComponentScan(basePackages = "com.jeequan.jeepay.*") //由于MainApplication没有在项目根目录 需要配置basePackages属性使得成功扫描所有Spring组件
@Configuration
public class JeepayMemberApplication {
@Autowired private SystemYmlConfig systemYmlConfig;
/** main启动函数 **/
public static void main(String[] args) {
//启动项目
SpringApplication.run(JeepayMemberApplication.class, args);
}
/** fastJson 配置信息 **/
@Bean
public HttpMessageConverters fastJsonConfig(){
// 开启 FastJSON 安全模式!
ParserConfig.getGlobalInstance().setSafeMode(true);
//新建fast-json转换器
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
//fast-json 配置信息
FastJsonConfig config = new FastJsonConfig();
config.setDateFormat("yyyy-MM-dd HH:mm:ss");
converter.setFastJsonConfig(config);
//设置响应的 Content-Type
converter.setSupportedMediaTypes(Arrays.asList(new MediaType[]{MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8}));
return new HttpMessageConverters(converter);
}
/** Mybatis plus 分页插件 **/
@Bean
public PaginationInnerInterceptor paginationInterceptor() {
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setMaxLimit(-1L);
return paginationInterceptor;
}
/*** 注入 定时任务 执行器 **/
/** xxl-job执行器配置信息 **/
@ConfigurationProperties(prefix = "xxl-job.executor")
@Bean
public XxlJobExecutorProp xxlJobExecutorProp() { return new XxlJobExecutorProp(); }
@Autowired
private XxlJobExecutorProp xxlJobExecutorProp;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(xxlJobExecutorProp.getAdminAddress());
xxlJobSpringExecutor.setAppname(xxlJobExecutorProp.getAppname());
if(xxlJobExecutorProp.getPort() != null){
xxlJobSpringExecutor.setPort(xxlJobExecutorProp.getPort());
}
xxlJobSpringExecutor.setAccessToken(xxlJobExecutorProp.getAccessToken());
xxlJobSpringExecutor.setLogPath(xxlJobExecutorProp.getLogPath());
xxlJobSpringExecutor.setLogRetentionDays(xxlJobExecutorProp.getLogretentiondays());
return xxlJobSpringExecutor;
}
}

View File

@@ -0,0 +1,69 @@
package com.jeequan.jeepay.member.config;
import com.jeequan.jeepay.core.cache.RedisUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
/*
* RedisConfig
*
* @author terrfly
* @date 2021/6/8 17:25
*/
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private Integer port;
@Value("${spring.redis.timeout}")
private Integer timeout;
@Value("${spring.redis.database}")
private Integer defaultDatabase;
@Value("${spring.redis.password}")
private String password;
/** 作用:不同系统的前缀。 a.当连接不同的database时可以为空(物理隔离) b.当redis集群时因为必须同一个database所以需通过前缀区分不同系统的业务。 **/
@Value("${spring.redis.sys-prefix-key}")
private String sysPrefixKey;
/** 当前系统的redis缓存操作对象 (主对象) **/
@Primary
@Bean(name = "defaultStringRedisTemplate")
public StringRedisTemplate sysStringRedisTemplate() {
// 赋值前缀key
RedisUtil.SYS_PREFIX_KEY = sysPrefixKey;
StringRedisTemplate template = new StringRedisTemplate();
LettuceConnectionFactory jedisConnectionFactory = new LettuceConnectionFactory();
jedisConnectionFactory.setHostName(host);
jedisConnectionFactory.setPort(port);
jedisConnectionFactory.setTimeout(timeout);
if (!StringUtils.isEmpty(password)) {
jedisConnectionFactory.setPassword(password);
}
if (defaultDatabase != 0) {
jedisConnectionFactory.setDatabase(defaultDatabase);
}
jedisConnectionFactory.afterPropertiesSet();
template.setConnectionFactory(jedisConnectionFactory);
return template;
}
}

View File

@@ -0,0 +1,39 @@
package com.jeequan.jeepay.member.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 系统Yml配置参数定义Bean
*
* @author terrfly
* @date 2021-04-27 15:50
*/
@Component
@ConfigurationProperties(prefix="isys")
@Data
public class SystemYmlConfig {
/** 是否允许跨域请求 [生产环境建议关闭, 若api与前端项目没有在同一个域名下时应开启此配置或在nginx统一配置允许跨域] **/
private Boolean allowCors;
/** 生成jwt的秘钥。 要求每个系统有单独的秘钥管理机制。 **/
private String jwtSecret;
/** DB SM4 加解密秘钥 (必须16位) [每个系统配置必须相同,否则加解密不一致导致业务异常!] **/
private String dbEncryptSecret;
/** web传输加解密 秘钥 (必须16位) [每个系统配置必须相同,否则加解密不一致导致业务异常!] **/
private String httpMessageEncryptSecret;
/** 支付网关的公钥和私钥(系统级别!), 请妥善保存,用于回调商户的商户侧的验证, 首次设置好之后不可随意变更! **/
private String sysRSA2PrivateKey;
/**支付网关的公钥和私钥(系统级别!), 请妥善保存,用于回调商户的商户侧的验证, 首次设置好之后不可随意变更! **/
private String sysRSA2PublicKey;
/** 是否内存缓存配置信息: true表示开启如支付网关地址/商户应用配置/服务商配置等, 开启后需检查MQ的广播模式是否正常 false表示直接查询DB. **/
private Boolean cacheConfig;
}

View File

@@ -0,0 +1,149 @@
package com.jeequan.jeepay.member.ctrl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.model.BaseModel;
import com.jeequan.jeepay.core.model.QRCodeParams;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.db.entity.MchInfo;
import com.jeequan.jeepay.db.entity.MchQrcodeCard;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.member.secruity.JeeMemberDetails;
import com.jeequan.jeepay.service.impl.MchInfoService;
import com.jeequan.jeepay.service.impl.MchQrcodeCardService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.service.impl.SysConfigService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.ArrayList;
import java.util.List;
/**
* @description:
* @author: zx
* @date: 2023/04/13 16:53
*/
public class CommonCtrl extends AbstractCtrl {
@Autowired protected PayOrderService payOrderService;
@Autowired protected IConfigContextQueryService configContextQueryService;
@Autowired protected SysConfigService sysConfigService;
@Autowired protected MchQrcodeCardService mchQrcodeCardService;
@Autowired protected MchInfoService mchInfoService;
/** 获取当前会员 */
protected JeeMemberDetails getCurrentUser(){
return (JeeMemberDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
/** 获取当前会员ID **/
protected String getCurrentMbrId() {
return getCurrentUser().getMbrId();
}
/** 解析聚合码token与payment解析一致 **/
public QRCodeParams tokenConvert(){
QRCodeParams qrCodeParams = JSON.parseObject(JeepayKit.aesDecode(getToken()), QRCodeParams.class); //解析token
qrCodeParams.setPageType(getCurrentPageType()); //封装页面类型
return qrCodeParams;
}
public String getToken(){
String token = request.getHeader(JeepayKit.TOKEN_KEY);
if(StringUtils.isEmpty(token)) {
throw new BizException("获取二维码参数失败");
}
return token;
}
public String getCurrentPageType(){
String pageType = request.getHeader("pageType");
if(StringUtils.isEmpty(pageType)) {
throw new BizException("不支持的客户端");
}
return pageType;
}
public MchAppConfigContext getQrParamsMchAppConfigContext(QRCodeParams qrCodeParams, boolean checkState){
/*if(qrCodeParams.getType() == QRCodeParams.TYPE_PAY_ORDER){ // 订单
PayOrder payOrder = getPayOrder(checkState);
return configContextQueryService.queryMchInfoAndAppInfo(payOrder.getMchNo(), payOrder.getAppId());
}else */
if(qrCodeParams.getType() == QRCodeParams.TYPE_QRC){ // 码牌二维码
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(qrCodeParams.getId());
if (mchQrCode == null || mchQrCode.getQrcState() != CS.YES) {
throw new BizException("码牌不存在或已停用");
}
if (mchQrCode.getBindState() != CS.YES) {
throw new BizException("请先绑定商户");
}
return configContextQueryService.queryMchInfoAndAppInfoV2(mchQrCode.getMchNo(), mchQrCode.getAppId(),null);
}
throw new BizException("二维码参数有误");
}
public PayOrder getPayOrder(boolean checkState){
JSONObject qrParams = JSON.parseObject(JeepayKit.aesDecode(getToken())); //解析token
String payOrderId = qrParams.getString("id");
PayOrder payOrder = payOrderService.getById(payOrderId);
if(checkState == false) {
return payOrder;
}
if(payOrder == null) {
throw new BizException("订单不存");
}
if(payOrder.getState() == PayOrder.STATE_SUCCESS) {
throw new BizException("订单已支付");
}
if(payOrder.getState() != PayOrder.STATE_INIT) {
throw new BizException("订单状态不正确");
}
return payOrder;
}
/** model 存入商户名称 **/
public void setMchName(List<? extends BaseModel> modeList) {
if(modeList == null || modeList.isEmpty()){
return ;
}
ArrayList<String> mchNoList = new ArrayList<>();
for (BaseModel model:modeList) {
JSONObject json = (JSONObject) JSONObject.toJSON(model);
String mchNo = json.getString("mchNo");
mchNoList.add(mchNo);
}
List<MchInfo> mchInfoList = mchInfoService.list(MchInfo.gw().select(MchInfo::getMchNo, MchInfo::getMchName).in(MchInfo::getMchNo, mchNoList));
for (BaseModel model:modeList) {
JSONObject json = (JSONObject) JSONObject.toJSON(model);
String mchNo = json.getString("mchNo");
if(StringUtils.isBlank(mchNo)) {
continue;
}
for (MchInfo info:mchInfoList) {
if (mchNo.equals(info.getMchNo())) {
model.addExt("mchName", info.getMchName());
}
}
}
}
}

View File

@@ -0,0 +1,89 @@
package com.jeequan.jeepay.member.ctrl.anon;
import cn.hutool.core.codec.Base64;
import com.jeequan.jeepay.bizcommons.manage.sms.SmsManager;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.QRCodeParams;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.db.entity.MemberInfo;
import com.jeequan.jeepay.member.ctrl.CommonCtrl;
import com.jeequan.jeepay.member.service.MchRechargeRuleEntryService;
import com.jeequan.jeepay.member.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 未知会员 服务入口controller
*
* @author zx
* @date 2023/4/12 17:27
*/
@RestController
@RequestMapping("/api/anon/member")
public class AnonMemberController extends CommonCtrl {
@Autowired
private MchRechargeRuleEntryService mchRechargeRuleService;
@Autowired
private MemberService memberService;
@Autowired
private SmsManager smsManager;
public final static String MCH_MBR_RECHARGE_RULE = "mch.mbr.recharge.rule"; // 获取商户会员充值规则列表
public final static String MCH_MBR_RECHARGE_RULE_STORE = "mch.mbr.recharge.rule.store"; // 获取充值规则适用门店
public final static String MBR_INFO = "mbr.info"; // 获取会员信息
public final static String MCH_INFO = "mch.info"; // 获取商户信息
public final static String MBR_TEL_BIND = "mbr.tel.bind"; // 会员手机号绑定
public final static String MBR_TEL_SEND_SMS_CODE = "mbr.tel.send.sms.code"; // 发送手机验证码
/**
* 未知会员 服务入口
**/
@PostMapping
public ApiRes entry() {
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
// 获取商户配置信息
MchAppConfigContext mchAppConfigContext = getQrParamsMchAppConfigContext(qrCodeParams, false);
if (mchAppConfigContext == null) {
throw new BizException("商户或商户应用不存在");
}
if (mchAppConfigContext.getMchInfo() == null || mchAppConfigContext.getMchInfo().getState() != CS.YES) {
throw new BizException("商户信息不存在或商户状态不可用");
}
String method = getValStringRequired("method");
switch (method) {
case MCH_MBR_RECHARGE_RULE:
return mchRechargeRuleService.getMchRechargeRule(mchAppConfigContext);
case MCH_MBR_RECHARGE_RULE_STORE:
return mchRechargeRuleService.getMchRechargeRuleStore(mchAppConfigContext);
case MBR_INFO:
return memberService.getMemberInfo(mchAppConfigContext.getMchNo(), Base64.decodeStr(getValStringRequired("channelUesrId")), qrCodeParams.getPageType());
case MCH_INFO:
return memberService.getMchInfo(mchAppConfigContext, qrCodeParams);
case MBR_TEL_SEND_SMS_CODE:
smsManager.sendSmsVercode(Base64.decodeStr(getValStringRequired("mbrTel")), CS.SMS_TYPE_API_ENUM.TYPE_MBR_TEL_BIND);
return ApiRes.ok();
case MBR_TEL_BIND:
String mbrTel = Base64.decodeStr(getValStringRequired("mbrTel"));
String code = Base64.decodeStr(getValStringRequired("code"));
String channelUesrId = Base64.decodeStr(getValStringRequired("channelUesrId"));
MemberInfo memberInfo = getObject(MemberInfo.class);
memberInfo.setMbrTel(mbrTel); // base64解密之后
memberInfo.setMchNo(mchAppConfigContext.getMchNo());
return memberService.mbrTelBind(memberInfo, code, channelUesrId, qrCodeParams.getPageType());
default:
return ApiRes.customFail("不支持的method类型");
}
}
}

View File

@@ -0,0 +1,89 @@
package com.jeequan.jeepay.member.ctrl.member;
import cn.hutool.core.util.DesensitizedUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jeequan.jeepay.core.constants.ApiCodeEnum;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.db.entity.MemberAccountHistory;
import com.jeequan.jeepay.member.ctrl.CommonCtrl;
import com.jeequan.jeepay.service.impl.MemberAccountHistoryService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 会员账户流水
*
* @author xiaoyu
* @date 2023/4/13 17:22
*/
@RestController
@RequestMapping("/api/member/accountHistory")
public class MemberAccountHistoryController extends CommonCtrl {
@Autowired private MemberAccountHistoryService accountHistoryService;
/**
* @author: xiaoyu
* @date: 2023/4/13 17:24
* @describe: 会员账户流水列表
*/
@GetMapping
public ApiRes list() {
MemberAccountHistory accountHistory = getObject(MemberAccountHistory.class);
LambdaQueryWrapper<MemberAccountHistory> wrapper = MemberAccountHistory.gw();
wrapper.eq(MemberAccountHistory::getMbrId, getCurrentMbrId());
// 查询条件
accountHistoryService.selectParams(accountHistory, wrapper);
// 额外条件参数
JSONObject paramJSON = getReqParamJSON();
// 二合一 会员名称/订单号/手机号
if (paramJSON != null && StringUtils.isNotEmpty(paramJSON.getString("unionQueryParam"))) {
wrapper.and(wr -> {
wr.like(MemberAccountHistory::getMbrTel, paramJSON.getString("unionQueryParam"))
.or().like(MemberAccountHistory::getMbrName, paramJSON.getString("unionQueryParam"))
.or().like(MemberAccountHistory::getRelaBizOrderId, paramJSON.getString("unionQueryParam"));
});
}
wrapper.orderByDesc(MemberAccountHistory::getCreatedAt);
IPage<MemberAccountHistory> page = accountHistoryService.page(getIPage(), wrapper);
// 手机号脱敏
page.getRecords().stream().forEach(i -> i.setMbrTel(DesensitizedUtil.mobilePhone(i.getMbrTel())));
// 商户名称赋值
setMchName(page.getRecords());
return ApiRes.page(page);
}
/**
* @author: xiaoyu
* @date: 2023/4/13 17:49
* @describe: 会员账户流水信息
*/
@PreAuthorize("hasAuthority('ENT_MEMBER_ACCOUNT_HISTORY_VIEW')")
@GetMapping("/{hid}")
public ApiRes detail(@PathVariable("hid") String hid) {
MemberAccountHistory history = accountHistoryService.getById(hid);
if (history == null || !history.getMbrId().equals(getCurrentMbrId())) {
return ApiRes.fail(ApiCodeEnum.SYS_OPERATION_FAIL_SEARCH);
}
return ApiRes.ok(history);
}
}

View File

@@ -0,0 +1,49 @@
package com.jeequan.jeepay.member.ctrl.member;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.QRCodeParams;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.db.entity.MemberInfo;
import com.jeequan.jeepay.member.ctrl.CommonCtrl;
import com.jeequan.jeepay.service.impl.MemberInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 会员服务入口controller
*
* @author zx
* @date 2023/4/12 17:27
*/
@RestController
@RequestMapping("/api/member")
public class MemberInfoController extends CommonCtrl {
@Autowired private MemberInfoService memberInfoService;
/**
* 获取会员信息
*/
@GetMapping
public ApiRes memberInfo() {
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
// 获取商户配置信息
MchAppConfigContext mchAppConfigContext = getQrParamsMchAppConfigContext(qrCodeParams, false);
// 查询会员信息
MemberInfo memberInfo = memberInfoService.selectOne(mchAppConfigContext.getMchNo(), getCurrentMbrId());
memberInfo.setWxMpOpenId(null);
memberInfo.setWxLiteOpenId(null);
memberInfo.setAliUserId(null);
memberInfo.setYsfUserId(null);
return ApiRes.ok(memberInfo);
}
}

View File

@@ -0,0 +1,88 @@
package com.jeequan.jeepay.member.ctrl.member;
import com.jeequan.jeepay.components.mq.model.PushPrinterMQ;
import com.jeequan.jeepay.components.mq.model.PushSpeakerMQ;
import com.jeequan.jeepay.components.mq.vender.IMQSender;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.QRCodeParams;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.db.entity.MchQrcodeCard;
import com.jeequan.jeepay.db.entity.MemberAccountHistory;
import com.jeequan.jeepay.db.entity.MemberInfo;
import com.jeequan.jeepay.member.ctrl.CommonCtrl;
import com.jeequan.jeepay.service.impl.MemberAccountHistoryService;
import com.jeequan.jeepay.service.impl.MemberInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 会员账户支付 controller
*
* @author zx
* @date 2023/4/12 17:27
*/
@RestController
@RequestMapping("/api/member/pay")
public class MemberPayController extends CommonCtrl {
@Autowired private MemberInfoService memberInfoService;
@Autowired private MemberAccountHistoryService memberAccountHistoryService;
@Autowired private IMQSender mqSender;
/**
* 会员账户支付
**/
@PostMapping
public ApiRes pay() {
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(qrCodeParams.getId());
if(mchQrCode == null || mchQrCode.getQrcState() != CS.YES || mchQrCode.getBindState() != CS.YES){
throw new BizException("当前静态码不存在或不可用或未绑定");
}
// 获取商户配置信息
MchAppConfigContext mchAppConfigContext = getQrParamsMchAppConfigContext(qrCodeParams, false);
if(mchAppConfigContext == null){
throw new BizException("商户或商户应用不存在");
}
if(mchAppConfigContext.getMchInfo() == null || mchAppConfigContext.getMchInfo().getState() != CS.YES){
throw new BizException("商户信息不存在或商户状态不可用");
}
Long amount = getRequiredAmountL("amount");
if(amount <= 0) {
throw new BizException("金额错误");
}
MemberInfo memberInfo = memberInfoService.selectOne(mchAppConfigContext.getMchNo(), getCurrentMbrId());
if (memberInfo == null || memberInfo.getState() != CS.YES) {
throw new BizException("会员不存在或已禁用");
}
MemberAccountHistory accountHistory = memberAccountHistoryService.updateMbrAccountAndInsertHistory(memberInfo.getMchNo(), mchQrCode.getStoreId(),
mchQrCode.getQrcId(), memberInfo.getMbrId(), -amount, MemberAccountHistory.BIZ_CONSUME, null, getValString("remark"));
// 语音播报
this.speak(String.valueOf(accountHistory.getHid()));
return ApiRes.ok();
}
public void speak(String hid) {
// 发送订单支付成功 云喇叭通知
mqSender.send(PushSpeakerMQ.build(hid, PushSpeakerMQ.MsgPayload.ORDER_TYPE_MEMBER));
// 发送订单支付成功 云打印通知
mqSender.send(PushPrinterMQ.build(hid, PushPrinterMQ.MsgPayload.ORDER_TYPE_MEMBER));
}
}

View File

@@ -0,0 +1,335 @@
package com.jeequan.jeepay.member.ctrl.member;
import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.ApiField;
import com.jeequan.jeepay.JeepayClient;
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.model.ApiRes;
import com.jeequan.jeepay.core.model.DBApplicationConfig;
import com.jeequan.jeepay.core.model.QRCodeParams;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.core.utils.JsonKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.db.entity.*;
import com.jeequan.jeepay.exception.JeepayException;
import com.jeequan.jeepay.member.ctrl.CommonCtrl;
import com.jeequan.jeepay.model.DeviceInfo;
import com.jeequan.jeepay.model.JeepayObject;
import com.jeequan.jeepay.model.PayOrderCreateResModel;
import com.jeequan.jeepay.request.PayOrderCreateRequest;
import com.jeequan.jeepay.response.PayOrderCreateResponse;
import com.jeequan.jeepay.service.impl.*;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 会员充值 服务入口controller
*
* @author zx
* @date 2023/4/12 17:27
*/
@RestController
@RequestMapping("/api/member/recharge")
public class MemberRechargeController extends CommonCtrl {
@Autowired private MemberInfoService memberInfoService;
@Autowired private MchAppService mchAppService;
@Autowired private MemberRechargeRuleService memberRechargeRuleService;
@Autowired private MemberRechargeRecordService memberRechargeRecordService;
@Autowired private MchConfigService mchConfigService;
@Autowired private MchApplymentService mchApplymentService;
/**
* 会员充值
**/
@PostMapping
public ApiRes recharge() {
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
// 获取商户配置信息
MchAppConfigContext mchAppConfigContext = getQrParamsMchAppConfigContext(qrCodeParams, false);
if(mchAppConfigContext == null){
throw new BizException("商户或商户应用不存在");
}
if(mchAppConfigContext.getMchInfo() == null || mchAppConfigContext.getMchInfo().getState() != CS.YES){
throw new BizException("商户信息不存在或商户状态不可用");
}
// 查询会员信息
MemberInfo memberInfo = memberInfoService.selectOne(mchAppConfigContext.getMchNo(), getCurrentMbrId());
if (memberInfo == null || memberInfo.getState() != CS.YES) {
return ApiRes.customFail("会员不存在或已禁用");
}
// 充值金额,前端传 充值规则ID则以此ID为准不传充值规则ID则充值为自定义金额且必填
String rechargeRuleId = getValString("rechargeRuleId");
Long payAmount = 0L; // 实际支付金额
Long giveAmount = 0L; // 赠送金额
if (StringUtils.isNotBlank(rechargeRuleId)) {
MemberRechargeRule rechargeRule = memberRechargeRuleService.getById(rechargeRuleId);
if (rechargeRule == null || !mchAppConfigContext.getMchNo().equals(rechargeRule.getMchNo()) || rechargeRule.getState() != CS.YES) {
throw new BizException("充值赠送规则不存在或已禁用");
}
payAmount = rechargeRule.getRechargeAmount();
if (rechargeRule.getGiveAmount() != null) {
giveAmount = rechargeRule.getGiveAmount();
}
}else {
payAmount = getRequiredAmountL("rechargeAmount");
}
if (payAmount <= 0) {
throw new BizException("充值金额错误!");
}
// 校验商户最大储值金额
Long memebrMaxbalance = null;
String val = mchConfigService.getConfigValByMchNoAndKey(memberInfo.getMchNo(), MchConfig.SELF_MEMBER_MAX_BALANCE);
if (StringUtils.isNotBlank(val) && NumberUtil.isLong(val)) {
memebrMaxbalance = Long.parseLong(val);
}
if (memebrMaxbalance == null || memebrMaxbalance.equals(0L)) {
memebrMaxbalance = sysConfigService.getDBDefaultConfig().getMchMbrMaxBalance();
}
if (memebrMaxbalance != null && !memebrMaxbalance.equals(0L)) {
Long totalBalance = memberInfoService.selectTotalBalance(MemberInfo.gw().eq(MemberInfo::getMchNo, memberInfo.getMchNo()));
if (memebrMaxbalance < totalBalance + payAmount + giveAmount) {
throw new BizException("已超商家充值总余额上限!");
}
}
MemberRechargeRecord rechargeRecord = initMemberRechargeRecord(qrCodeParams, memberInfo, payAmount, giveAmount, getValString("remark"));
return doPay(rechargeRecord, qrCodeParams, memberInfo);
}
private ApiRes doPay(MemberRechargeRecord rechargeRecord, QRCodeParams qrCodeParams, MemberInfo memberInfo) {
// 会员充值仅支持码牌模式
MchQrcodeCard mchQrcodeCard = mchQrcodeCardService.getById(qrCodeParams.getId());
PayOrderCreateRequest orderCreateRequest = new PayOrderCreateRequest();
PayOrderCreateReqModelAddStoreId model = new PayOrderCreateReqModelAddStoreId();
model.setPas(SysConfigService.PLATFORM_API_SECRET); // 通信秘钥
orderCreateRequest.setBizModel(model);
model.setWayCode(rechargeRecord.getWayCode());
if (CS.PAY_WAY_CODE_TYPE.WECHAT.equals(rechargeRecord.getWayCodeType())) {
if(qrCodeParams.getPageType().equals(QRCodeParams.PAGE_TYPE_WECHAT_H5) ){
model.setChannelExtra(JsonKit.newJson("openid", memberInfo.getWxMpOpenId()).toJSONString()); // 微信公众号openid
}else if(qrCodeParams.getPageType().equals(QRCodeParams.PAGE_TYPE_WECHAT_LITE) ){
model.setChannelExtra(JsonKit.newJson("openid", memberInfo.getWxLiteOpenId()).toJSONString()); // 微信小程序openid
}
}else if (CS.PAY_WAY_CODE_TYPE.ALIPAY.equals(rechargeRecord.getWayCodeType())) {
model.setChannelExtra(JsonKit.newJson("buyerUserId", memberInfo.getAliUserId()).toJSONString()); // 支付宝buyerUserId
}else if (CS.PAY_WAY_CODE_TYPE.YSFPAY.equals(rechargeRecord.getWayCodeType())) {
model.setChannelExtra(JsonKit.newJson("userId", memberInfo.getYsfUserId()).toJSONString()); // 云闪付userId
}
MchAppEntity mchAppEntity = mchAppService.getById(mchQrcodeCard.getAppId()); // 商户应用
model.setStoreId(mchQrcodeCard.getStoreId()); // 门店ID
model.setMchNo(rechargeRecord.getMchNo()); // 商户号
model.setAppId(mchAppEntity.getAppId());
model.setMchOrderNo(rechargeRecord.getRechargeRecordId()); // 商户号 <---> 充值记录ID
model.setQrcId(qrCodeParams.getId()); // 码牌ID
model.setAmount(rechargeRecord.getPayAmount()); // 充值记录 实际支付金额
model.setCurrency("CNY");
model.setClientIp(getClientIp());
model.setSubject("[" + rechargeRecord.getMchNo() + "商户会员充值]");
model.setBody("[" + rechargeRecord.getMchNo() + "商户会员充值]");
model.setBuyerRemark(rechargeRecord.getRemark()); // 买家备注
model.setNotifyUrl(rechargeRecord.getNotifyUrl()); //回调地址
model.setReturnUrl(rechargeRecord.getReturnUrl()); //同步跳转地址
model.setMbrId(memberInfo.getMbrId()); // 会员ID
model.setMbrTel(memberInfo.getMbrTel()); // 会员手机号
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.setDeviceNo(qrCodeParams.getId()); // 订单的qrcId
deviceInfo.setDeviceType(PayOrder.DEVICE_TYPE_QR_CODE);
DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
JeepayClient jeepayClient = new JeepayClient(dbApplicationConfig.getPaySiteUrl(), mchAppEntity.getAppSecret());
try {
// 发起支付
PayOrderCreateResponse response = jeepayClient.execute(orderCreateRequest);
// 明确失败
if(!response.isSuccess(mchAppEntity.getAppSecret())){
// 更新充值订单为充值失败
String errCode = response.getCode() == 0 ? response.get().getErrCode() : String.valueOf(response.getCode());
String erMsg = response.getCode() == 0 ? response.get().getErrMsg() : response.getMsg();
boolean result = memberRechargeRecordService.updateInit2Fail(rechargeRecord.getRechargeRecordId(), errCode, erMsg);
if(!result){
throw new BizException(ApiCodeEnum.SYS_OPERATION_FAIL_UPDATE);
}
return ApiRes.customFail(erMsg);
}
// 更新充值订单为充值中
PayOrderCreateResModel orderInfo = response.get();
boolean result = memberRechargeRecordService.updateInit2Ing(rechargeRecord.getRechargeRecordId(), orderInfo.getPayOrderId());
if(!result){
throw new BizException(ApiCodeEnum.SYS_OPERATION_FAIL_UPDATE);
}
JSONObject resultJSON = (JSONObject) JSONObject.toJSON(orderInfo);
resultJSON.put("payAmount", AmountUtil.convertCent2Dollar(rechargeRecord.getPayAmount()));
return ApiRes.ok(resultJSON);
} catch (JeepayException e) {
throw new BizException(e.getMessage());
}
}
private MemberRechargeRecord initMemberRechargeRecord(QRCodeParams qrCodeParams, MemberInfo memberInfo, Long payAmount, Long giveAmount, String remark) {
MchQrcodeCard mchQrcodeCard = mchQrcodeCardService.getById(qrCodeParams.getId());
MemberRechargeRecord rechargeRecord = new MemberRechargeRecord();
rechargeRecord.setRechargeRecordId(SeqKit.genMhoOrderId()); // 商户单号
rechargeRecord.setMbrId(memberInfo.getMbrId());
rechargeRecord.setMbrName(memberInfo.getMbrName());
rechargeRecord.setMbrTel(memberInfo.getMbrTel());
rechargeRecord.setMchNo(memberInfo.getMchNo());
rechargeRecord.setPayAmount(payAmount); // 支付金额
rechargeRecord.setGiveAmount(giveAmount);
rechargeRecord.setEntryAmount(payAmount + defaultLong(giveAmount)); // 账户入账金额
rechargeRecord.setState(MemberRechargeRecord.STATE_INIT);
covertWayCode(qrCodeParams, rechargeRecord);
MchApplyment applyment = mchApplymentService.getById(mchQrcodeCard.getMchApplyId());
DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
rechargeRecord.setReturnUrl(dbApplicationConfig.genUniJsapiPayUrl(QRCodeParams.TYPE_QRC, mchQrcodeCard.getEntryPage(), mchQrcodeCard.getQrcId() + "", applyment.getIsvNo())); //同步跳转地址
rechargeRecord.setNotifyUrl(dbApplicationConfig.getMemberSiteUrl() + "/api/anon/notify/memberRecharge"); //回调地址
rechargeRecord.setRemark(remark);
boolean result = memberRechargeRecordService.save(rechargeRecord);
if (!result) {
throw new BizException("充值失败");
}
return rechargeRecord;
}
/** 转换支付方式 */
private void covertWayCode(QRCodeParams qrCodeParams, MemberRechargeRecord rechargeRecord){
// 是否商户自己的小程序调起的
Byte isUseSubmchAccount = getValByteDefault("isUseSubmchAccount", CS.NO);
// 获取到当前页面类型
String currentPageType = qrCodeParams.getPageType();
// 支付宝内 && 自行创建二维码
if(QRCodeParams.PAGE_TYPE_ALIPAY_H5.equals(currentPageType) && qrCodeParams.getType() == QRCodeParams.TYPE_QRC){
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(qrCodeParams.getId());
if(CS.PAY_WAY_CODE.ALI_WAP.equals(mchQrCode.getAlipayWayCode())){ //wap支付 无需获取
throw new BizException("支付宝wap或自行创建二维码不支持此功能");
}
}
if(currentPageType.equals(QRCodeParams.PAGE_TYPE_ALIPAY_H5) ){
rechargeRecord.setWayCode(CS.PAY_WAY_CODE.ALI_JSAPI);
rechargeRecord.setWayCodeType(CS.PAY_WAY_CODE_TYPE.ALIPAY);
}else if( currentPageType.equals(QRCodeParams.PAGE_TYPE_ALIPAY_LITE)) {
rechargeRecord.setWayCode(CS.PAY_WAY_CODE.ALI_LITE);
rechargeRecord.setWayCodeType(CS.PAY_WAY_CODE_TYPE.ALIPAY);
}else if(currentPageType.equals(QRCodeParams.PAGE_TYPE_WECHAT_H5) ){
rechargeRecord.setWayCode(CS.PAY_WAY_CODE.WX_JSAPI);
rechargeRecord.setWayCodeType(CS.PAY_WAY_CODE_TYPE.WECHAT);
}else if(currentPageType.equals(QRCodeParams.PAGE_TYPE_WECHAT_LITE) ){
rechargeRecord.setWayCode(CS.PAY_WAY_CODE.WX_LITE);
rechargeRecord.setWayCodeType(CS.PAY_WAY_CODE_TYPE.WECHAT);
}else if(currentPageType.equals(QRCodeParams.PAGE_TYPE_YSFPAY_H5) ){
rechargeRecord.setWayCode(CS.PAY_WAY_CODE.YSF_JSAPI);
rechargeRecord.setWayCodeType(CS.PAY_WAY_CODE_TYPE.YSFPAY);
}else {
throw new BizException("不支持的客户端");
}
}
private Long defaultLong(Long amount) {
if (amount == null) {
return 0L;
}
return amount;
}
/** 与阿里云sdk一样 直接extends PayOrderCreateReqModel无法传入到接口中对应的参数。 需要把所有的参数放进来。。。 = = 。 **/
@Data
public static class PayOrderCreateReqModelAddStoreId extends JeepayObject {
@ApiField("pas")
private String pas;
@ApiField("storeId")
private String storeId;
@ApiField("qrcId")
private String qrcId;
private static final long serialVersionUID = -3998573128290306948L;
@ApiField("mchNo")
private String mchNo; // 商户号
@ApiField("appId")
private String appId; // 应用ID
@ApiField("mchOrderNo")
String mchOrderNo; // 商户订单号
@ApiField("wayCode")
String wayCode; // 支付方式
@ApiField("amount")
Long amount; // 支付金额
@ApiField("currency")
String currency; // 货币代码当前只支持cny
@ApiField("clientIp")
String clientIp; // 客户端IP
@ApiField("subject")
String subject; // 商品标题
@ApiField("body")
String body; // 商品描述
@ApiField("channelBizData")
String channelBizData; // 渠道业务参数
@ApiField("notifyUrl")
String notifyUrl; // 异步通知地址
@ApiField("returnUrl")
String returnUrl; // 跳转通知地址
@ApiField("expiredTime")
String expiredTime; // 订单失效时间
@ApiField("channelExtra")
String channelExtra; // 特定渠道额外支付参数
@ApiField("extParam")
String extParam; // 商户扩展参数
@ApiField("divisionMode")
private Byte divisionMode; // 分账模式: 0-该笔订单不允许分账[默认], 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额)
@ApiField("sellerRemark")
String sellerRemark; // 卖家备注
@ApiField("buyerRemark")
String buyerRemark; // 买家备注
@ApiField("deviceInfo")
private DeviceInfo deviceInfo; // 设备信息
@ApiField("mbrId")
private String mbrId; // 会员ID
@ApiField("mbrTel")
private String mbrTel; // 会员手机号
}
}

View File

@@ -0,0 +1,90 @@
package com.jeequan.jeepay.member.ctrl.member;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.db.entity.MchAppEntity;
import com.jeequan.jeepay.db.entity.MemberRechargeRecord;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.member.ctrl.CommonCtrl;
import com.jeequan.jeepay.service.impl.MchAppService;
import com.jeequan.jeepay.service.impl.MemberRechargeRecordService;
import com.jeequan.jeepay.util.JeepayKit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 会员充值 - 回调函数
*
* @author zx
* @date 2023/4/12 14:22
*/
@Slf4j
@RestController
@RequestMapping("/api/anon/notify")
public class MemberRechargeNotifyController extends CommonCtrl {
@Autowired private MchAppService mchAppService;
@Autowired private MemberRechargeRecordService memberRechargeRecordService;
/** 会员充值 - 回调函数 */
@RequestMapping("/memberRecharge")
public String memberRechargeNotify() {
String logPrefix = "===== 进入【商户会员充值回调】=====";
//请求参数
JSONObject params = getReqParamJSON();
log.info("{}通知参数={}", logPrefix, params.toJSONString());
// 充值记录
String rechargeRecordId = getValString("mchOrderNo"); // 充值单号
MemberRechargeRecord rechargeRecord = memberRechargeRecordService.getById(rechargeRecordId);
if (rechargeRecord == null) {
log.warn("{}充值记录不存在充值记录ID={}", logPrefix, rechargeRecordId);
return "memberRechargeRecord is not exists";
}
if (rechargeRecord.getState() == MemberRechargeRecord.STATE_SUCCESS || rechargeRecord.getState() == MemberRechargeRecord.STATE_FAIL) {
log.info("{}充值记录状态已完结,状态={}, 充值记录ID={}", logPrefix, rechargeRecord.getState(), rechargeRecordId);
return "success";
}
String mchNo = params.getString("mchNo");
String appId = params.getString("appId");
String sign = params.getString("sign");
MchAppEntity mchAppEntity = mchAppService.getById(appId);
if(mchAppEntity == null || !mchAppEntity.getMchNo().equals(mchNo)){
log.warn("{}商户应用不存在充值记录ID={}", logPrefix, rechargeRecordId);
return "app is not exists";
}
// 验签
params.remove("sign");
if(!JeepayKit.getSign(params, mchAppEntity.getAppSecret()).equalsIgnoreCase(sign)){
log.warn("{}验签失败充值记录ID={}", logPrefix, rechargeRecordId);
return "sign fail";
}
int payOrderState = params.getInteger("state"); // 支付订单状态 0-订单生成 1-支付中 2-支付成功 3-支付失败
boolean updateOrderSuccess = true; //默认更新成功
// 支付成功
if (payOrderState == PayOrder.STATE_SUCCESS) {
updateOrderSuccess = memberRechargeRecordService.updateIng2SuccessAndMbrAccount(rechargeRecordId, params.getString("payOrderId"));
// 支付失败
}else if (payOrderState == PayOrder.STATE_FAIL) {
updateOrderSuccess = memberRechargeRecordService.updateIng2Fail(rechargeRecordId, params.getString("errCode"), params.getString("errMsg"));
}
// 更新订单 异常
if(!updateOrderSuccess){
log.error("{}充值记录ID={}updateOrderSuccess={} ", logPrefix, rechargeRecordId, updateOrderSuccess);
return "fail";
}
log.info("===== {}, 通知完成。 充值记录ID={}, payOrderState={} =====", logPrefix, rechargeRecordId, payOrderState);
return "success";
}
}

View File

@@ -0,0 +1,108 @@
package com.jeequan.jeepay.member.ctrl.member;
import cn.hutool.core.util.DesensitizedUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jeequan.jeepay.core.constants.ApiCodeEnum;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.db.entity.MemberRechargeRecord;
import com.jeequan.jeepay.db.entity.PayWay;
import com.jeequan.jeepay.member.ctrl.CommonCtrl;
import com.jeequan.jeepay.service.impl.MemberRechargeRecordService;
import com.jeequan.jeepay.service.impl.PayWayService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 会员充值记录
*
* @author xiaoyu
* @date 2023/4/13 17:22
*/
@RestController
@RequestMapping("/api/member/rechargeRecord")
public class MemberRechargeRecordController extends CommonCtrl {
@Autowired private MemberRechargeRecordService rechargeRecordService;
@Autowired private PayWayService payWayService;
/**
* @author: xiaoyu
* @date: 2023/4/13 17:24
* @describe: 会员充值记录列表
*/
@GetMapping
public ApiRes list() {
MemberRechargeRecord rechargeRecord = getObject(MemberRechargeRecord.class);
LambdaQueryWrapper<MemberRechargeRecord> wrapper = MemberRechargeRecord.gw();
wrapper.eq(MemberRechargeRecord::getMbrId, getCurrentMbrId());
// 查询参数
rechargeRecordService.selectParams(rechargeRecord, wrapper);
// 额外条件参数
JSONObject paramJSON = getReqParamJSON();
// 二合一 会员名称/订单号/手机号
if (paramJSON != null && StringUtils.isNotEmpty(paramJSON.getString("unionQueryParam"))) {
wrapper.and(wr -> {
wr.like(MemberRechargeRecord::getMbrTel, paramJSON.getString("unionQueryParam"))
.or().like(MemberRechargeRecord::getMbrName, paramJSON.getString("unionQueryParam"))
.or().like(MemberRechargeRecord::getPayOrderId, paramJSON.getString("unionQueryParam"));
});
}
wrapper.orderByDesc(MemberRechargeRecord::getCreatedAt);
IPage<MemberRechargeRecord> page = rechargeRecordService.page(getIPage(), wrapper);
// 得到所有支付方式
Map<String, String> payWayNameMap = new HashMap<>();
List<PayWay> payWayList = payWayService.list();
// 手机号脱敏、支付方式名称赋值
page.getRecords().stream().forEach(i -> {
i.setMbrTel(DesensitizedUtil.mobilePhone(i.getMbrTel()));
if (!CollectionUtils.isEmpty(payWayList)) {
for (PayWay payWay:payWayList) {
payWayNameMap.put(payWay.getWayCode(), payWay.getWayName());
}
// 存入支付方式名称
if (StringUtils.isNotEmpty(payWayNameMap.get(i.getWayCode()))) {
i.addExt("wayName", payWayNameMap.get(i.getWayCode()));
}else {
i.addExt("wayName", i.getWayCode());
}
}
});
return ApiRes.page(page);
}
/**
* @author: xiaoyu
* @date: 2023/4/13 17:49
* @describe: 会员充值记录详情
*/
@GetMapping("/{recordId}")
public ApiRes detail(@PathVariable("recordId") String recordId) {
MemberRechargeRecord record = rechargeRecordService.getById(recordId);
if (record == null || !record.getMbrId().equals(getCurrentMbrId())) {
return ApiRes.fail(ApiCodeEnum.SYS_OPERATION_FAIL_SEARCH);
}
return ApiRes.ok(record);
}
}

View File

@@ -0,0 +1,37 @@
package com.jeequan.jeepay.member.mq;
import com.jeequan.jeepay.components.mq.model.ResetAppConfigMQ;
import com.jeequan.jeepay.core.interfaces.push.IBaiduBceService;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.service.impl.SysConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 接收MQ消息
* 业务: 更新系统配置参数
* @author terrfly
* @date 2021/7/27 9:23
*/
@Slf4j
@Component
public class ResetAppConfigMQReceiver implements ResetAppConfigMQ.IMQReceiver {
@Autowired
private SysConfigService sysConfigService;
@Override
public void receive(ResetAppConfigMQ.MsgPayload payload) {
log.info("成功接收更新系统配置的订阅通知, msg={}", payload);
sysConfigService.initDBConfig(payload.getGroupKey());
// 修改百度语音配置需清空redis缓存的百度token
if (SysConfigService.BAIDU_BCE_CONFIG.getLeft().equals(payload.getGroupKey())) {
IBaiduBceService baiduBceService = SpringBeansUtil.getBean("baiduBceService", IBaiduBceService.class);
baiduBceService.clearBaiduBceTokenCache(SysConfigService.BAIDU_BCE_CONFIG.getRight());
}
log.info("系统配置静态属性已重置");
}
}

View File

@@ -0,0 +1,61 @@
package com.jeequan.jeepay.member.mq;
import com.jeequan.jeepay.components.mq.model.ResetIsvMchAppInfoConfigMQ;
import com.jeequan.jeepay.core.interfaces.IConfigContextService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 接收MQ消息
* 业务: 更新服务商/商户/商户应用配置信息;
*
* @author terrfly
* @since 2021/7/27 9:23
*/
@Slf4j
@Component
public class ResetIsvMchAppInfoMQReceiver implements ResetIsvMchAppInfoConfigMQ.IMQReceiver {
@Autowired(required = false)
private IConfigContextService configContextService;
@Override
public void receive(ResetIsvMchAppInfoConfigMQ.MsgPayload payload) {
if (payload.getResetType() == ResetIsvMchAppInfoConfigMQ.RESET_TYPE_ISV_INFO) {
this.modifyIsvInfo(payload.getIsvNo());
} else if (payload.getResetType() == ResetIsvMchAppInfoConfigMQ.RESET_TYPE_MCH_INFO) {
this.modifyMchInfo(payload.getMchNo());
}
}
/**
* 接收 [商户配置信息] 的消息
*/
private void modifyMchInfo(String mchNo) {
log.info("成功接收 [商户配置信息] 的消息, msg={}", mchNo);
if (configContextService != null) configContextService.initMchInfoConfigContext(mchNo);
log.info(" [商户配置信息] 已重置");
}
/**
* 接收 [商户应用支付参数配置信息] 的消息
*/
private void modifyMchApp(String mchNo, String appId) {
log.info("成功接收 [商户应用支付参数配置信息] 的消息, mchNo={}, appId={}", mchNo, appId);
if (configContextService != null) configContextService.initMchAppConfigContext(mchNo, appId);
log.info(" [商户应用支付参数配置信息] 已重置");
}
/**
* 重置ISV信息
*/
private void modifyIsvInfo(String isvNo) {
log.info("成功接收 [ISV信息] 重置, msg={}", isvNo);
if (configContextService != null) configContextService.initIsvConfigContext(isvNo);
log.info("[ISV信息] 已重置");
}
}

View File

@@ -0,0 +1,47 @@
package com.jeequan.jeepay.member.secruity;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.utils.DateKit;
import lombok.Data;
import java.util.Map;
/**
* JWT payload 载体
* 格式:
* {
* "mbrId": "10001",
* "created": "1568250147846",
* "cacheKey": "KEYKEYKEYKEY",
* }
*
* @author terrfly
* @date 2021/6/8 18:01
*/
@Data
public class JWTPayload {
private String mbrId; //登录会员ID
private Long created; //创建时间, 格式13位时间戳
private String cacheKey; //redis保存的key
protected JWTPayload() {
}
public JWTPayload(JeeMemberDetails jeeMemberDetails) {
this.setMbrId(jeeMemberDetails.getMemberInfo().getMbrId());
this.setCreated(DateKit.currentTimeMillis());
this.setCacheKey(jeeMemberDetails.getCacheKey());
}
/**
* toMap
**/
public Map<String, Object> toMap() {
JSONObject json = (JSONObject) JSONObject.toJSON(this);
return json.toJavaObject(Map.class);
}
}

View File

@@ -0,0 +1,49 @@
package com.jeequan.jeepay.member.secruity;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
/**
* JWT工具包
*
* @author terrfly
* @date 2021/6/8 16:32
*/
public class JWTUtils {
public static long EXPIRED_TIME = 10 * 60;
/**
* 生成token
**/
public static String generateToken(JWTPayload jwtPayload, String jwtSecret) {
return Jwts.builder()
.setClaims(jwtPayload.toMap())
//过期时间 = 当前时间 + (设置过期时间[单位 s ] token放置redis 过期时间无意义
//.setExpiration(new Date(DateKit.currentTimeMillis() + (jwtExpiration * 1000) ))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
/**
* 根据token与秘钥 解析token并转换为 JWTPayload
**/
public static JWTPayload parseToken(String token, String secret) {
try {
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
JWTPayload result = new JWTPayload();
result.setMbrId(claims.get("mbrId", String.class));
result.setCreated(claims.get("created", Long.class));
result.setCacheKey(claims.get("cacheKey", String.class));
return result;
} catch (Exception e) {
return null; //解析失败
}
}
}

View File

@@ -0,0 +1,37 @@
package com.jeequan.jeepay.member.secruity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
/**
* 用户身份认证失败处理类
*
* @author terrfly
* @date 2021/6/8 17:11
*/
@Component
public class JeeAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// This is invoked when user tries to access a secured REST resource without supplying any credentials
// We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
//返回json形式的错误信息
// response.setCharacterEncoding("UTF-8");
// response.setContentType("application/json");
// response.getWriter().println("{\"code\":1001, \"msg\":\"Unauthorized\"}");
response.getWriter().flush();
}
}

View File

@@ -0,0 +1,81 @@
package com.jeequan.jeepay.member.secruity;
import com.jeequan.jeepay.core.cache.RedisUtil;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.member.config.SystemYmlConfig;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* <p><b>Title: </b>JwtAuthenticationTokenFilter.java
* <p><b>Description: </b>
* spring security框架中验证组件的前置过滤器
* 用于验证token有效期并放置ContextAuthentication信息,为后续spring security框架验证提供数据
* 避免使用@Component等bean自动装配注解@Component会将filter被spring实例化为web容器的全局filter导致重复过滤。
* @modify terrfly
* @version V1.0
*
* @date 2021-04-27 15:50
* <p>
*/
public class JeeAuthenticationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
JeeMemberDetails jeeMemberDetails = commonFilter(request);
if(jeeMemberDetails == null){
chain.doFilter(request, response);
return;
}
//将信息放置到Spring-security context中
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(jeeMemberDetails, null, null);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
private JeeMemberDetails commonFilter(HttpServletRequest request){
String authToken = request.getHeader(CS.ACCESS_TOKEN_NAME);
if(StringUtils.isEmpty(authToken)){
authToken = request.getParameter(CS.ACCESS_TOKEN_NAME);
}
if(StringUtils.isEmpty(authToken)){
return null; //放行,并交给UsernamePasswordAuthenticationFilter进行验证,返回公共错误信息.
}
JWTPayload jwtPayload = JWTUtils.parseToken(authToken, SpringBeansUtil.getBean(SystemYmlConfig.class).getJwtSecret()); //反解析token信息
//token字符串解析失败
if( jwtPayload == null || StringUtils.isEmpty(jwtPayload.getCacheKey())) {
return null;
}
//根据用户名查找数据库
JeeMemberDetails jwtBaseUser = RedisUtil.getObject(jwtPayload.getCacheKey(), JeeMemberDetails.class);
if(jwtBaseUser == null){ // 找不到对应的缓存
RedisUtil.del(jwtPayload.getCacheKey());
return null; //数据库查询失败删除redis
}
//续签时间
RedisUtil.expire(jwtPayload.getCacheKey(), JWTUtils.EXPIRED_TIME);
return jwtBaseUser;
}
}

View File

@@ -0,0 +1,139 @@
package com.jeequan.jeepay.member.secruity;
import com.jeequan.jeepay.db.entity.MemberInfo;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
/**
* 实现Spring Security的UserDetails接口
*
* @author zx
* @date 2023/4/8 16:34
*/
@Slf4j
@Data
public class JeeMemberDetails implements UserDetails {
/**
* 会员信息
**/
private MemberInfo memberInfo;
/**
* 密码
**/
private String credential;
/**
* 角色+权限 集合 (角色必须以: ROLE_ 开头)
**/
private Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
/**
* 缓存标志
**/
private String cacheKey;
/**
* 登录IP
**/
private String loginIp;
/**
* 认证类型,见 CREDENTIAL_AUTH_TYPE
**/
private String authType;
//此处的无参构造为json反序列化提供
public JeeMemberDetails() {
}
public JeeMemberDetails(MemberInfo memberInfo) {
this.setMemberInfo(memberInfo);
}
/**
* spring-security 需要验证的密码
**/
@Override
public String getPassword() {
return getCredential();
}
/**
* spring-security 登录名
**/
@Override
public String getUsername() {
return getMemberInfo().getMbrId();
}
/**
* 账户是否过期
**/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 账户是否已解锁
**/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 密码是否过期
**/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 账户是否开启
**/
@Override
public boolean isEnabled() {
return true;
}
/**
* 获取权限集合
**/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public static JeeMemberDetails getCurrentMemberDetails() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
}
try {
return (JeeMemberDetails) authentication.getPrincipal();
} catch (Exception e) {
return null;
}
}
/**
* 获取当前登录者ID
**/
public String getMbrId() {
return this.getMemberInfo() != null ? this.getMemberInfo().getMbrId() : null;
}
}

View File

@@ -0,0 +1,38 @@
package com.jeequan.jeepay.member.secruity;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.JeepayAuthenticationException;
import com.jeequan.jeepay.db.entity.MemberInfo;
import com.jeequan.jeepay.service.impl.MemberInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* UserDetailsService实现类
*
* @author terrfly
* @modify zhuxiao
* @date 2021-04-27 15:50
*/
@Service
public class JeeUserDetailsServiceImpl implements UserDetailsService {
@Autowired private MemberInfoService memberInfoService;
@Override
public UserDetails loadUserByUsername(String loginUsernameStr) throws UsernameNotFoundException {
MemberInfo memberInfo = memberInfoService.getById(loginUsernameStr);
if(memberInfo == null){ //没有该用户信息
throw JeepayAuthenticationException.build("用户名/密码错误!");
}
if(CS.PUB_USABLE != memberInfo.getState()){ //状态不合法
throw JeepayAuthenticationException.build("用户状态不可登录,请联系管理员!");
}
return new JeeMemberDetails(memberInfo);
}
}

View File

@@ -0,0 +1,131 @@
package com.jeequan.jeepay.member.secruity;
import com.jeequan.jeepay.member.config.SystemYmlConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* Spring Security 配置项
*
* @author terrfly
* @date 2021/6/8 17:11
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启@PreAuthorize @PostAuthorize 等前置后置安全校验注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired private UserDetailsService userDetailsService;
@Autowired private JeeAuthenticationEntryPoint unauthorizedHandler;
@Autowired private SystemYmlConfig systemYmlConfig;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 使用BCrypt强哈希函数 实现PasswordEncoder
* **/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder());
}
/** 允许跨域请求 **/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
if(systemYmlConfig.getAllowCors()){
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); //带上cookie信息
// config.addAllowedOrigin(CorsConfiguration.ALL); //允许跨域的域名, *表示允许任何域名使用
config.addAllowedOriginPattern(CorsConfiguration.ALL); //使用addAllowedOriginPattern 避免出现 When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
config.addAllowedHeader(CorsConfiguration.ALL); //允许任何请求头
config.addAllowedMethod(CorsConfiguration.ALL); //允许任何方法post、get等
source.registerCorsConfiguration("/**", config); // CORS 配置对所有接口都有效
}
return new CorsFilter(source);
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 由于使用的是JWT我们这里不需要csrf
.csrf().disable()
.cors().and()
// 认证失败处理方式
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 添加JWT filter
httpSecurity.addFilterBefore(new JeeAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
// 禁用缓存
httpSecurity.headers().cacheControl();
}
@Override
public void configure(WebSecurity web) throws Exception {
//ignore文件 无需进入spring security 框架
// 1.允许对于网站静态资源的无授权访问
// 2.对于获取token的rest api要允许匿名访问
web.ignoring().antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/**/*.png",
"/**/*.jpg",
"/**/*.jpeg",
"/**/*.svg",
"/**/*.ico",
"/**/*.webp",
"/*.txt",
"/**/*.xls",
"/**/*.mp4" //支持mp4格式的文件匿名访问
)
.antMatchers(
"/api/anon/**" //匿名访问接口
);
}
}

View File

@@ -0,0 +1,80 @@
package com.jeequan.jeepay.member.service;
import cn.hutool.core.util.IdUtil;
import com.jeequan.jeepay.core.cache.RedisUtil;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.db.entity.MemberInfo;
import com.jeequan.jeepay.member.config.SystemYmlConfig;
import com.jeequan.jeepay.member.secruity.JWTPayload;
import com.jeequan.jeepay.member.secruity.JWTUtils;
import com.jeequan.jeepay.member.secruity.JeeMemberDetails;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
/**
* 认证Service
*
* @modify zhuxiao
* @date 2021-04-27 15:50
*/
@Slf4j
@Service
public class AuthService {
@Autowired private SystemYmlConfig systemYmlConfig;
@Autowired private UserDetailsService userDetailsService;
// 通过 mbrId 登录
public String authByMbrId(String mbrId, String existsCacheKey){
return userDetailsProcess(userDetailsService.loadUserByUsername(mbrId), existsCacheKey);
}
/** 根据用户ID 删除用户缓存信息 **/
public void delAuthentication(List<Long> sysUserIdList){
if(sysUserIdList == null || sysUserIdList.isEmpty()){
return ;
}
for (Long sysUserId : sysUserIdList) {
Collection<String> cacheKeyList = RedisUtil.keys(CS.getCacheKeyToken(sysUserId, "*"));
if(cacheKeyList == null || cacheKeyList.isEmpty()){
continue;
}
for (String cacheKey : cacheKeyList) {
RedisUtil.del(cacheKey);
}
}
}
/** 获取到userDetail之后的操作 **/
public String userDetailsProcess(UserDetails userDetails, String existsCacheKey) {
JeeMemberDetails jeeMemberDetails = (JeeMemberDetails) userDetails;
//验证通过后 再查询用户角色和权限信息集合
MemberInfo memberInfo = jeeMemberDetails.getMemberInfo();
//生成token
String cacheKey = StringUtils.isNotEmpty(existsCacheKey) ? existsCacheKey : CS.getCacheKeyToken(memberInfo.getMbrId(), IdUtil.fastUUID());
//生成iToken 并放置到缓存
jeeMemberDetails.setCacheKey(cacheKey);
RedisUtil.set(cacheKey, jeeMemberDetails, JWTUtils.EXPIRED_TIME); // 放置在redis缓存时间10分钟,
//将信息放置到Spring-security context中
UsernamePasswordAuthenticationToken authenticationRest = new UsernamePasswordAuthenticationToken(jeeMemberDetails, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationRest);
//返回JWTToken
return JWTUtils.generateToken(new JWTPayload(jeeMemberDetails), systemYmlConfig.getJwtSecret());
}
}

View File

@@ -0,0 +1,54 @@
package com.jeequan.jeepay.member.service;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.member.config.SystemYmlConfig;
import com.jeequan.jeepay.service.impl.*;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
/**
* 定义通用CommonService
*
* @author zx
* @date 2023/4/8 17:09
*/
public abstract class CommonService extends AbstractCtrl {
@Autowired
protected SystemYmlConfig mainConfig;
@Autowired
protected PayInterfaceDefineService payInterfaceDefineService;
@Autowired
protected MchInfoService mchInfoService;
@Autowired
protected MchStoreService mchStoreService;
@Autowired
protected AgentInfoService agentInfoService;
@Autowired
protected SysConfigService sysConfigService;
/**
* 获取当前用户登录IP
*
* @return
*/
protected String getIp() {
return getClientIp();
}
/**
* 会员头像地址默认值获取
**/
public String getMemberAvatarDefault() {
Collections.shuffle(CS.MEMBER_AVATAR_ICON);
return CS.MEMBER_AVATAR_ICON.get(0);
}
}

View File

@@ -0,0 +1,78 @@
package com.jeequan.jeepay.member.service;
import com.alibaba.fastjson.JSONObject;
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.model.ApiRes;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.utils.JsonKit;
import com.jeequan.jeepay.db.entity.MchConfig;
import com.jeequan.jeepay.db.entity.MchStore;
import com.jeequan.jeepay.db.entity.MemberRechargeRule;
import com.jeequan.jeepay.service.impl.MchConfigService;
import com.jeequan.jeepay.service.impl.MchStoreService;
import com.jeequan.jeepay.service.impl.MemberRechargeRuleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 商户充值规则服务类
*
* @author zx
* @date 2023/4/8 17:40
*/
@Service
@Slf4j
public class MchRechargeRuleEntryService {
@Autowired
private MemberRechargeRuleService memberRechargeRuleService;
@Autowired
private MchStoreService mchStoreService;
@Autowired
private MchConfigService mchConfigService;
/**
* 获取商户充值规则
*/
public ApiRes getMchRechargeRule(MchAppConfigContext mchAppConfigContext) {
String mchNo = mchAppConfigContext.getMchNo();
// 查询商户充值规则
List<MemberRechargeRule> rechargeRuleList = memberRechargeRuleService.list(MemberRechargeRule.gw()
.select(MemberRechargeRule::getRuleId, MemberRechargeRule::getRechargeAmount, MemberRechargeRule::getGiveAmount, MemberRechargeRule::getSort)
.eq(MemberRechargeRule::getMchNo, mchNo)
.eq(MemberRechargeRule::getState, CS.YES)
.orderByAsc(MemberRechargeRule::getSort)
.orderByDesc(MemberRechargeRule::getCreatedAt)
);
// 自定义充值配置查询
MchConfig mchConfig = mchConfigService.getByMchNoAndConfigKey(mchNo, MchConfig.SELF_MEMBER_CUSTOM_AMOUNT_STATE);
// 自定义充值金额配置
JSONObject extJson = JsonKit.newJson(MchConfig.SELF_MEMBER_CUSTOM_AMOUNT_STATE, mchConfig == null ? MchConfig.NO : mchConfig.getConfigVal());
IPage<MemberRechargeRule> iPage = new Page<>();
iPage.setRecords(rechargeRuleList);
return ApiRes.page(iPage, extJson);
}
/**
* 获取商户充值规则 适用门店
*/
public ApiRes getMchRechargeRuleStore(MchAppConfigContext mchAppConfigContext) {
String mchNo = mchAppConfigContext.getMchNo();
return ApiRes.ok(mchStoreService.list(MchStore.gw()
.select(MchStore::getStoreName)
.eq(MchStore::getMchNo, mchNo)));
}
}

View File

@@ -0,0 +1,144 @@
package com.jeequan.jeepay.member.service;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.bizcommons.manage.sms.SmsManager;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.QRCodeParams;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.db.entity.MchQrcodeCard;
import com.jeequan.jeepay.db.entity.MchStore;
import com.jeequan.jeepay.db.entity.MemberInfo;
import com.jeequan.jeepay.service.impl.MchQrcodeCardService;
import com.jeequan.jeepay.service.impl.MemberInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 查询上游订单, & 补单服务实现类
*
* @author terrfly
* @date 2021/6/8 17:40
*/
@Service
@Slf4j
public class MemberService extends CommonService {
@Autowired
private AuthService authService;
@Autowired
private MemberInfoService memberInfoService;
@Autowired
private MchQrcodeCardService mchQrcodeCardService;
@Autowired
private SmsManager smsManager;
/**
* 查询商户信息
*/
public ApiRes getMchInfo(MchAppConfigContext mchAppConfigContext, QRCodeParams qrCodeParams) {
JSONObject resultJSON = new JSONObject();
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(qrCodeParams.getId());
if (mchQrCode == null || mchQrCode.getQrcState() != CS.YES) {
throw new BizException("当前静态码不存在或不可用");
}
if (mchQrCode.getFixedFlag() == CS.YES) {
resultJSON.put("amount", mchQrCode.getFixedPayAmount());
resultJSON.put("fixedFlag", true);
}
MchStore mchStore = mchStoreService.getById(mchQrCode.getStoreId());
if (mchStore != null && StringUtils.isNotBlank(mchStore.getStoreName())) {
resultJSON.put("mchName", mchStore.getStoreName());
resultJSON.put("storeLogo", mchStore.getStoreLogo());
} else {
resultJSON.put("mchName", mchAppConfigContext.getMchInfo().getMchShortName());
}
return ApiRes.ok(resultJSON);
}
/**
* 查询会员信息
*/
public ApiRes getMemberInfo(String mchNo, String channelUesrId, String pageType) {
log.info("查询会员信息接口mchNo={}, channelUesrId={}, pageType={}", mchNo, channelUesrId, pageType);
MemberInfo memberInfo = memberInfoService.selectOne(mchNo, null, null, channelUesrId, pageType);
if (memberInfo == null) {
return ApiRes.ok();
}
return ApiRes.ok(authService.authByMbrId(memberInfo.getMbrId(), null));
}
/**
* 会员绑定手机号
*/
public ApiRes mbrTelBind(MemberInfo memberInfo, String code, String channelUesrId, String pageType) {
String mbrTel = memberInfo.getMbrTel();
// 校验短信验证码
smsManager.checkSmsVercodeThrowBizEx(mbrTel, code, CS.SMS_TYPE_API_ENUM.TYPE_MBR_TEL_BIND);
if (StringUtils.equalsAny(pageType, QRCodeParams.PAGE_TYPE_ALIPAY_H5, QRCodeParams.PAGE_TYPE_ALIPAY_LITE)) {
memberInfo.setAliUserId(channelUesrId);
memberInfo.setMbrName("支付宝用户" + getTel4Last(memberInfo.getMbrTel()));
} else if (StringUtils.equals(pageType, QRCodeParams.PAGE_TYPE_WECHAT_H5)) {
memberInfo.setWxMpOpenId(channelUesrId);
memberInfo.setMbrName("微信用户" + getTel4Last(memberInfo.getMbrTel()));
} else if (StringUtils.equals(pageType, QRCodeParams.PAGE_TYPE_WECHAT_LITE)) {
memberInfo.setWxLiteOpenId(channelUesrId);
memberInfo.setMbrName("微信用户" + getTel4Last(memberInfo.getMbrTel()));
} else if (StringUtils.equals(pageType, QRCodeParams.PAGE_TYPE_YSFPAY_H5)) {
memberInfo.setYsfUserId(channelUesrId);
memberInfo.setMbrName("云闪付用户" + getTel4Last(memberInfo.getMbrTel()));
} else {
throw new BizException("不支持的客户端");
}
// 手机号查询会员若手机号已绑定将更新会员的channelUserId
MemberInfo dbRecord = memberInfoService.selectOne(memberInfo.getMchNo(), null, mbrTel, null, null);
if (dbRecord != null) {
memberInfo.setMbrId(dbRecord.getMbrId());
memberInfo.setMbrName(null); // 不更新会员名称
memberInfo.setSafeKey(null);
} else {
memberInfo.setMbrId("B" + DateUtil.currentSeconds());
memberInfo.setSafeKey(JeepayKit.genAccountSafeKey(memberInfo.getMbrId(), 0L));
memberInfo.setAvatarUrl(getMemberAvatarDefault());
}
boolean result = memberInfoService.saveOrUpdate(memberInfo);
if (!result) {
return ApiRes.customFail("操作失败");
}
return ApiRes.ok(authService.authByMbrId(memberInfo.getMbrId(), null));
}
/**
* 手机尾号4位
**/
public String getTel4Last(String memberTel) {
if (StringUtils.isEmpty(memberTel)) {
return null;
}
return memberTel.substring(memberTel.length() - 4);
}
}

View File

@@ -0,0 +1,115 @@
package com.jeequan.jeepay.member.task;
import cn.hutool.core.collection.CollectionUtil;
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.db.entity.MemberRechargeRecord;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.service.impl.MemberRechargeRecordService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* 会员充值定时
*
* @author xiaoyu
* @date 2023/4/15 10:07
*/
@Slf4j
@Component
public class MemberRechargeTask {
@Autowired private MemberRechargeRecordService rechargeRecordService;
@Autowired private PayOrderService payOrderService;
private static final int QUERY_PAGE_SIZE = 100; //每次查询数量
@XxlJob("memberRechargeOrderTaskHandler")
public void start() {
log.info("开始【处理会员充值任务】");
//查询条件:充值中的记录
LambdaQueryWrapper<MemberRechargeRecord> lambdaQueryWrapper = MemberRechargeRecord.gw()
.eq(MemberRechargeRecord::getState, MemberRechargeRecord.STATE_ING)
.select(MemberRechargeRecord::getPayOrderId, MemberRechargeRecord::getRechargeRecordId);
int currentPageIndex = 1; //当前页码
while(true){
try {
// 查询充值中的记录
IPage<MemberRechargeRecord> resultPage = rechargeRecordService.page(new Page(currentPageIndex, QUERY_PAGE_SIZE), lambdaQueryWrapper);
if(resultPage == null || resultPage.getRecords().isEmpty()){ //本次查询无结果, 不再继续查询;
break;
}
// 缓存订单号
ArrayList<String> orderList = new ArrayList<>();
resultPage.getRecords().stream().forEach(record ->{
orderList.add(record.getPayOrderId());
});
// 订单号列表为空 跳出循环
if (CollectionUtil.isEmpty(orderList)) {
break;
}
// 查询关联订单
List<PayOrder> payOrderList = payOrderService.list(PayOrder.gw().in(PayOrder::getPayOrderId, orderList));
// 缓存订单信息
HashMap<String, PayOrder> orderMap = new HashMap<>();
payOrderList.stream().forEach(order -> {
orderMap.put(order.getPayOrderId(), order);
});
for (MemberRechargeRecord record : resultPage.getRecords()) {
PayOrder payOrder = orderMap.get(record.getPayOrderId());
if (payOrder == null) {
log.info("【处理会员充值任务】订单{}不存在", record.getPayOrderId());
break;
}
// 支付成功处理充值
if (PayOrder.STATE_SUCCESS == payOrder.getState()) {
// 处理充值记录
boolean result = rechargeRecordService.updateIng2SuccessAndMbrAccount(record.getRechargeRecordId(), payOrder.getPayOrderId());
log.info("【处理会员充值任务】订单支付成功 rechargeId{}, payOrderId{}, 处理结果:{}", record.getRechargeRecordId(), payOrder.getPayOrderId(), result);
// 订单不为初始化、支付中、支付成功时。处理为充值失败
}else if (PayOrder.STATE_INIT != payOrder.getState() && PayOrder.STATE_ING != payOrder.getState() && PayOrder.STATE_SUCCESS != payOrder.getState()) {
// 处理充值记录
rechargeRecordService.updateIng2Fail(record.getRechargeRecordId(), payOrder.getErrCode(), payOrder.getErrMsg());
log.info("【处理会员充值任务】订单支付失败 rechargeId{}, payOrderId{}, errCode{}, errMsg:{}, 订单状态:{}", record.getRechargeRecordId(), payOrder.getPayOrderId(), payOrder.getErrCode(), payOrder.getErrMsg(), payOrder.getState());
}else {
// 其他状态跳过
break;
}
}
//已经到达页码最大量,无需再次查询
if(resultPage.getPages() <= currentPageIndex){
break;
}
currentPageIndex++;
} catch (Exception e) { //出现异常,直接退出,避免死循环。
log.error("error", e);
break;
}
}
log.info("结束【处理会员充值任务】");
}
}

View File

@@ -0,0 +1,36 @@
package com.jeequan.jeepay.member.web;
import com.jeequan.jeepay.core.utils.ApiResBodyAdviceKit;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* 自定义springMVC返回数据格式
*
* @author terrfly
* @modify zhuxiao
* @date 2021-04-27 15:50
*/
@ControllerAdvice
public class ApiResBodyAdvice implements ResponseBodyAdvice {
/** 判断哪些需要拦截 **/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
/** 拦截返回数据处理 */
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//处理扩展字段
return ApiResBodyAdviceKit.beforeBodyWrite(body);
}
}

View File

@@ -0,0 +1,31 @@
package com.jeequan.jeepay.member.web;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 数据响应拦截器
*
* @author terrfly
* @modify zhuxiao
* @date 2021-04-27 15:50
*/
@Component
public class ApiResInterceptor implements HandlerInterceptor {
/** postHandler是在请求结束之后, 视图渲染之前执行的,但只有preHandle方法返回true的时候才会执行
* 如果ctrl使用了@RestController或者@ResponseBody注解 则ModelAndView = null, 因为不走视图转换器, 而是走的RequestResponseBodyMethodProcessor。
* ————————————————
*
* **/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//do
}
}

View File

@@ -0,0 +1,36 @@
package com.jeequan.jeepay.member.web;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import org.springframework.web.context.ServletContextAware;
import javax.servlet.ServletContext;
/**
* 上下文配置
*
* @author terrfly
* @modify zhuxiao
* @date 2021-04-27 15:50
*/
@Service
public class ApplicationContextKit implements ServletContextAware,InitializingBean{
private ServletContext servletContext ;
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
/**
* afterPropertiesSet 是在什么之后执行? 启动顺序是?
* 调用PropKitSpringBeansUtil.getBean获取方式 会不会出现找不到bean的问题
*
* */
@Override
public void afterPropertiesSet() throws Exception {
}
}

View File

@@ -0,0 +1,26 @@
package com.jeequan.jeepay.member.web;
import com.jeequan.jeepay.core.config.WebConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
/**
* webmvc配置
*
* @author terrfly
* @modify zhuxiao
* @date 2021-04-27 15:50
*/
@Configuration
public class WebmvcConfig extends WebConfig {
@Autowired
private ApiResInterceptor apiResInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(apiResInterceptor);
}
}

View File

@@ -0,0 +1,148 @@
#################################
# spring boot支持外部application.yml 读取优先级为:
# 1、file:./config/当前目录下的config文件夹
# 2、file:./(当前目录)
# 3、classpath:/config/classpath下的config目录
# 4、classpath:/classpath根目录
# 建议: 如果是jar则放置到与jar相同的目录下 如果解压文件放置到classpath: config目录下。 (需要将文件重命名为 application.yml )
#
# 该yml文件只配置与环境相关的参数 其他配置读取项目下的配置项
#
#################################
# 数据库的配置项, 自定义配置放置在配置顶层
db-config:
master: #主库配置(必填)
# yml填写url连接串 无需将&符号进行转义
url: jdbc:mysql://127.0.0.1:3306/yinshangfu?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: YinShangFuMysql@123456
encrypt-account: false # 连接账号和密码是否已加密。 truedb的账密需配置密文函数详见 DBProp.main() false: db账密请填写明文
# 连接池配置项
initial-size: 5 #初始化时建立物理连接的个数
min-idle: 5 #最小连接池数量
max-active: 30 #最大连接池数量
max-wait: 60000 #获取连接时最大等待时间,单位毫秒
# 检测相关
test-while-idle: true # 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。
test-on-borrow: false # 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
test-on-return: false # 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
min-evictable-idle-time-millis: 300000 #连接保持空闲而不被驱逐的最小时间
validation-query: SELECT 1 FROM DUAL
# 是否缓存preparedStatement
pool-prepared-statements: false # 是否缓存preparedStatement也就是PSCache。PSCache对支持游标的数据库性能提升巨大比如说oracle。在mysql下建议关闭。
max-pool-prepared-statement-per-connection-size: 20 # 要启用PSCache必须配置大于0当大于0时poolPreparedStatements自动触发修改为true。
# 配置监控统计拦截的filters去掉后监控界面sql无法统计 通过connectProperties属性来打开mergeSql功能慢SQL记录
filters: stat,wall
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
slave: #从库配置(可选), 若没有显式配置则使用master的配置项。 配置参数与master一致。
# spring-boot组件配置
spring:
servlet:
multipart:
enabled: true #是否启用http上传处理
max-request-size: 10MB #最大请求文件的大小
max-file-size: 10MB #设置单个文件最大长度
resources:
static-locations: classpath:/static #项目静态资源路径 可直接通过http访问
freemarker:
template-loader-path: classpath:/templates #freemarker模板目录
template-encoding: UTF-8
suffix: .ftl
settings:
classic_compatible: true # 如果变量为null,转化为空字符串,比如做比较的时候按照空字符做比较
number_format: '#' #数字格式进行原样显示,不加格式化字符例如 100,00
cache:
type: redis
redis:
host: 127.0.0.1
port: 6379
database: 5 #1库运营平台 #2库商户系统 #3库支付网关 #4库代理商 #5库会员
timeout: 1000
password: ax123456ax
sys-prefix-key: PKMBR_ # 作用:不同系统的前缀。 a.当连接不同的database时可以为空(物理隔离) b.当redis集群时因为必须同一个database所以需通过前缀区分不同系统的业务。
# #activeMQ配置 ( 注意: activeMQ配置项需在spring的下级 )
rabbitmq:
addresses: 127.0.0.1:5672
username: root
password: YinShangFuRabbit@123456
dynamic: true
virtual-host: /
#
# #rabbitmq配置 ( 注意: rabbitmq配置项需在spring的下级 )
# rabbitmq:
# addresses: 127.0.0.1:5672
# username: guest
# password: guest
# dynamic: true
# virtual-host: /
## rocketmq配置 ( 注意rocketmq配置项请放置到根目录 不是spring的二级配置 )
#rocketmq:
# name-server: 127.0.0.1:9876
# producer:
# group: JEEPAY-GROUP
## 阿里云rocketmq配置 ( 注意aliyun-rocketmq配置项请放置到根目录 不是spring的二级配置需要阿里云开通rocketMQ产品创建Group和Topic )
#aliyun-rocketmq:
# namesrvAddr: xxx
# accessKey: xxx
# secretKey: xxx
# groupIdPrefix: GID_JEEPAY_ # (分组前缀, 具体名称详见AliyunRocketMQFactory.java )
#日志配置参数。
# 当存在logback-spring.xml文件时 该配置将引进到logback配置 springboot配置不生效。
# 不存在logback-spring.xml 文件时, 使用springboot的配置 同样可用。
logging:
level:
root: info #主日志级别
com.jeequan.jeepay: info #该项目日志级别当需要打印sql时请开启为debug
path: ./member/logs #日志存放地址
# xxl-job 执行器配置项
xxl-job:
executor:
admin-address: http://127.0.0.1:9300/xxl-job-admin # 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
access-token: jeepayTaskToken@2022 # 执行器通讯TOKEN
appname: jeepay-plus-member-executor # 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
port: 9321 # 执行器端口号 [选填]小于等于0则自动获取默认端口为9999单机部署多个执行器时注意要配置不同执行器端口
log-path: ${logging.file.path} # 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
logretentiondays: 7 # 执行器日志文件保存天数 [选填] 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
#xxl-job:
# executor:
# admin-address: http://127.0.0.1:9300/xxl-job-admin # 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
# access-token: jeepayTaskToken@2022 # 执行器通讯TOKEN
# appname: jeepay-plus-member-executor # 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
# port: 9321 # 执行器端口号 [选填]小于等于0则自动获取默认端口为9999单机部署多个执行器时注意要配置不同执行器端口
# log-path: ${logging.file.path} # 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
# logretentiondays: 7 # 执行器日志文件保存天数 [选填] 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
#系统业务参数
isys:
jwt-secret: zCfdYJepLgVLBw3c #生成jwt的秘钥。 要求每个系统有单独的秘钥管理机制。
db-encrypt-secret: 1234567890123456 #DB SM4 加解密秘钥 (必须16位) [每个系统配置必须相同,否则加解密不一致导致业务异常!]
http-message-encrypt-secret: 1234567890123456 #web传输加解密 秘钥 (必须16位) [每个系统配置必须相同,否则加解密不一致导致业务异常!]
# 支付网关的公钥和私钥(系统级别!), 请妥善保存,用于回调商户的商户侧的验证, 首次设置好之后不可随意变更!
sys-RSA2-private-key: MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCLU0lXJu9vcJCwrwFGwT1CqXUSY+JMW2iSRPtgLw6lxjU403hS1VyKxA5uTZokv80Wd+bcxDMrM9DwHeGqhjlWvbB/lqaRliJJRdgLVUedrDY+3YSN75uHBtLmxZbnaUpV6/aWA/a+lkNqHxYCWB1jggFB9DhVg2SgoMGDOYHDNA5ZDl47sKpanW0kfG9lINSV4xgDPmAJxUnhkG/7eVEBDev9EIhc+LIMC7Zj+UYc+s/TK4c31fFTW4dZAp3LVEdct9qXU5qm/fXIXxeo3E0bWyOP3VL4Xmx2S9Host1YzjzXPSi1TJjX7rxQkhmbE/dQJ+ws/VvKPuUpmLEPGsm/AgMBAAECggEBAIONCFq58KoQZw3ssA/WtbkTt+69URc31+0EJTYUOIheNjKJubq8qrx7kgSkUT8RutvUKq+YsZfBPS77h/Ay/EDiqpxN6sjcMVNuFyfcRdqimDWTg21hKEC+OLSdLHcj+4RVYGcVJw2dY9n3sBhWiqlCP12+8tILViA0qYL18YgVYJM3bL2MCXfUnm8/Rn1ut5LuDtU/UuaVz9vuCqNirZJedZx3WEq69ZRt9m44XN934NikbUGxQRlz32WRXDo+ssSTu3174UbDYc8nhqO0jUvuzfjOOMf9NYRJsgqVihxMLvvMaUhEE3w6qZPLMj6KhTiG5QHexBbyLgDxH4TZ4gECgYEAz7vZFMoKYifCi+eFykH/ad8QYYoYLlrU1EN2fpIJqwSHZbpBfB8NM5Ov26xU/aNHGKxtSOrUFva5sWub5f9OlsOAmaUOPuVuaJbsq+e3cNQ41jBcdppGJLNx1p/69zR1rF9TbI9Ambd8sOgX3OZmImA2Ldlk2KvMKRruxwc4uN8CgYEAq7J0u5KTwI3PxENLWQ/6tOBNdbCoyqFM+FdANVHRA4dhhdoJ1x0bpdt3tapPFJTBURSEspNxPl4iT+GdpoF6KwqTsQFmB5TLlfx8wY9SIc+sI/ifZvMA5Dv8vVfYWNig7rGV+vIyJCCNbJ5OMa1xvxTDc7Dx4XPxcJ4sR1ZyqyECgYAe447O8ZADsmfSR9X0EkY5ZurXpiIcWnNFMNbg0TRQ0raTYNO18iQTZEWFA6YLpQjAWXtSmWB6HavU/uxKkeEMt/taXVm17oWxVafRk/4J7/SXnM9S73O4p1opENbPhWRuAiq0fMSdVtRatdg+h5/uQqIrxSSit0D/Z7rTq3Y6vwKBgQCd8AlbJckOHiTZd8GOypkm2xHFydxqkJfZ9YCVy44Fvfnig5/7pcXx+oEStfgKiY+OQt6R2fkYksTjUDmRmZbEkvUqpIuzO5dOf7RO5MR7X6oMaL5QmAXg7KFflrfnelYHW4oIDdQ70UnmeXSaU97HE5V7DXBioCGfI5C9inLuoQKBgQCwJmQ2heyTbG1DIBuqf+GFXLuOp76M/7S9c+5R7yfxyTzAbiKRIPeSF5wlxNXEnGwK7qB9CmctlBdnV7A0qnZVFMXf7AbBDUzOCjiy1RSh04BNPnu0dTIygX2PE5inltrHiZtTgciKwj9MexT97F4mTR76kIMz5SGNZe3PscQr7g==
sys-RSA2-public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi1NJVybvb3CQsK8BRsE9Qql1EmPiTFtokkT7YC8OpcY1ONN4UtVcisQObk2aJL/NFnfm3MQzKzPQ8B3hqoY5Vr2wf5amkZYiSUXYC1VHnaw2Pt2Eje+bhwbS5sWW52lKVev2lgP2vpZDah8WAlgdY4IBQfQ4VYNkoKDBgzmBwzQOWQ5eO7CqWp1tJHxvZSDUleMYAz5gCcVJ4ZBv+3lRAQ3r/RCIXPiyDAu2Y/lGHPrP0yuHN9XxU1uHWQKdy1RHXLfal1Oapv31yF8XqNxNG1sjj91S+F5sdkvR6LLdWM481z0otUyY1+68UJIZmxP3UCfsLP1byj7lKZixDxrJvwIDAQAB
#是否允许跨域请求 [生产环境建议关闭, 若api与前端项目没有在同一个域名下时应开启此配置或在nginx统一配置允许跨域]
allow-cors: true
#是否内存缓存配置信息: true表示开启如支付网关地址/商户应用配置/服务商配置等, 开启后需检查MQ的广播模式是否正常 false表示直接查询DB.
cache-config: false
oss:
file-root-path: /home/jeepay/upload #存储根路径 ( 无需以‘/’结尾 )
file-public-path: ${isys.oss.file-root-path}/public #公共读取块 ( 一般配合root-path参数进行设置需以/ 开头, 无需以‘/’结尾 )
file-private-path: ${isys.oss.file-root-path}/private #私有化本地访问不允许url方式公共读取 ( 一般配合root-path参数进行设置需以/ 开头, 无需以‘/’结尾 )
mq:
vender: rabbitMQ # 切换MQ厂商 支持:【 activeMQ rabbitMQ rocketMQ aliYunRocketMQ 】, 需正确配置 【对应的yml参数】 和 【jeepay-components-mq项目下pom.xml中的依赖包】。

View File

@@ -0,0 +1,114 @@
# spring-boot组件配置
spring:
servlet:
multipart:
enabled: true #是否启用http上传处理
max-request-size: 10MB #最大请求文件的大小
max-file-size: 10MB #设置单个文件最大长度
resources:
static-locations: classpath:/static #项目静态资源路径 可直接通过http访问
freemarker:
template-loader-path: classpath:/templates #freemarker模板目录
template-encoding: UTF-8
suffix: .ftl
settings:
classic_compatible: true # 如果变量为null,转化为空字符串,比如做比较的时候按照空字符做比较
number_format: '#' #数字格式进行原样显示,不加格式化字符例如 100,00
cache:
type: redis
redis:
host: 127.0.0.1
port: 6379
timeout: 1000
password: ax123456ax
activemq:
broker-url: failover:(tcp://127.0.0.1:61616?wireFormat.maxInactivityDuration=0) #
in-memory: false # Jeepay
user: admin # activeMQ
password: 123456
pool:
enabled: true
max-connections: 10
idle-timeout: 30000 #
logging:
level:
root: info #主日志级别
# xxl-job 执行器配置项
xxl-job:
executor:
admin-address: http://127.0.0.1:8282/xxl-job-admin # 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
access-token: jeepayTaskToken@2022 # 执行器通讯TOKEN
# appname: jeepay-plus-bill-executor # 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
# port: # 执行器端口号 [选填]小于等于0则自动获取默认端口为9999单机部署多个执行器时注意要配置不同执行器端口
log-path: ${logging.file.path} # 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
logretentiondays: 7 # 执行器日志文件保存天数 [选填] 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
db-config:
master: #主库配置(必填)
# yml填写url连接串 无需将&符号进行转义
url: jdbc:p6spy:mysql://rm-bp1c8prh6j399epb5.mysql.rds.aliyuncs.com/yinshangfu?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
username: yinshangfu
password: YinShangFuMysql@123456
encrypt-account: false # 连接账号和密码是否已加密。 truedb的账密需配置密文函数详见 DBProp.main() false: db账密请填写明文
# 连接池配置项
initial-size: 5 #初始化时建立物理连接的个数
min-idle: 5 #最小连接池数量
max-active: 30 #最大连接池数量
max-wait: 60000 #获取连接时最大等待时间,单位毫秒
# 检测相关
test-while-idle: true # 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。
test-on-borrow: false # 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
test-on-return: false # 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
min-evictable-idle-time-millis: 300000 #连接保持空闲而不被驱逐的最小时间
validation-query: SELECT 1 FROM DUAL
# 是否缓存preparedStatement
pool-prepared-statements: false # 是否缓存preparedStatement也就是PSCache。PSCache对支持游标的数据库性能提升巨大比如说oracle。在mysql下建议关闭。
max-pool-prepared-statement-per-connection-size: 20 # 要启用PSCache必须配置大于0当大于0时poolPreparedStatements自动触发修改为true。
# 配置监控统计拦截的filters去掉后监控界面sql无法统计 通过connectProperties属性来打开mergeSql功能慢SQL记录
filters: stat,wall
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
slave: #从库配置(可选), 若没有显式配置则使用master的配置项。 配置参数与master一致。
# yml填写url连接串 无需将&符号进行转义
url: jdbc:p6spy:mysql://rm-bp1c8prh6j399epb5.mysql.rds.aliyuncs.com/yinshangfu?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
username: yinshangfu
password: YinShangFuMysql@123456
encrypt-account: false # 连接账号和密码是否已加密。 truedb的账密需配置密文函数详见 DBProp.main() false: db账密请填写明文
# 连接池配置项
initial-size: 5 #初始化时建立物理连接的个数
min-idle: 5 #最小连接池数量
max-active: 30 #最大连接池数量
max-wait: 60000 #获取连接时最大等待时间,单位毫秒
# 检测相关
test-while-idle: true # 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。
test-on-borrow: false # 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
test-on-return: false # 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
min-evictable-idle-time-millis: 300000 #连接保持空闲而不被驱逐的最小时间
validation-query: SELECT 1 FROM DUAL
# 是否缓存preparedStatement
pool-prepared-statements: false # 是否缓存preparedStatement也就是PSCache。PSCache对支持游标的数据库性能提升巨大比如说oracle。在mysql下建议关闭。
max-pool-prepared-statement-per-connection-size: 20 # 要启用PSCache必须配置大于0当大于0时poolPreparedStatements自动触发修改为true。
# 配置监控统计拦截的filters去掉后监控界面sql无法统计 通过connectProperties属性来打开mergeSql功能慢SQL记录
filters: stat,wall
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
isys:
#是否允许跨域请求 [生产环境建议关闭, 若api与前端项目没有在同一个域名下时应开启此配置或在nginx统一配置允许跨域]
allow-cors: true
db-encrypt-secret: 1234567890123456 #DB SM4 加解密秘钥 (必须16位) [每个系统配置必须相同,否则加解密不一致导致业务异常!]
http-message-encrypt-secret: 1234567890123456 #web传输加解密 秘钥 (必须16位) [每个系统配置必须相同,否则加解密不一致导致业务异常!]
# 支付网关的公钥和私钥(系统级别!), 请妥善保存,用于回调商户的商户侧的验证, 首次设置好之后不可随意变更!
sys-RSA2-private-key: MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCLU0lXJu9vcJCwrwFGwT1CqXUSY+JMW2iSRPtgLw6lxjU403hS1VyKxA5uTZokv80Wd+bcxDMrM9DwHeGqhjlWvbB/lqaRliJJRdgLVUedrDY+3YSN75uHBtLmxZbnaUpV6/aWA/a+lkNqHxYCWB1jggFB9DhVg2SgoMGDOYHDNA5ZDl47sKpanW0kfG9lINSV4xgDPmAJxUnhkG/7eVEBDev9EIhc+LIMC7Zj+UYc+s/TK4c31fFTW4dZAp3LVEdct9qXU5qm/fXIXxeo3E0bWyOP3VL4Xmx2S9Host1YzjzXPSi1TJjX7rxQkhmbE/dQJ+ws/VvKPuUpmLEPGsm/AgMBAAECggEBAIONCFq58KoQZw3ssA/WtbkTt+69URc31+0EJTYUOIheNjKJubq8qrx7kgSkUT8RutvUKq+YsZfBPS77h/Ay/EDiqpxN6sjcMVNuFyfcRdqimDWTg21hKEC+OLSdLHcj+4RVYGcVJw2dY9n3sBhWiqlCP12+8tILViA0qYL18YgVYJM3bL2MCXfUnm8/Rn1ut5LuDtU/UuaVz9vuCqNirZJedZx3WEq69ZRt9m44XN934NikbUGxQRlz32WRXDo+ssSTu3174UbDYc8nhqO0jUvuzfjOOMf9NYRJsgqVihxMLvvMaUhEE3w6qZPLMj6KhTiG5QHexBbyLgDxH4TZ4gECgYEAz7vZFMoKYifCi+eFykH/ad8QYYoYLlrU1EN2fpIJqwSHZbpBfB8NM5Ov26xU/aNHGKxtSOrUFva5sWub5f9OlsOAmaUOPuVuaJbsq+e3cNQ41jBcdppGJLNx1p/69zR1rF9TbI9Ambd8sOgX3OZmImA2Ldlk2KvMKRruxwc4uN8CgYEAq7J0u5KTwI3PxENLWQ/6tOBNdbCoyqFM+FdANVHRA4dhhdoJ1x0bpdt3tapPFJTBURSEspNxPl4iT+GdpoF6KwqTsQFmB5TLlfx8wY9SIc+sI/ifZvMA5Dv8vVfYWNig7rGV+vIyJCCNbJ5OMa1xvxTDc7Dx4XPxcJ4sR1ZyqyECgYAe447O8ZADsmfSR9X0EkY5ZurXpiIcWnNFMNbg0TRQ0raTYNO18iQTZEWFA6YLpQjAWXtSmWB6HavU/uxKkeEMt/taXVm17oWxVafRk/4J7/SXnM9S73O4p1opENbPhWRuAiq0fMSdVtRatdg+h5/uQqIrxSSit0D/Z7rTq3Y6vwKBgQCd8AlbJckOHiTZd8GOypkm2xHFydxqkJfZ9YCVy44Fvfnig5/7pcXx+oEStfgKiY+OQt6R2fkYksTjUDmRmZbEkvUqpIuzO5dOf7RO5MR7X6oMaL5QmAXg7KFflrfnelYHW4oIDdQ70UnmeXSaU97HE5V7DXBioCGfI5C9inLuoQKBgQCwJmQ2heyTbG1DIBuqf+GFXLuOp76M/7S9c+5R7yfxyTzAbiKRIPeSF5wlxNXEnGwK7qB9CmctlBdnV7A0qnZVFMXf7AbBDUzOCjiy1RSh04BNPnu0dTIygX2PE5inltrHiZtTgciKwj9MexT97F4mTR76kIMz5SGNZe3PscQr7g==
sys-RSA2-public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi1NJVybvb3CQsK8BRsE9Qql1EmPiTFtokkT7YC8OpcY1ONN4UtVcisQObk2aJL/NFnfm3MQzKzPQ8B3hqoY5Vr2wf5amkZYiSUXYC1VHnaw2Pt2Eje+bhwbS5sWW52lKVev2lgP2vpZDah8WAlgdY4IBQfQ4VYNkoKDBgzmBwzQOWQ5eO7CqWp1tJHxvZSDUleMYAz5gCcVJ4ZBv+3lRAQ3r/RCIXPiyDAu2Y/lGHPrP0yuHN9XxU1uHWQKdy1RHXLfal1Oapv31yF8XqNxNG1sjj91S+F5sdkvR6LLdWM481z0otUyY1+68UJIZmxP3UCfsLP1byj7lKZixDxrJvwIDAQAB
#是否内存缓存配置信息: true表示开启如支付网关地址/商户应用配置/服务商配置等, 开启后需检查MQ的广播模式是否正常 false表示直接查询DB.
cache-config: false
mq:
vender: activeMQ
oss:
file-root-path: /home/www/.upload #存储根路径 ( 无需以‘/’结尾 )
file-public-path: ${isys.oss.file-root-path}/public #公共读取块 ( 一般配合root-path参数进行设置需以/ 开头, 无需以‘/’结尾 )

View File

@@ -0,0 +1,111 @@
# spring-boot组件配置
spring:
servlet:
multipart:
enabled: true #是否启用http上传处理
max-request-size: 10MB #最大请求文件的大小
max-file-size: 10MB #设置单个文件最大长度
resources:
static-locations: classpath:/static #项目静态资源路径 可直接通过http访问
freemarker:
template-loader-path: classpath:/templates #freemarker模板目录
template-encoding: UTF-8
suffix: .ftl
settings:
classic_compatible: true # 如果变量为null,转化为空字符串,比如做比较的时候按照空字符做比较
number_format: '#' #数字格式进行原样显示,不加格式化字符例如 100,00
cache:
type: redis
redis:
host: 47.111.143.211
port: 6379
timeout: 1000
password: ax123456ax
rabbitmq:
addresses: 47.111.143.211:5672
username: root
password: YinShangFuRabbit@123456
dynamic: true
virtual-host: /
logging:
level:
root: info #主日志级别
# xxl-job 执行器配置项
xxl-job:
executor:
admin-address: http://47.111.143.211:8282/xxl-job-admin # 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
access-token: jeepayTaskToken@2022 # 执行器通讯TOKEN
# appname: jeepay-plus-bill-executor # 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
# port: # 执行器端口号 [选填]小于等于0则自动获取默认端口为9999单机部署多个执行器时注意要配置不同执行器端口
log-path: ${logging.file.path} # 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
logretentiondays: 7 # 执行器日志文件保存天数 [选填] 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
db-config:
master: #主库配置(必填)
# yml填写url连接串 无需将&符号进行转义
url: jdbc:mysql://47.111.143.211:3306/yinshangfu?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: YinShangFuMysql@123456
encrypt-account: false # 连接账号和密码是否已加密。 truedb的账密需配置密文函数详见 DBProp.main() false: db账密请填写明文
# 连接池配置项
initial-size: 5 #初始化时建立物理连接的个数
min-idle: 5 #最小连接池数量
max-active: 30 #最大连接池数量
max-wait: 60000 #获取连接时最大等待时间,单位毫秒
# 检测相关
test-while-idle: true # 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。
test-on-borrow: false # 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
test-on-return: false # 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
min-evictable-idle-time-millis: 300000 #连接保持空闲而不被驱逐的最小时间
validation-query: SELECT 1 FROM DUAL
# 是否缓存preparedStatement
pool-prepared-statements: false # 是否缓存preparedStatement也就是PSCache。PSCache对支持游标的数据库性能提升巨大比如说oracle。在mysql下建议关闭。
max-pool-prepared-statement-per-connection-size: 20 # 要启用PSCache必须配置大于0当大于0时poolPreparedStatements自动触发修改为true。
# 配置监控统计拦截的filters去掉后监控界面sql无法统计 通过connectProperties属性来打开mergeSql功能慢SQL记录
filters: stat,wall
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
slave: #从库配置(可选), 若没有显式配置则使用master的配置项。 配置参数与master一致。
# yml填写url连接串 无需将&符号进行转义
url: jdbc:mysql://47.111.143.211:3306/yinshangfu?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: YinShangFuMysql@123456
encrypt-account: false # 连接账号和密码是否已加密。 truedb的账密需配置密文函数详见 DBProp.main() false: db账密请填写明文
# 连接池配置项
initial-size: 5 #初始化时建立物理连接的个数
min-idle: 5 #最小连接池数量
max-active: 30 #最大连接池数量
max-wait: 60000 #获取连接时最大等待时间,单位毫秒
# 检测相关
test-while-idle: true # 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。
test-on-borrow: false # 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
test-on-return: false # 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
min-evictable-idle-time-millis: 300000 #连接保持空闲而不被驱逐的最小时间
validation-query: SELECT 1 FROM DUAL
# 是否缓存preparedStatement
pool-prepared-statements: false # 是否缓存preparedStatement也就是PSCache。PSCache对支持游标的数据库性能提升巨大比如说oracle。在mysql下建议关闭。
max-pool-prepared-statement-per-connection-size: 20 # 要启用PSCache必须配置大于0当大于0时poolPreparedStatements自动触发修改为true。
# 配置监控统计拦截的filters去掉后监控界面sql无法统计 通过connectProperties属性来打开mergeSql功能慢SQL记录
filters: stat,wall
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
isys:
#是否允许跨域请求 [生产环境建议关闭, 若api与前端项目没有在同一个域名下时应开启此配置或在nginx统一配置允许跨域]
allow-cors: true
db-encrypt-secret: 1234567890123456 #DB SM4 加解密秘钥 (必须16位) [每个系统配置必须相同,否则加解密不一致导致业务异常!]
http-message-encrypt-secret: 1234567890123456 #web传输加解密 秘钥 (必须16位) [每个系统配置必须相同,否则加解密不一致导致业务异常!]
# 支付网关的公钥和私钥(系统级别!), 请妥善保存,用于回调商户的商户侧的验证, 首次设置好之后不可随意变更!
sys-RSA2-private-key: MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCLU0lXJu9vcJCwrwFGwT1CqXUSY+JMW2iSRPtgLw6lxjU403hS1VyKxA5uTZokv80Wd+bcxDMrM9DwHeGqhjlWvbB/lqaRliJJRdgLVUedrDY+3YSN75uHBtLmxZbnaUpV6/aWA/a+lkNqHxYCWB1jggFB9DhVg2SgoMGDOYHDNA5ZDl47sKpanW0kfG9lINSV4xgDPmAJxUnhkG/7eVEBDev9EIhc+LIMC7Zj+UYc+s/TK4c31fFTW4dZAp3LVEdct9qXU5qm/fXIXxeo3E0bWyOP3VL4Xmx2S9Host1YzjzXPSi1TJjX7rxQkhmbE/dQJ+ws/VvKPuUpmLEPGsm/AgMBAAECggEBAIONCFq58KoQZw3ssA/WtbkTt+69URc31+0EJTYUOIheNjKJubq8qrx7kgSkUT8RutvUKq+YsZfBPS77h/Ay/EDiqpxN6sjcMVNuFyfcRdqimDWTg21hKEC+OLSdLHcj+4RVYGcVJw2dY9n3sBhWiqlCP12+8tILViA0qYL18YgVYJM3bL2MCXfUnm8/Rn1ut5LuDtU/UuaVz9vuCqNirZJedZx3WEq69ZRt9m44XN934NikbUGxQRlz32WRXDo+ssSTu3174UbDYc8nhqO0jUvuzfjOOMf9NYRJsgqVihxMLvvMaUhEE3w6qZPLMj6KhTiG5QHexBbyLgDxH4TZ4gECgYEAz7vZFMoKYifCi+eFykH/ad8QYYoYLlrU1EN2fpIJqwSHZbpBfB8NM5Ov26xU/aNHGKxtSOrUFva5sWub5f9OlsOAmaUOPuVuaJbsq+e3cNQ41jBcdppGJLNx1p/69zR1rF9TbI9Ambd8sOgX3OZmImA2Ldlk2KvMKRruxwc4uN8CgYEAq7J0u5KTwI3PxENLWQ/6tOBNdbCoyqFM+FdANVHRA4dhhdoJ1x0bpdt3tapPFJTBURSEspNxPl4iT+GdpoF6KwqTsQFmB5TLlfx8wY9SIc+sI/ifZvMA5Dv8vVfYWNig7rGV+vIyJCCNbJ5OMa1xvxTDc7Dx4XPxcJ4sR1ZyqyECgYAe447O8ZADsmfSR9X0EkY5ZurXpiIcWnNFMNbg0TRQ0raTYNO18iQTZEWFA6YLpQjAWXtSmWB6HavU/uxKkeEMt/taXVm17oWxVafRk/4J7/SXnM9S73O4p1opENbPhWRuAiq0fMSdVtRatdg+h5/uQqIrxSSit0D/Z7rTq3Y6vwKBgQCd8AlbJckOHiTZd8GOypkm2xHFydxqkJfZ9YCVy44Fvfnig5/7pcXx+oEStfgKiY+OQt6R2fkYksTjUDmRmZbEkvUqpIuzO5dOf7RO5MR7X6oMaL5QmAXg7KFflrfnelYHW4oIDdQ70UnmeXSaU97HE5V7DXBioCGfI5C9inLuoQKBgQCwJmQ2heyTbG1DIBuqf+GFXLuOp76M/7S9c+5R7yfxyTzAbiKRIPeSF5wlxNXEnGwK7qB9CmctlBdnV7A0qnZVFMXf7AbBDUzOCjiy1RSh04BNPnu0dTIygX2PE5inltrHiZtTgciKwj9MexT97F4mTR76kIMz5SGNZe3PscQr7g==
sys-RSA2-public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi1NJVybvb3CQsK8BRsE9Qql1EmPiTFtokkT7YC8OpcY1ONN4UtVcisQObk2aJL/NFnfm3MQzKzPQ8B3hqoY5Vr2wf5amkZYiSUXYC1VHnaw2Pt2Eje+bhwbS5sWW52lKVev2lgP2vpZDah8WAlgdY4IBQfQ4VYNkoKDBgzmBwzQOWQ5eO7CqWp1tJHxvZSDUleMYAz5gCcVJ4ZBv+3lRAQ3r/RCIXPiyDAu2Y/lGHPrP0yuHN9XxU1uHWQKdy1RHXLfal1Oapv31yF8XqNxNG1sjj91S+F5sdkvR6LLdWM481z0otUyY1+68UJIZmxP3UCfsLP1byj7lKZixDxrJvwIDAQAB
#是否内存缓存配置信息: true表示开启如支付网关地址/商户应用配置/服务商配置等, 开启后需检查MQ的广播模式是否正常 false表示直接查询DB.
cache-config: false
mq:
vender: rabbitMQ
oss:
file-root-path: D:/jeepayFiles #存储根路径 ( 无需以‘/’结尾 )
file-public-path: ${isys.oss.file-root-path}/public #公共读取块 ( 一般配合root-path参数进行设置需以/ 开头, 无需以‘/’结尾 )

View File

@@ -0,0 +1,7 @@
server:
port: 9221 #设置端口
servlet:
context-path: / #设置应用的目录. 前缀需要带/, 无需设置后缀, 示例 【 /xxx 】 or 【 / 】
spring:
profiles:
active: dev

View File

@@ -0,0 +1,8 @@
__
/ /___ ___ ____ ____ ___ __
__ / // _ \/ _ \/ __ \/ __ `/ / / /
/ /_/ // __/ __/ /_/ / /_/ / /_/ /
\____/ \___/\___/ .___/\__,_/\__, /
/_/ /____/
:: Jeepay Plus S3 :: (v3.3.6.RELEASE)
计全支付 - 让支付接入更简单 : https://www.jeequan.com

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false" debug="false">
<!-- 日志存放路径, 读取application.yml 需要使用springProperty获取 -->
<springProperty scope="context" name="currentLoggerFilePath" source="logging.file.path"/>
<!-- 主日志级别配置 -->
<springProperty scope="context" name="currentRootLevel" source="logging.level.root"/>
<!-- 项目配置, 如修改包名,请搜索并全部替换掉 -->
<springProperty scope="context" name="currentProjectLevel" source="logging.level.com.jeequan.jeepay"/>
<!-- 日志文件名称 logback属性 -->
<property name="currentLoggerFileName" value="mbr" />
<!-- 日志格式, 参考https://logback.qos.ch/manual/layouts.html -->
<property name="currentLoggerPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} - %msg%n" />
<!-- appender 控制台日志 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8" >
<pattern>${currentLoggerPattern}</pattern>
</encoder>
</appender>
<!-- appender主日志文件 -->
<appender name="ALL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志文件路径及文件名 -->
<file>${currentLoggerFilePath}/${currentLoggerFileName}.all.log</file>
<!-- 内容编码及格式 -->
<encoder charset="UTF-8" ><pattern>${currentLoggerPattern}</pattern></encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${currentLoggerFilePath}/${currentLoggerFileName}.all.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>5</maxHistory> <!--日志文件保留天数-->
</rollingPolicy>
</appender>
<!-- appender错误信息日志文件 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志文件路径及文件名 -->
<file>${currentLoggerFilePath}/${currentLoggerFileName}.error.log</file>
<!-- 内容编码及格式 -->
<encoder charset="UTF-8" ><pattern>${currentLoggerPattern}</pattern></encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${currentLoggerFilePath}/${currentLoggerFileName}.error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>20</maxHistory> <!--日志文件保留天数-->
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 主日志级别配置 -->
<root level="${currentRootLevel}">
<appender-ref ref="STDOUT" />
<appender-ref ref="ALL_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
<!-- 项目日志级别配置 -->
<logger name="com.jeequan.jeepay" level="${currentProjectLevel}"/>
</configuration>

View File

@@ -0,0 +1,4 @@
<html>
<body>
</body>
</html>