From c062952ae63c5b4a7e66642df4ae420256315c9b Mon Sep 17 00:00:00 2001 From: songmingsong <2929511417@qq.com> Date: Fri, 6 Dec 2024 11:14:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=A8=A1=E6=9D=BF=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E9=80=9A=E7=9F=A5=E3=80=81=E5=BE=AE=E4=BF=A1=E8=8E=B7?= =?UTF-8?q?=E5=8F=96AccessToken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ycwl/basic/constant/WeiXinConstant.java | 9 +- .../mobile/AppWxNotifyController.java | 38 ++++++ .../controller/mobile/AppWxPayController.java | 6 +- .../com/ycwl/basic/enums/BizCodeEnum.java | 3 +- .../model/{wxPay => wx}/WXPayOrderReqVO.java | 2 +- .../basic/model/wx/WechatAccessTokenVO.java | 24 ++++ .../com/ycwl/basic/model/wx/WechatBaseVO.java | 39 ++++++ .../model/wx/WechatMessageSubscribeForm.java | 44 +++++++ .../com/ycwl/basic/model/wx/WechatMssVO.java | 45 +++++++ .../basic/model/wx/WechatTemplateData.java | 21 +++ .../model/{wxPay => wx}/WxPayRespVO.java | 2 +- .../WxchatCallbackSuccessData.java | 2 +- .../impl/mobile/WxNotifyServiceImpl.java | 123 ++++++++++++++++++ .../service/impl/mobile/WxPayServiceImpl.java | 6 +- .../basic/service/mobile/WxNotifyService.java | 17 +++ .../basic/service/mobile/WxPayService.java | 6 +- 16 files changed, 373 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/controller/mobile/AppWxNotifyController.java rename src/main/java/com/ycwl/basic/model/{wxPay => wx}/WXPayOrderReqVO.java (94%) create mode 100644 src/main/java/com/ycwl/basic/model/wx/WechatAccessTokenVO.java create mode 100644 src/main/java/com/ycwl/basic/model/wx/WechatBaseVO.java create mode 100644 src/main/java/com/ycwl/basic/model/wx/WechatMessageSubscribeForm.java create mode 100644 src/main/java/com/ycwl/basic/model/wx/WechatMssVO.java create mode 100644 src/main/java/com/ycwl/basic/model/wx/WechatTemplateData.java rename src/main/java/com/ycwl/basic/model/{wxPay => wx}/WxPayRespVO.java (96%) rename src/main/java/com/ycwl/basic/model/{wxPay => wx}/WxchatCallbackSuccessData.java (97%) create mode 100644 src/main/java/com/ycwl/basic/service/impl/mobile/WxNotifyServiceImpl.java create mode 100644 src/main/java/com/ycwl/basic/service/mobile/WxNotifyService.java diff --git a/src/main/java/com/ycwl/basic/constant/WeiXinConstant.java b/src/main/java/com/ycwl/basic/constant/WeiXinConstant.java index 3748885..602b007 100644 --- a/src/main/java/com/ycwl/basic/constant/WeiXinConstant.java +++ b/src/main/java/com/ycwl/basic/constant/WeiXinConstant.java @@ -56,7 +56,7 @@ public class WeiXinConstant { /** * 获取ACCESS_TOKEN */ - public static final String ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token"; + public static final String ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; /** * 登录凭证校验 */ @@ -78,6 +78,11 @@ public class WeiXinConstant { */ public static final String GENERATE_URL_LINK = "https://api.weixin.qq.com/wxa/generate_urllink?access_token=%s"; + /** + * 发送模板消息 + */ + public static final String MESSAGE_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token="; + /*-----------------------------*/ /* */ /* 支付相关 */ @@ -108,6 +113,8 @@ public class WeiXinConstant { public static final String REFUNDS_NONCE = "nonce"; public static final String REFUNDS_REFUND_STATUS = "refund_status"; public static final String REFUNDS_ASSOCIATED_DATA = "associated_data"; + public static final String WECHAT_ERRCODE = "errcode"; + public static final String WECHAT_ERRMSG = "errmsg"; /** * 退款的token的SCHEMA */ diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppWxNotifyController.java b/src/main/java/com/ycwl/basic/controller/mobile/AppWxNotifyController.java new file mode 100644 index 0000000..754bcf6 --- /dev/null +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppWxNotifyController.java @@ -0,0 +1,38 @@ +package com.ycwl.basic.controller.mobile; + + +import com.alibaba.fastjson.JSONObject; +import com.ycwl.basic.annotation.IgnoreToken; +import com.ycwl.basic.model.wx.WechatMessageSubscribeForm; +import com.ycwl.basic.service.mobile.WxNotifyService; +import com.ycwl.basic.utils.ApiResponse; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @Author: songmingsong + * @CreateTime: 2024-12-06 + * @Description: 微信消息模板通知 + * @Version: 1.0 + */ +@RestController +@RequestMapping("/api/mobile/wx/notify/v1") +@Api(tags = "微信消息模板通知") +public class AppWxNotifyController { + @Autowired + private WxNotifyService wxNotifyService; + + @ApiOperation(value = "通知", notes = "通知") + @PostMapping("/pushMessage") + @IgnoreToken + public ApiResponse pushMessage(@RequestBody WechatMessageSubscribeForm req) { + JSONObject resJson = wxNotifyService.pushMessage(req); + return ApiResponse.success(resJson); + } + +} diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppWxPayController.java b/src/main/java/com/ycwl/basic/controller/mobile/AppWxPayController.java index 0bd7ad0..72227f5 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppWxPayController.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppWxPayController.java @@ -3,8 +3,8 @@ package com.ycwl.basic.controller.mobile; import com.ycwl.basic.annotation.IgnoreToken; import com.ycwl.basic.enums.BizCodeEnum; -import com.ycwl.basic.model.wxPay.WXPayOrderReqVO; -import com.ycwl.basic.model.wxPay.WxPayRespVO; +import com.ycwl.basic.model.wx.WXPayOrderReqVO; +import com.ycwl.basic.model.wx.WxPayRespVO; import com.ycwl.basic.service.mobile.WxPayService; import com.ycwl.basic.utils.ApiResponse; import io.swagger.annotations.Api; @@ -26,7 +26,7 @@ import java.security.GeneralSecurityException; * @Version: 1.0 */ @RestController -@RequestMapping("/api/mobile/wx/v1") +@RequestMapping("/api/mobile/wx/pay/v1") @Api(tags = "微信支付相关接口") public class AppWxPayController { diff --git a/src/main/java/com/ycwl/basic/enums/BizCodeEnum.java b/src/main/java/com/ycwl/basic/enums/BizCodeEnum.java index c6401f2..f5e889c 100644 --- a/src/main/java/com/ycwl/basic/enums/BizCodeEnum.java +++ b/src/main/java/com/ycwl/basic/enums/BizCodeEnum.java @@ -418,13 +418,14 @@ public enum BizCodeEnum { /* -------------------------------------*/ /* */ - /* 2000-2019 微信支付相关业务码 */ + /* 2000-2019 微信相关业务码 */ /* */ /* -------------------------------------*/ ADVANCE_PAYMENT_FAILED(2001, "预支付失败"), ADVANCE_PAYMENT_CALLBACK_FAILED(2002, "预支付回调失败"), ADVANCE_PAYMENT_REFUND_FAILED(2003, "退款失败"), ADVANCE_PAYMENT_CALLBACK_REFUND_FAILED(2004, "退款回调失败"), + REQUEST_WECHAT_FAIL(2005, "请求微信服务器发生异常"), ; diff --git a/src/main/java/com/ycwl/basic/model/wxPay/WXPayOrderReqVO.java b/src/main/java/com/ycwl/basic/model/wx/WXPayOrderReqVO.java similarity index 94% rename from src/main/java/com/ycwl/basic/model/wxPay/WXPayOrderReqVO.java rename to src/main/java/com/ycwl/basic/model/wx/WXPayOrderReqVO.java index d4e0e63..10ccebc 100644 --- a/src/main/java/com/ycwl/basic/model/wxPay/WXPayOrderReqVO.java +++ b/src/main/java/com/ycwl/basic/model/wx/WXPayOrderReqVO.java @@ -1,4 +1,4 @@ -package com.ycwl.basic.model.wxPay; +package com.ycwl.basic.model.wx; import io.swagger.annotations.ApiModelProperty; diff --git a/src/main/java/com/ycwl/basic/model/wx/WechatAccessTokenVO.java b/src/main/java/com/ycwl/basic/model/wx/WechatAccessTokenVO.java new file mode 100644 index 0000000..b9eb392 --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/wx/WechatAccessTokenVO.java @@ -0,0 +1,24 @@ +package com.ycwl.basic.model.wx; + +import lombok.Data; + +/** + * 微信获取token对象 + * @author: chenxi + * @date: 2021/8/5 20:50 + */ +@Data +public class WechatAccessTokenVO extends WechatBaseVO{ + + /** + * 微信access_token,由于微信接口返回数据,此处无法保证驼峰命名 + */ + private String access_token; + + /** + * 过期时间,单位秒 + */ + private Integer expires_in; + + +} diff --git a/src/main/java/com/ycwl/basic/model/wx/WechatBaseVO.java b/src/main/java/com/ycwl/basic/model/wx/WechatBaseVO.java new file mode 100644 index 0000000..a89351a --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/wx/WechatBaseVO.java @@ -0,0 +1,39 @@ +package com.ycwl.basic.model.wx; + +import lombok.Data; + + +/** + * @author: chenxi + * @date: 2021/8/5 21:06 + */ +@Data +public class WechatBaseVO { + + /** + * 微信成功返回码 + */ + public static final Integer SUCCESS_CODE = 0; + + /** + * 错误码,0表示正确 + */ + private Integer errcode; + + /** + * 错误信息 + */ + private String errmsg; + + /** + * 判断当前响应是否正常 + * @return + */ + public boolean isSuccess(){ + if (errcode == null || errcode.equals(SUCCESS_CODE)){ + return true; + } + return false; + } + +} diff --git a/src/main/java/com/ycwl/basic/model/wx/WechatMessageSubscribeForm.java b/src/main/java/com/ycwl/basic/model/wx/WechatMessageSubscribeForm.java new file mode 100644 index 0000000..fe1efac --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/wx/WechatMessageSubscribeForm.java @@ -0,0 +1,44 @@ +package com.ycwl.basic.model.wx; + + +import lombok.Data; + +import java.util.Map; + +/** + * @Author: songmingsong + * @CreateTime: 2024-12-06 + * @Description: 发送消息所需要的参数 + * @Version: 1.0 + */ +@Data +public class WechatMessageSubscribeForm { + + /** + * 用户的openID + */ + private String openId; + + /** + * 默认跳到小程序首页 + */ + private String page = "pages/index/index"; + + /** + * 推送文字 + */ + private Map data; + + /** + * 跳转小程序的类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版 + */ + private String miniprogram_state; + + /** + * 进入小程序查看的语言类型:支持zh_CN、en_US、zh_HK、zh_TW,默认为zh_CN + */ + private String lang; + + +} + diff --git a/src/main/java/com/ycwl/basic/model/wx/WechatMssVO.java b/src/main/java/com/ycwl/basic/model/wx/WechatMssVO.java new file mode 100644 index 0000000..77236ac --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/wx/WechatMssVO.java @@ -0,0 +1,45 @@ +package com.ycwl.basic.model.wx; + + +import lombok.Data; + +import java.util.Map; + +/** + * @Author: songmingsong + * @CreateTime: 2024-12-06 + * @Description: 消息通知请求参数 + * @Version: 1.0 + */ +@Data +public class WechatMssVO { + /** + * 用户openid + */ + private String touser; + + /** + * 订阅消息模版id + */ + private String template_id; + + /** + * 默认跳到小程序首页 + */ + private String page = "pages/index/index"; + + /** + * 推送文字 + */ + private Map data; + + /** + * 跳转小程序的类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版 + */ + private String miniprogram_state; + + /** + * 进入小程序查看的语言类型:支持zh_CN、en_US、zh_HK、zh_TW,默认为zh_CN + */ + private String lang; +} diff --git a/src/main/java/com/ycwl/basic/model/wx/WechatTemplateData.java b/src/main/java/com/ycwl/basic/model/wx/WechatTemplateData.java new file mode 100644 index 0000000..d0f8c6a --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/wx/WechatTemplateData.java @@ -0,0 +1,21 @@ +package com.ycwl.basic.model.wx; + + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * @Author: songmingsong + * @CreateTime: 2024-12-06 + * @Description: 消息内容 + * @Version: 1.0 + */ +@AllArgsConstructor +@Data +public class WechatTemplateData { + + /** + * 消息通知的内容 + */ + private String value; +} diff --git a/src/main/java/com/ycwl/basic/model/wxPay/WxPayRespVO.java b/src/main/java/com/ycwl/basic/model/wx/WxPayRespVO.java similarity index 96% rename from src/main/java/com/ycwl/basic/model/wxPay/WxPayRespVO.java rename to src/main/java/com/ycwl/basic/model/wx/WxPayRespVO.java index 448cffd..4b711ef 100644 --- a/src/main/java/com/ycwl/basic/model/wxPay/WxPayRespVO.java +++ b/src/main/java/com/ycwl/basic/model/wx/WxPayRespVO.java @@ -1,4 +1,4 @@ -package com.ycwl.basic.model.wxPay; +package com.ycwl.basic.model.wx; import io.swagger.annotations.ApiModelProperty; diff --git a/src/main/java/com/ycwl/basic/model/wxPay/WxchatCallbackSuccessData.java b/src/main/java/com/ycwl/basic/model/wx/WxchatCallbackSuccessData.java similarity index 97% rename from src/main/java/com/ycwl/basic/model/wxPay/WxchatCallbackSuccessData.java rename to src/main/java/com/ycwl/basic/model/wx/WxchatCallbackSuccessData.java index cefbf97..e9f152a 100644 --- a/src/main/java/com/ycwl/basic/model/wxPay/WxchatCallbackSuccessData.java +++ b/src/main/java/com/ycwl/basic/model/wx/WxchatCallbackSuccessData.java @@ -1,4 +1,4 @@ -package com.ycwl.basic.model.wxPay; +package com.ycwl.basic.model.wx; import cn.hutool.core.date.DateUtil; diff --git a/src/main/java/com/ycwl/basic/service/impl/mobile/WxNotifyServiceImpl.java b/src/main/java/com/ycwl/basic/service/impl/mobile/WxNotifyServiceImpl.java new file mode 100644 index 0000000..aa645ea --- /dev/null +++ b/src/main/java/com/ycwl/basic/service/impl/mobile/WxNotifyServiceImpl.java @@ -0,0 +1,123 @@ +package com.ycwl.basic.service.impl.mobile; + + +import com.alibaba.fastjson.JSONObject; +import com.ycwl.basic.config.WechatConfig; +import com.ycwl.basic.constant.NumberConstant; +import com.ycwl.basic.constant.WeiXinConstant; +import com.ycwl.basic.enums.BizCodeEnum; +import com.ycwl.basic.exception.AppException; +import com.ycwl.basic.model.wx.WechatAccessTokenVO; +import com.ycwl.basic.model.wx.WechatMessageSubscribeForm; +import com.ycwl.basic.model.wx.WechatMssVO; +import com.ycwl.basic.service.mobile.WxNotifyService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import static com.ycwl.basic.constant.WeiXinConstant.*; + +/** + * @Author: songmingsong + * @CreateTime: 2024-12-06 + * @Description: 微信消息通知实现 + * @Version: 1.0 + */ +@Slf4j +@Service +public class WxNotifyServiceImpl implements WxNotifyService { + + private final RestTemplate restTemplate = new RestTemplate(); + + @Autowired + private WechatConfig wechatConfig; + + /** + * 缓存accessToken + */ + private Map cacheAccessTokenMap = new ConcurrentHashMap<>(); + + @Override + public String getAccessToken() { + String cacheKey = wechatConfig.getMiniProgramAppId() + wechatConfig.getMiniProgramSecret(); + AccessTokenCacheEntity accessTokenCacheEntity = cacheAccessTokenMap.get(cacheKey); + if (accessTokenCacheEntity != null && accessTokenCacheEntity.expireDate.getTime() > System.currentTimeMillis()) { + return accessTokenCacheEntity.accessToken; + } + String url = String.format(WeiXinConstant.ACCESS_TOKEN_WITH_PARAM, wechatConfig.getMiniProgramAppId(), wechatConfig.getMiniProgramSecret()); + ResponseEntity responseEntity = restTemplate.getForEntity(url, WechatAccessTokenVO.class); + if (HttpStatus.OK == responseEntity.getStatusCode()) { + WechatAccessTokenVO accessTokenVO = responseEntity.getBody(); + if (accessTokenVO.isSuccess()) { + accessTokenCacheEntity = new AccessTokenCacheEntity(); + accessTokenCacheEntity.accessToken = accessTokenVO.getAccess_token(); + // 设置过期时间,减100秒防止网络延迟失效 + accessTokenCacheEntity.expireDate = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(accessTokenVO.getExpires_in() - NumberConstant.HUNDRED)); + // 缓存一份,有效期内避免重复请求 + cacheAccessTokenMap.put(cacheKey, accessTokenCacheEntity); + return accessTokenVO.getAccess_token(); + } else { + log.error("[微信token]请求AccessToken出现异常,错误信息为{}:{}", accessTokenVO.getErrcode(), accessTokenVO.getErrmsg()); + throw new AppException(BizCodeEnum.REQUEST_WECHAT_FAIL); + } + } + throw new AppException(BizCodeEnum.REQUEST_WECHAT_FAIL); + } + + /** + * 给用户发送通知 + * + * @param info + * @return + */ + + @Override + public JSONObject pushMessage(WechatMessageSubscribeForm info) { + RestTemplate restTemplate = new RestTemplate(); + String url = MESSAGE_SEND_URL + getAccessToken(); + + // 拼接推送的模板 + WechatMssVO wxMssVO = new WechatMssVO(); + wxMssVO.setTouser(info.getOpenId()); // 用户的openId + wxMssVO.setTemplate_id(wechatConfig.getTemplateId()); // 订阅消息模板id + wxMssVO.setLang(info.getLang()); // 语言类型 + wxMssVO.setMiniprogram_state(info.getMiniprogram_state()); // 跳转小程序类型 + wxMssVO.setPage(info.getPage()); + + // // TODO: 推送的内容 + // Map map = new HashMap<>(); + // map.put("msg", new WechatTemplateData("发消息了")); + wxMssVO.setData(info.getData()); + + // 发送 + ResponseEntity responseEntity = restTemplate.postForEntity(url, wxMssVO, String.class); + JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(responseEntity.getBody())); + Integer errcode = jsonObject.getInteger(WECHAT_ERRCODE); + String errmsg = jsonObject.getString(WECHAT_ERRMSG); + if (errcode != NumberConstant.ZERO) { + throw new AppException(errcode, errmsg); + } + return JSONObject.parseObject(JSONObject.toJSONString(responseEntity.getBody())); + } + + + private class AccessTokenCacheEntity { + /** + * token + */ + private String accessToken; + + /** + * 有效期到 + */ + private Date expireDate; + } +} diff --git a/src/main/java/com/ycwl/basic/service/impl/mobile/WxPayServiceImpl.java b/src/main/java/com/ycwl/basic/service/impl/mobile/WxPayServiceImpl.java index 7740fc5..9160b6a 100644 --- a/src/main/java/com/ycwl/basic/service/impl/mobile/WxPayServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/impl/mobile/WxPayServiceImpl.java @@ -23,9 +23,9 @@ import com.ycwl.basic.enums.BizCodeEnum; import com.ycwl.basic.enums.OrderStateEnum; import com.ycwl.basic.exception.AppException; import com.ycwl.basic.model.pc.order.resp.OrderRespVO; -import com.ycwl.basic.model.wxPay.WXPayOrderReqVO; -import com.ycwl.basic.model.wxPay.WxPayRespVO; -import com.ycwl.basic.model.wxPay.WxchatCallbackSuccessData; +import com.ycwl.basic.model.wx.WXPayOrderReqVO; +import com.ycwl.basic.model.wx.WxPayRespVO; +import com.ycwl.basic.model.wx.WxchatCallbackSuccessData; import com.ycwl.basic.service.HttpService; import com.ycwl.basic.service.mobile.WxPayService; import com.ycwl.basic.service.pc.OrderService; diff --git a/src/main/java/com/ycwl/basic/service/mobile/WxNotifyService.java b/src/main/java/com/ycwl/basic/service/mobile/WxNotifyService.java new file mode 100644 index 0000000..967b499 --- /dev/null +++ b/src/main/java/com/ycwl/basic/service/mobile/WxNotifyService.java @@ -0,0 +1,17 @@ +package com.ycwl.basic.service.mobile; + +import com.alibaba.fastjson.JSONObject; +import com.ycwl.basic.model.wx.WechatMessageSubscribeForm; + +public interface WxNotifyService { + + /** + * 获取微信token + */ + String getAccessToken(); + + /** + * 发送模板消息 + */ + JSONObject pushMessage(WechatMessageSubscribeForm info); +} diff --git a/src/main/java/com/ycwl/basic/service/mobile/WxPayService.java b/src/main/java/com/ycwl/basic/service/mobile/WxPayService.java index c2e3698..1f24914 100644 --- a/src/main/java/com/ycwl/basic/service/mobile/WxPayService.java +++ b/src/main/java/com/ycwl/basic/service/mobile/WxPayService.java @@ -1,8 +1,8 @@ package com.ycwl.basic.service.mobile; -import com.ycwl.basic.model.wxPay.WXPayOrderReqVO; -import com.ycwl.basic.model.wxPay.WxPayRespVO; -import com.ycwl.basic.model.wxPay.WxchatCallbackSuccessData; +import com.ycwl.basic.model.wx.WXPayOrderReqVO; +import com.ycwl.basic.model.wx.WxPayRespVO; +import com.ycwl.basic.model.wx.WxchatCallbackSuccessData; import javax.servlet.http.HttpServletRequest; import java.io.IOException;