Browse Source

添加微信支付功能,修改公众号登陆接口

master
zhangye 3 years ago
parent
commit
93ed25d202
  1. 143
      cloudutil/src/main/resources/application-util-green.yml
  2. 66
      cloudutil/src/main/resources/application-util-greenvalley.yml
  3. 82
      cloudutil/src/main/resources/application-util-prodsd.yml
  4. 24
      ptos_tall/src/main/java/com/ccsens/ptos_tall/service/UserService.java
  5. 16
      ptos_tall/src/main/java/com/ccsens/ptos_tall/service/WxUserService.java
  6. 2
      util/src/main/java/com/ccsens/util/WebConstant.java
  7. 20
      util/src/main/java/com/ccsens/util/bean/wx/po/WxApiTicket.java
  8. 21
      util/src/main/java/com/ccsens/util/wx/WxGzhUtil.java
  9. 120
      wechatutil/src/main/java/com/ccsens/wechatutil/bean/vo/WxNativePay.java
  10. 47
      wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativePay.java
  11. 265
      wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativeUtils.java
  12. 1
      wechatutil/src/main/java/com/ccsens/wechatutil/util/WxCodeError.java
  13. 2
      wechatutil/src/main/java/com/ccsens/wechatutil/util/WxConstant.java
  14. 36
      wechatutil/src/main/java/com/ccsens/wechatutil/wxcommon/WxCommonUtil.java
  15. 2
      wechatutil/src/main/java/com/ccsens/wechatutil/wxofficial/OfficialAccountMessageUtil.java

143
cloudutil/src/main/resources/application-util-green.yml

@ -1,143 +0,0 @@
#<<<<<<< HEAD
##服务端点暴露
#management:
# endpoints:
# web:
# exposure:
# # 暴露xxx端点,如需暴露多个,用,分隔;如需暴露所有端点,用'*'
# include: auditevents,caches,conditions,flyway,health,heapdump,httptrace,info,integrationgraph,jolokia,logfile,loggers,liquibase,metrics,mappings,prometheus,scheduledtasks,sessions,shutdown,threaddump,hystrix.stream
## # 不暴露哪些端点
## exclude: env,beans,configprops
# endpoint:
# health:
# # 是否展示健康检查详情
# show-details: always
# health:
# redis:
# enabled: false
##eureka注册
#eureka:
# client:
# service-url:
# # 指定eureka server通信地址,注意/eureka/小尾巴不能少
# #defaultZone: http://admin:admin@peer1:8761/eureka/,http://admin:admin@peer2:8762/eureka/
## defaultZone: http://admin:admin@49.233.89.188:7010/eureka/
# defaultZone: http://admin:admin@192.168.0.99:7010/eureka/
# instance:
# # 是否注册IP到eureka server,如不指定或设为false,那就回注册主机名到eureka server
# prefer-ip-address: true
# metadata-map:
# management:
# context-path: ${server.servlet.context-path:}/actuator
# home-page-url-path: ${server.servlet.context-path:}/
# status-page-url-path: ${server.servlet.context-path:}/actuator/info
# health-check-url-path: ${server.servlet.context-path:}/actuator/health
#feign:
# client:
# config:
# default:
# connectTime: 5000
# readTimeout: 5000
# # NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
# # BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
# # HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
# # FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据
# loggerLevel: basic
# hystrix:
# enabled: true
## sleuth
#logging:
# level:
# root: info
# org.springframework.cloud.sleuth: DEBUG
#spring:
## zipkin:
## base-url: http://49.233.89.188:9411
## sleuth:
## sampler:
## # 采样率,模式0.1,也就是10%,为了便于观察效果,改为1.0,也就是100%。生产环境建议保持默认。
## probability: 1.0
# cloud:
# inetutils:
#=======
#服务端点暴露
management:
endpoints:
web:
exposure:
# 暴露xxx端点,如需暴露多个,用,分隔;如需暴露所有端点,用'*'
include: auditevents,caches,conditions,flyway,health,heapdump,httptrace,info,integrationgraph,jolokia,logfile,loggers,liquibase,metrics,mappings,prometheus,scheduledtasks,sessions,shutdown,threaddump,hystrix.stream
# # 不暴露哪些端点
# exclude: env,beans,configprops
endpoint:
health:
# 是否展示健康检查详情
show-details: always
health:
redis:
enabled: false
#eureka注册
eureka:
client:
service-url:
# 指定eureka server通信地址,注意/eureka/小尾巴不能少
#defaultZone: http://admin:admin@peer1:8761/eureka/,http://admin:admin@peer2:8762/eureka/
defaultZone: http://admin:admin@49.232.6.143:7010/eureka/
# defaultZone: http://admin:admin@192.168.0.99:7010/eureka/
# defaultZone: http://admin:admin@test.tall.wiki:7010/eureka/
instance:
# 是否注册IP到eureka server,如不指定或设为false,那就回注册主机名到eureka server
prefer-ip-address: true
metadata-map:
management:
context-path: ${server.servlet.context-path:}/actuator
home-page-url-path: ${server.servlet.context-path:}/
status-page-url-path: ${server.servlet.context-path:}/actuator/info
health-check-url-path: ${server.servlet.context-path:}/actuator/health
feign:
client:
config:
default:
connectTime: 5000
readTimeout: 5000
# NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
# BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
# HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
# FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据
loggerLevel: basic
hystrix:
enabled: true
hystrix:
threadpool:
default:
coreSize: 200 #并发执行的最大线程数,默认10
maxQueueSize: 1000 #BlockingQueue的最大队列数,默认值-1
queueSizeRejectionThreshold: 800 #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5
command:
default:
execution:
timeout:
enabled: true
isolation:
strategy: SEMAPHORE
thread:
timeoutInMilliseconds: 60000
#ribbon的超时时间
ribbon:
ReadTimeout: 60000
ConnectTimeout: 60000
# sleuth
logging:
level:
root: info
org.springframework.cloud.sleuth: DEBUG
spring:
# zipkin:
# base-url: http://49.233.89.188:9411
# sleuth:
# sampler:
# # 采样率,模式0.1,也就是10%,为了便于观察效果,改为1.0,也就是100%。生产环境建议保持默认。
# probability: 1.0
cloud:
inetutils:
ignored-interfaces: ['VMware.*']

66
cloudutil/src/main/resources/application-util-greenvalley.yml

@ -1,66 +0,0 @@
#服务端点暴露
management:
endpoints:
web:
exposure:
# 暴露xxx端点,如需暴露多个,用,分隔;如需暴露所有端点,用'*'
include: auditevents,caches,conditions,flyway,health,heapdump,httptrace,info,integrationgraph,jolokia,logfile,loggers,liquibase,metrics,mappings,prometheus,scheduledtasks,sessions,shutdown,threaddump,hystrix.stream
# # 不暴露哪些端点
# exclude: env,beans,configprops
endpoint:
health:
# 是否展示健康检查详情
show-details: always
health:
redis:
enabled: false
#eureka注册
eureka:
client:
service-url:
# 指定eureka server通信地址,注意/eureka/小尾巴不能少
defaultZone: http://admin:admin@82.157.24.76:7010/eureka/
# defaultZone: http://admin:admin@49.232.6.143:7010/eureka/
instance:
# 是否注册IP到eureka server,如不指定或设为false,那就回注册主机名到eureka server
prefer-ip-address: true
metadata-map:
management:
context-path: ${server.servlet.context-path:}/actuator
home-page-url-path: ${server.servlet.context-path:}/
status-page-url-path: ${server.servlet.context-path:}/actuator/info
health-check-url-path: ${server.servlet.context-path:}/actuator/health
feign:
client:
config:
default:
connectTime: 5000
readTimeout: 5000
# NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
# BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
# HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
# FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据
loggerLevel: basic
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
thread:
timeoutInMilliseconds: 60000
#ribbon的超时时间
ribbon:
ReadTimeout: 60000
ConnectTimeout: 60000
# sleuth
logging:
level:
root: info
org.springframework.cloud.sleuth: DEBUG
spring:
cloud:
inetutils:
ignored-interfaces: ['VMware.*']

82
cloudutil/src/main/resources/application-util-prodsd.yml

@ -1,82 +0,0 @@
#服务端点暴露
management:
endpoints:
web:
exposure:
# 暴露xxx端点,如需暴露多个,用,分隔;如需暴露所有端点,用'*'
include: auditevents,caches,conditions,flyway,health,heapdump,httptrace,info,integrationgraph,jolokia,logfile,loggers,liquibase,metrics,mappings,prometheus,scheduledtasks,sessions,shutdown,threaddump,hystrix.stream
# # 不暴露哪些端点
exclude: env,beans,configprops
endpoint:
health:
# 是否展示健康检查详情
show-details: always
health:
redis:
enabled: false
#eureka注册
eureka:
client:
service-url:
# 指定eureka server通信地址,注意/eureka/小尾巴不能少
#defaultZone: http://admin:admin@peer1:8761/eureka/,http://admin:admin@peer2:8762/eureka/
# defaultZone: http://admin:admin@81.70.54.64:7010/eureka/
defaultZone: http://admin:admin@121.36.3.207:7010/eureka/
instance:
# 是否注册IP到eureka server,如不指定或设为false,那就回注册主机名到eureka server
prefer-ip-address: true
metadata-map:
management:
context-path: ${server.servlet.context-path:}/actuator
home-page-url-path: ${server.servlet.context-path:}/
status-page-url-path: ${server.servlet.context-path:}/actuator/info
health-check-url-path: ${server.servlet.context-path:}/actuator/health
feign:
client:
config:
default:
connectTime: 5000
readTimeout: 5000
# NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
# BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
# HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
# FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据
loggerLevel: basic
hystrix:
enabled: true
hystrix:
threadpool:
default:
coreSize: 200 #并发执行的最大线程数,默认10
maxQueueSize: 1000 #BlockingQueue的最大队列数,默认值-1
queueSizeRejectionThreshold: 800 #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5
command:
default:
execution:
timeout:
enabled: true
isolation:
strategy: SEMAPHORE
thread:
timeoutInMilliseconds: 60000
#ribbon的超时时间
ribbon:
ReadTimeout: 60000
ConnectTimeout: 60000
# sleuth
logging:
level:
root: info
org.springframework.cloud.sleuth: DEBUG
spring:
# zipkin:
# base-url: http://140.143.228.3:9411
# sleuth:
# sampler:
# # 采样率,模式0.1,也就是10%,为了便于观察效果,改为1.0,也就是100%。生产环境建议保持默认。
# probability: 0.1
cloud:
inetutils:
ignored-interfaces: ['VMware.*']

24
ptos_tall/src/main/java/com/ccsens/ptos_tall/service/UserService.java

@ -241,23 +241,35 @@ public class UserService implements IUserService {
UserVo.UserSign userSignVo;
//获取微信信息并保存
log.info("公众号登陆,{}", code);
WxOauth2UserInfo wxOauth2UserInfo = WxGzhUtil.getOauth2UserInfo(identifyType, code);
WxOauth2UserInfo wxOauth2UserInfo = WxGzhUtil.getOauth2UserInfo(identifyType, code, null, null);
log.info("获取用户的微信信息,{}", wxOauth2UserInfo);
SysAuth theAuth;
if (ObjectUtil.isNull(wxOauth2UserInfo)) {
throw new BaseException(CodeEnum.NOT_SELECT_WX);
}
if(wxOauth2UserInfo.getUnionId() == null){
wxOauth2UserInfo.setUnionId("");
}
SysAuthExample authExample = new SysAuthExample();
authExample.createCriteria().andIdentifyTypeEqualTo((byte) WebConstant.IDENTIFY_TYPE.OAUTH2_Wx.value)
.andIdentifierEqualTo(wxOauth2UserInfo.getOpenId()).andCredentialEqualTo(wxOauth2UserInfo.getUnionId());
if(wxOauth2UserInfo.getUnionId() == null){
authExample.createCriteria().andIdentifyTypeEqualTo((byte) WebConstant.IDENTIFY_TYPE.OAUTH2_Wx.value)
.andIdentifierEqualTo(wxOauth2UserInfo.getOpenId());
}else {
authExample.createCriteria().andIdentifyTypeEqualTo((byte) WebConstant.IDENTIFY_TYPE.OAUTH2_Wx.value)
.andIdentifierEqualTo(wxOauth2UserInfo.getOpenId()).andCredentialEqualTo(wxOauth2UserInfo.getUnionId());
}
List<SysAuth> authList = sysAuthDao.selectByExample(authExample);
if (CollectionUtil.isNotEmpty(authList)) {
theAuth = authList.get(0);
log.info("该用户已有微信登录的auth信息,{}", theAuth);
} else {
SysAuthExample sysAuthExample = new SysAuthExample();
sysAuthExample.createCriteria().andCredentialEqualTo(wxOauth2UserInfo.getUnionId());
List<SysAuth> sysAuthList = sysAuthDao.selectByExample(sysAuthExample);
List<SysAuth> sysAuthList = null;
if(wxOauth2UserInfo.getUnionId() != null){
SysAuthExample sysAuthExample = new SysAuthExample();
sysAuthExample.createCriteria().andCredentialEqualTo(wxOauth2UserInfo.getUnionId());
sysAuthList = sysAuthDao.selectByExample(sysAuthExample);
}
if (CollectionUtil.isNotEmpty(sysAuthList)) {
//添加认证方式
theAuth = new SysAuth();

16
ptos_tall/src/main/java/com/ccsens/ptos_tall/service/WxUserService.java

@ -125,14 +125,16 @@ public class WxUserService implements IWxUserService {
tokenBean.setAvatarUrl(userSign.getAvatarUrl());
log.info("生成token成功");
//根据eventKey更新redis内的用户关注信息
String eventKey = notice.getEventKey();
if (notice.getEventKey().startsWith(PtOsConstant.EVENT_KEY_PREFIX)) {
eventKey = eventKey.substring(PtOsConstant.EVENT_KEY_PREFIX.length());
if(StrUtil.isNotEmpty(notice.getEventKey())){
String eventKey = notice.getEventKey();
if (notice.getEventKey().startsWith(PtOsConstant.EVENT_KEY_PREFIX)) {
eventKey = eventKey.substring(PtOsConstant.EVENT_KEY_PREFIX.length());
}
String key = PtOsConstant.OFFICIAL_EVENT_KEY + eventKey;
long seconds = 60L * 100L;
redisUtil.set(key,tokenBean,seconds);
log.info("存reids--key:{}",key);
}
String key = PtOsConstant.OFFICIAL_EVENT_KEY + eventKey;
long seconds = 60L * 100L;
redisUtil.set(key,tokenBean,seconds);
log.info("存reids--key:{}",key);
break;
case "unsubscribe":
//用openId查找用户

2
util/src/main/java/com/ccsens/util/WebConstant.java

@ -361,7 +361,7 @@ public class WebConstant {
public enum IDENTIFY_TYPE {
Wxmp(0,"微信小程序"), Phone(1,"电话")
, Email(2,"Email"), Account(3,"账号")
,OAUTH2_Wx(4,"微信公众号网页授权登录"),Wx_H5(5,"网页微信登陆")
,OAUTH2_Wx(8,"微信公众号网页授权登录"),Wx_H5(5,"网页微信登陆")
,OAUTH2_WeiBo(6,"微博"),WxEnterprise(7, "企业微信"),
OFFICIAL_ACCOUNT_WX(9,"微信公众号登录"),Wx_H5_TEST(10,"网页微信登陆测试");
// 钉钉是11

20
util/src/main/java/com/ccsens/util/bean/wx/po/WxApiTicket.java

@ -0,0 +1,20 @@
package com.ccsens.util.bean.wx.po;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @author
*/
@Data
public class WxApiTicket {
@JsonProperty("errcode")
private Integer errcode;
@JsonProperty("errmsg")
private String errmsg;
@JsonProperty("ticket")
private String ticket;
@JsonProperty("expires_in")
private Long expiresIn;
}

21
util/src/main/java/com/ccsens/util/wx/WxGzhUtil.java

@ -1,5 +1,6 @@
package com.ccsens.util.wx;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSONException;
@ -77,8 +78,12 @@ public class WxGzhUtil {
= "https://api.weixin.qq.com/sns/userinfo?access_token=%1$s&openid=%2$s";
public static final String MESSAGE_TEMPLATE_SEND = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%1$s";
private static final String APPID = "wx7af1bf1e14facf82";
private static final String SECRET = "a6613fae11b497639c0224b820aaf6d9";
// private static final String APPID = "wx7af1bf1e14facf82";
// private static final String SECRET = "a6613fae11b497639c0224b820aaf6d9";
// TODO ;临时更换默认appId
private static final String APPID = "wxcb60fcfeaddeb3e3";
private static final String SECRET = "e6d27d1b2a0a89b16ec61fdb5e628034";
private static final String APPID_H5_TEST = "wxd1842e073e0e6d91";
private static final String SECRET_H5_TEST = "96d69b79039caf92a2abafa999880cad";
@ -295,9 +300,9 @@ public class WxGzhUtil {
* @param code OAuth2授权码
* @return WxOauth2AccessToken
*/
public static WxOauth2AccessToken getOauth2AccessToken(WebConstant.IDENTIFY_TYPE identifyType,String code) throws BaseException {
public static WxOauth2AccessToken getOauth2AccessToken(WebConstant.IDENTIFY_TYPE identifyType,String code,String appId, String secret) throws BaseException {
WxOauth2AccessToken wxOauth2AccessToken = null;
String url = String.format(URL_GET_OAUTH2_ACCESS_TOKEN,appId(identifyType),secret(identifyType),code);
String url = String.format(URL_GET_OAUTH2_ACCESS_TOKEN,appId,secret,code);
String response = HttpRequest.get(url).execute().body();
log.info("url: {}\nresponse: {}",url,response);
try {
@ -355,11 +360,15 @@ public class WxGzhUtil {
* @return WxOauth2UserInfo
* @throws BaseException 异常
*/
public static WxOauth2UserInfo getOauth2UserInfo(WebConstant.IDENTIFY_TYPE identifyType,String code) throws BaseException {
public static WxOauth2UserInfo getOauth2UserInfo(WebConstant.IDENTIFY_TYPE identifyType,String code,String appId, String secret) throws BaseException {
if (StrUtil.isNotBlank(PropUtil.wxH5) && "0".equals(PropUtil.wxH5)){
identifyType = WebConstant.IDENTIFY_TYPE.Wx_H5_TEST;
}
WxOauth2AccessToken wxOauth2AccessToken = getOauth2AccessToken(identifyType,code);
if(ObjectUtil.isNull(appId) || ObjectUtil.isNull(secret)){
appId = appId(identifyType);
secret = secret(identifyType);
}
WxOauth2AccessToken wxOauth2AccessToken = getOauth2AccessToken(identifyType,code,appId,secret);
return getOauth2UserInfo(wxOauth2AccessToken.getAccessToken(),wxOauth2AccessToken.getOpenId());
}

120
wechatutil/src/main/java/com/ccsens/wechatutil/bean/vo/WxNativePay.java

@ -0,0 +1,120 @@
package com.ccsens.wechatutil.bean.vo;
import lombok.Data;
import java.util.List;
/**
* @author
*/
@Data
public class WxNativePay {
/**
* 应用ID
*/
private String appid;
/**
* 直连商户号
*/
private String mchid;
/**
* 商品描述
*/
private String description;
/**
* 商户订单号 商户系统内部订单号只能是数字大小写字母_-*且在同一个商户号下唯一
*/
private String out_trade_no;
/**
* 交易结束时间
*/
private String time_expire;
/**
* 附加数据
*/
private String attach;
/**
* 通知地址
*/
private String notify_url;
/**
* 订单优惠标记
*/
private String goods_tag;
/**
* 订单金额
*/
private Amount amount;
/**
* 优惠功能
*/
private Detail detail;
/**
* 场景信息
*/
private SceneInfo scene_info;
/**
* 结算信息
*/
private SceneInfo settle_info;
@Data
public static class Amount{
//总金额 是 单位为分。
private int total;
//货币类型
private String currency;
}
@Data
public static class Detail{
//订单原价 单位为分。
private int cost_price;
//商品小票ID
private String invoice_id;
//单品列表
private List<GoodsDetail> goods_detail;
}
@Data
public static class GoodsDetail{
//商户侧商品编码 是
private String merchant_goods_id;
//微信支付商品编码 否
private String wechatpay_goods_id;
//商品名称
private String goods_name;
//商品数量 是
private int quantity;
//商品单价 是
private int unit_price;
}
@Data
public static class SceneInfo{
//用户终端IP
private String payer_client_ip;
//商户端设备号
private String device_id;
//商户门店信息
private StoreInfo store_info;
}
@Data
public static class StoreInfo{
//门店编号 是
private String id;
//门店名称
private String name;
//地区编码
private String area_code;
//详细地址
private String address;
}
@Data
public static class SettleInfo{
//是否指定分账
private boolean profit_sharing;
}
}

47
wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativePay.java

@ -0,0 +1,47 @@
package com.ccsens.wechatutil.payutil.wxnative;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ccsens.util.QrCodeUtil;
import com.ccsens.wechatutil.bean.vo.WxNativePay;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
/**
* @author
*/
@Slf4j
public class NativePay {
/**
* 生成订单返回收款二维码
* @param serialNo 证书序列号
* @param pathPem 秘钥文件地址
* @param wxNativePay 订单信息
* @param filePath 二维码存放地址前缀路径
* @return 返回二维码访问路径不包含前缀
*/
public static String generateOrder( String serialNo, String pathPem, WxNativePay wxNativePay, String filePath){
String path = "";
try {
HttpUrl httpurl = HttpUrl.parse(NativeUtils.NATIVE_URL);
String body = String.valueOf(JSON.parseObject(JSON.toJSONString(wxNativePay)));
String authorization = NativeUtils.schema + " " +
NativeUtils.getToken("POST", httpurl, body, wxNativePay.getOut_trade_no(), wxNativePay.getMchid(), serialNo, pathPem);
//下单调用的接口,JSON格式
String codeUrl = NativeUtils.nativePostBody(NativeUtils.NATIVE_URL, body, authorization);
log.info("调用微信下单返回:{}", codeUrl);
JSONObject jsonObject = JSONObject.parseObject(codeUrl);
String url = jsonObject.getString("code_url");
if(StrUtil.isNotBlank(url)){
path = QrCodeUtil.getQrCodeWithUtf8(url,filePath);
}else {
path = String.valueOf(jsonObject);
}
} catch (Exception e) {
e.printStackTrace();
}
return path;
}
}

265
wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativeUtils.java

@ -0,0 +1,265 @@
package com.ccsens.wechatutil.payutil.wxnative;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import okhttp3.HttpUrl;
import org.apache.commons.lang3.RandomStringUtils;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author
*/
public class NativeUtils {
static String schema = "WECHATPAY2-SHA256-RSA2048";
// /**
// * 填写你的商户号
// */
// static String MCH_ID = "1624859575";
// /**
// * 填写证书序列号
// */
// static String SERIAL_NO = "71433832548290D95806FAF490878BB9B2677D4E";
// /**
// * 填写应用APP ID
// */
// static String APP_ID = "wxcb60fcfeaddeb3e3";
//
// /**
// * apiclient_key.pem文件地址 如C:\Users\Administrator\Desktop\wechat\apiclient_key.pem
// */
// static String PATH_PEM = "C:\\Users\\dou\\Desktop\\wxpay\\apiclient_key.pem";
/**
* 请求地址
*/
static String NATIVE_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/native";
//
// /**
// * 填写你的返回地址 需要HTTPS返回地址
// */
// static String RETURN_ADDRESS = "https://test.tall.wiki/gateway/defaultwbs/debug";
// public static void main(String[] args) {
// try {
// int money = 100;
// String description = "测试支付";
// HttpUrl httpurl = HttpUrl.parse(NATIVE_URL);
// //签名表头信息 Authorization
// String orderId = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
// String body = OrderData(orderId, money, description);
// String authorization = schema + " " + getToken("POST", httpurl, body, orderId);
// //下单调用的接口,JSON格式
// String codeUrl = nativePostBody(NATIVE_URL, body, authorization);
// System.out.println(codeUrl);
//
// JSONObject jsonObject = JSONObject.parseObject(codeUrl);
// String code_url = jsonObject.getString("code_url");
// if(StrUtil.isNotBlank(code_url)){
// String s = generateQRCode(code_url);
// System.out.println(s);
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// static String OrderData(String orderId, int money, String description) {
// // 应用ID appid
// // 直连商户号 mchid
// // 商品描述 description
// //商户订单号 out_trade_no
// //通知地址 notify_url
// //订单金额 amount对象: 总金额 total 货币类型 currency
// JSONObject jsonObject = new JSONObject();
// jsonObject.put("mchid", MCH_ID);
// jsonObject.put("out_trade_no", orderId);
// jsonObject.put("appid", APP_ID);
// jsonObject.put("description", description);
// jsonObject.put("notify_url", RETURN_ADDRESS);
// Map<String, Object> map = new HashMap<>();
// map.put("total", money);
// map.put("currency", "CNY");
// jsonObject.put("amount", map);
// return String.valueOf(jsonObject);
//
// }
/**
* 获取签名信息所有值
*
* @param method 请求方法
* @param url URL地址
* @param body BOdy参数
* @return
*/
static String getToken(String method, HttpUrl url, String body, String orderId, String mchId, String serialNo, String pathPem) throws Exception {
//获得系统时间,把毫秒换算成秒 /1000
long timestamp = System.currentTimeMillis() / 1000;
String message = buildMessage(method, url, timestamp, orderId, body);
String signature = sign(message.getBytes("utf-8"), pathPem);
return "mchid=\"" + mchId + "\","
+ "nonce_str=\"" + orderId + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + serialNo + "\","
+ "signature=\"" + signature + "\"";
}
/**
* 拼接明文数值
*
* @param method 请求方法 GET or POST
* @param url 网络请求方法地址 取除域名项
* @param timestamp 时间戳
* @param nonceStr 随机数
* @param body GET请求不需要Body参数POST需要Body
* @return
*/
static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
String canonicalUrl = url.encodedPath();
//get请求自动做了校验,会把空字符串进行识别,注意是空字符串!!!!!
if (url.encodedQuery() != null) {
canonicalUrl += "?" + url.encodedQuery();
}
//官方的方法自动做了换行的所有动作,注意唤起支付的参数不一样需要更换(这里是统一下单所以直接照搬即可)
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
/**
* 签名加密
*
* @param message
* @return
* @throws NoSuchAlgorithmException
* @throws SignatureException
* @throws IOException
* @throws InvalidKeyException
*/
static String sign(byte[] message, String pathPem) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
//加密方式
Signature sign = Signature.getInstance("SHA256withRSA");
//私钥,通过getPrivateKey来获取,这是个方法可以接调用 ,需要的是_key.pem文件的绝对路径配上文件名
sign.initSign(getPrivateKey(pathPem));
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
/**
* 获取私钥
*
* @param filename 私钥文件路径 (required)
* @return 私钥对象
* <p>
* 完全不需要修改注意此方法也是去掉了头部和尾部注意文件路径名
*/
public static PrivateKey getPrivateKey(String filename) throws IOException {
String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
try {
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
/**
* Post请求带Body参数
*
* @param actionUrl
* @param params
* @param requestString
* @return
* @throws IOException
*/
public static String nativePostBody(String actionUrl, String params, String requestString)
throws IOException {
String serverURL = actionUrl;
StringBuffer sbf = new StringBuffer();
String strRead = null;
URL url = new URL(serverURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//请求post方式
connection.setRequestMethod("POST");
connection.setDoInput(true);
connection.setDoOutput(true);
//header内的的参数在这里set
connection.setRequestProperty("Content-Type", "application/json");
//Native支付需要的参数表头参数
connection.setRequestProperty("Authorization", requestString);
connection.connect();
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
//body参数放这里
writer.write(params);
writer.flush();
InputStream is = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
while ((strRead = reader.readLine()) != null) {
sbf.append(strRead);
sbf.append("\r\n");
}
reader.close();
connection.disconnect();
String results = sbf.toString();
return results;
}
public static String generateQRCode(String code_url) throws WriterException, IOException {
//生成二维码
//设置二维码的尺寸
int width = 200;
int hight = 200;
//创建map
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET,"UTF-8");
//创建矩阵对象 调用谷歌提供的
BitMatrix bitMatrix = new MultiFormatWriter().encode(code_url, BarcodeFormat.QR_CODE, width, hight, hints);
//创建二维码生成路径
String filePath = "C:\\Users\\dou\\Desktop\\wxpay\\";
String fileName = RandomStringUtils.randomAlphanumeric(10)+ ".jpg";
Path path = FileSystems.getDefault().getPath(filePath,fileName);
//将创建的矩阵转换成图片
MatrixToImageWriter.writeToPath(bitMatrix,"jpg",path);
return filePath + fileName;
}
}

1
wechatutil/src/main/java/com/ccsens/wechatutil/util/WxCodeError.java

@ -10,6 +10,7 @@ public class WxCodeError extends CodeError {
public static final Code WX_HTTP_ERROR = new Code(1,"微信接口请求失败", true);
public static final Code ACCESS_TOKEN_ERROR = new Code(2,"accessToken获取失败", true);
public static final Code OPENID_ERROR = new Code(2,"openId获取失败", true);
public static final Code API_TICKET_ERROR = new Code(3,"API_TICKET获取失败", true);
}

2
wechatutil/src/main/java/com/ccsens/wechatutil/util/WxConstant.java

@ -26,6 +26,8 @@ public class WxConstant {
public static final String QR_CODE_CREATE = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={}";
/**根据openId获取用户信息*/
public static final String USER_INFO = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=%1$s&openid=%2$s&lang=zh_CN";
/*** 获取临时票据api_ticket */
public static final String URL_GET_API_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%1$s&type=jsapi";
/*** 小程序登录路径 */

36
wechatutil/src/main/java/com/ccsens/wechatutil/wxcommon/WxCommonUtil.java

@ -6,6 +6,7 @@ import com.ccsens.util.JacksonUtil;
import com.ccsens.util.RedisUtil;
import com.ccsens.util.WebConstant;
import com.ccsens.util.bean.wx.po.WxAccessToken;
import com.ccsens.util.bean.wx.po.WxApiTicket;
import com.ccsens.util.exception.BaseException;
import com.ccsens.wechatutil.util.WxCodeError;
import com.ccsens.wechatutil.util.WxConstant;
@ -65,4 +66,39 @@ public class WxCommonUtil {
log.info("读取reids的token:{}", obj);
return (String) obj;
}
/**
* 通过accessToken获取临时票据ticket
*/
public static String getTicketByAccessToken(String accessToken) throws BaseException {
log.info("获取ticket");
// Object obj = util.redisUtil.get(WxConstant.ACCESS_TOKEN + appId);
// if (obj == null || StrUtil.isBlank((String) obj)) {
WxApiTicket wxApiTicket;
String url = String.format(WxConstant.URL_GET_API_TICKET, accessToken);
String response = HttpRequest.get(url).execute().body();
log.info("getApiTicke: {}", response);
try {
if (StrUtil.isEmpty(response) || null == (wxApiTicket = JacksonUtil.jsonToBean(response, WxApiTicket.class))) {
throw new BaseException(WxCodeError.WX_HTTP_ERROR);
}
} catch (IOException e) {
throw new BaseException(e.getMessage());
}
if (null != wxApiTicket.getErrcode() && wxApiTicket.getErrcode() != 0) {
throw new BaseException(wxApiTicket.getErrcode(), wxApiTicket.getErrmsg());
}
if (StrUtil.isEmpty(wxApiTicket.getTicket())) {
throw new BaseException(WxCodeError.API_TICKET_ERROR);
}
// util.redisUtil.set(WebConstant.Wx.ACCESS_TOKEN + appId, wxAccessToken.getAccessToken(), WebConstant.Wx.EXPIRE_TIME);
// log.info("存储access_token:{}", wxAccessToken.getAccessToken());
// return wxAccessToken.getAccessToken();
// }
// log.info("读取reids的token:{}", obj);
return wxApiTicket.getTicket();
}
}

2
wechatutil/src/main/java/com/ccsens/wechatutil/wxofficial/OfficialAccountMessageUtil.java

@ -116,7 +116,7 @@ public class OfficialAccountMessageUtil {
} catch (IOException e) {
throw new BaseException(e.getMessage());
}
if (null != wxBaseEntity.getErrcode()) {
if (null != wxBaseEntity.getErrcode() && wxBaseEntity.getErrcode() != 0) {
throw new BaseException(wxBaseEntity.getErrcode(), wxBaseEntity.getErrmsg());
}
}

Loading…
Cancel
Save