一. 微信小程序授权登录
引入依赖
<!--微信小程序-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.3.0</version>
</dependency>
授权登录代码
- 先判断Redis中缓存是否命中, 如果命中则直接返回缓存信息
- 如果未命中缓存, 则使用临时校验码code去微信换取openid
- 查询数据库该openid对应用户信息是否存在, 存在则直接返回, 不存在则注册用户信息
- 最后将用户信息存入redis, 并返回相应的token供下次授权登录前判断是否缓存信息
@ApiOperation("微信授权登录")
@PostMapping("/customer_login")
public R<SysWxUser> customerLogin(@RequestBody WXAuth wxAuth) {
return weixinService.customerLogin(wxAuth);
}
R<SysWxUser> customerLogin(WXAuth wxAuth);
/**
* 授权登录
*
* @param wxAuth 授权信息
* @return 结果
*/
@Override
public R<SysWxUser> customerLogin(WXAuth wxAuth) {
String code = wxAuth.getCode();
String iv = wxAuth.getIv();
String token = wxAuth.getToken();
String encryptedData = wxAuth.getEncryptedData();
if(StrUtil.isBlank(code) && StrUtil.isBlank(token)){
return R.fail(MessageConstants.PARAMS_ERROR);
}
// 判断登录态是否在有效期
if(StrUtil.isNotBlank(token)){
// token不为空 判断token是否过期
String content = redisTemplate.opsForValue().get(RedisKey.WX_SESSION_KEY + token);
if(content == null){
return R.fail(MessageConstants.TOKEN_NOT_EXIST);
}
// 查询token对应用户信息
SysWxUser info = JSONUtil.toBean(content, SysWxUser.class);
// 刷新token有效时间
redisTemplate.opsForValue().set(RedisKey.WX_SESSION_KEY + token, content, 3600, TimeUnit.SECONDS);
return R.ok(info);
}
SysWxUser wxUser = null;
WxMaJscode2SessionResult sessionInfo = null;
try {
sessionInfo = wxMaService.getUserService().getSessionInfo(code);
WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(sessionInfo.getSessionKey(), encryptedData, iv);
String openid = sessionInfo.getOpenid();
// 使用微信openId查询是否有此用户(WxUser)
wxUser = lambdaQuery().eq(SysWxUser::getOpenid, openid).one();
// 不存在 构建用户信息进行注册
if(wxUser == null) {
wxUser = new SysWxUser();
wxUser.setOpenid(openid);
wxUser.setWxName(Constants.WX_PREFIX + RandomUtil.randomString(6));
wxUser.setAvatarUrl(userInfo.getAvatarUrl());
int insert = wxUserMapper.insert(wxUser);
if (insert == 0) {
return R.fail(MessageConstants.USER_BIND_ERROR);
}
}
} catch (Exception e) {
e.printStackTrace();
log.error(MessageConstants.SYSTEM_ERROR);
}
String sessionKey = sessionInfo.getSessionKey();
String cacheKey = RedisKey.WX_SESSION_KEY + sessionKey;
// 将 openid / sessionKey 存入redis
redisTemplate.opsForValue().set(cacheKey, JSONUtil.toJsonStr(wxUser), 3600, TimeUnit.SECONDS);
return R.ok(wxUser,Constants.TOKEN_PRE + sessionKey);
}
部分实体类
/**
* 响应信息主体
*
* @author tong
*/
public class R<T> implements Serializable
{
private static final long serialVersionUID = 1L;
/** 成功 */
public static final int SUCCESS = HttpStatus.SUCCESS;
/** 失败 */
public static final int FAIL = HttpStatus.ERROR;
private int code;
private String msg;
private T data;
public static <T> R<T> ok()
{
return restResult(null, SUCCESS, "操作成功");
}
public static <T> R<T> ok(T data)
{
return restResult(data, SUCCESS, "操作成功");
}
public static <T> R<T> ok(T data, String msg)
{
return restResult(data, SUCCESS, msg);
}
public static <T> R<T> fail()
{
return restResult(null, FAIL, "操作失败");
}
public static <T> R<T> fail(String msg)
{
return restResult(null, FAIL, msg);
}
public static <T> R<T> fail(T data)
{
return restResult(data, FAIL, "操作失败");
}
public static <T> R<T> fail(T data, String msg)
{
return restResult(data, FAIL, msg);
}
public static <T> R<T> fail(int code, String msg)
{
return restResult(null, code, msg);
}
private static <T> R<T> restResult(T data, int code, String msg)
{
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
public int getCode()
{
return code;
}
public void setCode(int code)
{
this.code = code;
}
public String getMsg()
{
return msg;
}
public void setMsg(String msg)
{
this.msg = msg;
}
public T getData()
{
return data;
}
public void setData(T data)
{
this.data = data;
}
public static <T> Boolean isError(R<T> ret)
{
return !isSuccess(ret);
}
public static <T> Boolean isSuccess(R<T> ret)
{
return R.SUCCESS == ret.getCode();
}
}
/**
* 用户微信信息表
* @TableName sys_wx_user
*/
@TableName(value ="sys_wx_user")
@Data
public class SysWxUser implements Serializable {
/**
* 用户id
*/
@TableId
private Long id;
/**
* 用户姓名
*/
private String name;
/**
* 用户绑定微信的唯一标识
*/
private String openid;
/**
* 联系电话
*/
private String phone;
/**
* 微信名称
*/
private String wxName;
/**
* 微信头像在线地址
*/
private String avatarUrl;
/**
* 删除标志
*/
private String delFlag;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
/**
* @Author: Ccoo
* @Date: 2024-03-21 16:42
* version 1.0
*/
@Data
@Accessors(chain = true)
public class WXAuth {
@ApiModelProperty("临时授权码")
private String code;
@ApiModelProperty("加密数据")
private String encryptedData;
@ApiModelProperty("加密算法的初始向量")
private String iv;
@ApiModelProperty("token")
private String token;
}
二. 微信支付
引入依赖
<!--微信支付-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.11</version>
</dependency>
编写配置文件
具体的参数配置需要去微信支付平台申请
wx:
# 微信小程序appid
app-id: wxcb25xxxxxxxxxxxx
# 商户号
mch-id: 16xxxxxxxx
# 证书序列号
mch-serial-no: 59BAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# 小程序密钥
app-secret: 8ccxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# api密钥
api-key: tonxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# 回调接口地址
notify-url: https://15x.xx.xxx.xx/pay/payCallBack
# 证书地址
key-path: /apiclient_key.pem
cert-path: /apiclient_cert.pem
cert-p12-path: /apiclient_cert.p12
msgDataFormat: JSON
编写相关配置
/**
* @Author:Ccoo
* @Date:2024/3/21 20:40
*/
@Component
@ConfigurationProperties(prefix = "wx")
@Data
@ToString
public class WxProperties {
/**
* 设置微信小程序的appid
*/
private String appId;
/**
* 设置微信小程序的Secret
*/
private String appSecret;
/**
* 微信支付分配的商户号
*/
private String mchId;
/**
* 密钥文件的路径
*/
private String keyPath;
/**
* 商户证书的路径
*/
private String certPath;
/**
* 商户密钥,用于签名和验证
*/
private String apiKey;
/**
* 接收微信支付异步通知的URL
*/
private String notifyUrl;
/**
* 商户API证书序列号
*/
private String mchSerialNo;
/**
* 消息格式,XML或者JSON
*/
private String msgDataFormat;
}
/**
* 微信支付相关自动配置
* @author Ccoo
* 2024/3/24
*/
@Slf4j
@Configuration
public class WechatPayAutoConfiguration {
@Autowired
private WxProperties wxProperties;
@Autowired
private ResourceLoader resourceLoader;
private static final String CLASS_PATH = "classpath:";
/**
* 自动更新证书
*
* @return RSAAutoCertificateConfig
*/
@Bean
public Config config() throws IOException {
String path = CLASS_PATH + wxProperties.getCertPath();
Resource resourceCert = resourceLoader.getResource(path);
X509Certificate certificate = getCertificate(resourceCert.getInputStream());
String merchantSerialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
String privatePath = CLASS_PATH + wxProperties.getKeyPath();
Resource resourcePrivate = resourceLoader.getResource(privatePath);
String privateKey = inputStreamToString(resourcePrivate.getInputStream());
return new RSAAutoCertificateConfig.Builder()
.merchantId(wxProperties.getMchId())
.privateKey(privateKey)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(wxProperties.getApiKey())
.build();
}
/**
* 微信支付对象
* @param config Config
* @return JsapiServiceExtension
*/
@Bean
public JsapiServiceExtension jsapiServiceExtension(Config config){
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
return service;
}
/**
* 微信回调对象
*
* @param config Config
* @return NotificationParser
*/
@Bean
public NotificationParser notificationParser(Config config) {
NotificationParser parser = new NotificationParser((NotificationConfig) config);
return parser;
}
/**
* 读取私钥文件,将文件流读取成string
*
* @param inputStream
* @return
* @throws IOException
*/
public String inputStreamToString(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
reader.close();
return stringBuilder.toString();
}
/**
* 获取证书 将文件流转成证书文件
*
* @param inputStream 证书文件
* @return {@link X509Certificate} 获取证书
*/
public static X509Certificate getCertificate(InputStream inputStream) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
cert.checkValidity();
return cert;
} catch (CertificateExpiredException e) {
throw new RuntimeException("证书已过期", e);
} catch (CertificateNotYetValidException e) {
throw new RuntimeException("证书尚未生效", e);
} catch (CertificateException e) {
throw new RuntimeException("无效的证书", e);
}
}
}
微信支付相关代码
@Api(tags = {"支付管理接口"})
@Slf4j
@RestController
@RequestMapping("/pay")
@RequiredArgsConstructor
public class Paycontroller {
private final Payservice payservice;
private final WechatPayExternalService wechatPayService;
@ApiOperation("微信预支付")
@PostMapping("/createPreOrder")
public R<?> createPreOrder(@RequestBody CreateOrderPayRequest createOrderPay) {
return R.ok(wechatPayService.prepayWithRequestPayment(createOrderPay));
}
@ApiOperation("查询订单")
@GetMapping("/queryOrder")
public R<?> queryOrder(@RequestParam String outTradeNo) {
return R.ok(wechatPayService.queryStatus(outTradeNo));
}
@ApiOperation("取消订单")
@PostMapping("/closeOrder")
public void closeOrder(@RequestParam String outTradeNo) {
//方法没有返回值,意味着成功时API返回204 No Content
wechatPayService.closeOrder(outTradeNo);
}
}
/**
* 提交预支付请求付款
*
* @param createOrderPay 订单请求体
* @return PrepayWithRequestPaymentResponse 预付费与请求付款响应
*/
PayResponse prepayWithRequestPayment(CreateOrderPayRequest createOrderPay);
/**
* 查询状态
*
* @param outTradeNo 商户支付no
* @return 状态信息
*/
Transaction queryStatus(String outTradeNo);
/**
* 取消订单
*
* @param outTradeNo
*/
void closeOrder(String outTradeNo);
/**
* @author Ccoo
* 2024/3/24
*/
@Slf4j
@Service
public class WechatPayExternalServiceImpl implements WechatPayExternalService {
@Resource
private WxProperties wxProperties;
@Resource
private JsapiServiceExtension jsapiServiceExtension;
@Override
public PayResponse prepayWithRequestPayment(CreateOrderPayRequest createOrderPay) {
PrepayRequest request = new PrepayRequest();
String orderId = createOrderPay.getOutTradeNo();
// 设置支付金额
Amount amount = new Amount();
BigDecimal payMoney = createOrderPay.getPayMoney();
BigDecimal amountTotal = payMoney.multiply(new BigDecimal("100").setScale(0, RoundingMode.DOWN));
amount.setTotal(amountTotal.intValue());
request.setAmount(amount);
// 设置支付者
Payer payer = new Payer();
payer.setOpenid(createOrderPay.getOpenId());
request.setPayer(payer);
// 设置支付超时时间 10分钟
request.setTimeExpire(getExpiredTimeStr());
// 设置商户信息
request.setAppid(wxProperties.getAppId());
request.setMchid(wxProperties.getMchId());
// 设置附加数据
request.setAttach(String.valueOf(createOrderPay.getId()));
// 设置商品描述
request.setDescription(createOrderPay.getPayContent());
// 设置通知地址
request.setNotifyUrl(wxProperties.getNotifyUrl());
// 这里生成流水号,后续用这个流水号与微信交互,查询订单状态
request.setOutTradeNo(orderId);
PrepayWithRequestPaymentResponse result;
try {
result = jsapiServiceExtension.prepayWithRequestPayment(request);
} catch (HttpException e) {
log.error("微信下单发送HTTP请求失败,错误信息:{}", e.getHttpRequest());
throw new RuntimeException("微信下单发送HTTP请求失败", e);
} catch (ServiceException e) {
// 服务返回状态小于200或大于等于300,例如500
log.error("微信下单服务状态错误,错误信息:{}", e.getErrorMessage());
throw new RuntimeException("微信下单服务状态错误", e);
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
log.error("服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}", e.getMessage());
throw new RuntimeException("服务返回成功,返回体类型不合法,或者解析返回体失败", e);
}
return BeanUtil.copyProperties(result, PayResponse.class);
}
/**
* 查询订单状态
* @param outTradeNo 商户支付no
* @return 状态信息
*/
@Override
public Transaction queryStatus(String outTradeNo) {
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
// 设置商户信息
request.setMchid(wxProperties.getMchId());
// 设置订单号
request.setOutTradeNo(outTradeNo);
try {
return jsapiServiceExtension.queryOrderByOutTradeNo(request);
} catch (ServiceException e) {
log.error("订单查询失败,返回码:{},返回信息:{}", e.getErrorCode(), e.getErrorMessage());
throw new RuntimeException("订单查询失败", e);
}
}
/**
* 关闭订单
* @param outTradeNo 订单号
*/
@Override
public void closeOrder(String outTradeNo) {
CloseOrderRequest closeRequest = new CloseOrderRequest();
// 设置商户信息
closeRequest.setMchid(wxProperties.getMchId());
// 设置订单号
closeRequest.setOutTradeNo(outTradeNo);
try {
//方法没有返回值,意味着成功时API返回204 No Content
jsapiServiceExtension.closeOrder(closeRequest);
} catch (ServiceException e) {
log.error("订单关闭失败,返回码:{},返回信息:{}", e.getErrorCode(), e.getErrorMessage());
throw new RuntimeException("订单关闭失败", e);
}
}
}
部分实体类
/**
* @author Ccoo
* 2024/3/24
*/
@Data
public class CreateOrderPayRequest {
/**
* 主键id
*/
private Long id;
/**
* 商户支付no 和微信交互 查询订单使用(outTradeNo)
*/
private String outTradeNo;
/**
* 用户openid
*/
private String openId;
/**
* 支付金额
*/
private BigDecimal payMoney;
/**
* 支付内容
*/
private String payContent;
}