注: 里面很多代码 参照 若依 开源项目
记录一下
后续会更新 支付和退款 二维码 红包 名片分享 等功能代码
引入一下微信SDK
开发配置
微信配置实体类
litemall.wx 对应yml文件 微信配置
@Data
@Configuration
@ConfigurationProperties(prefix = "litemall.wx")
public class WxProperties {
private String appId;
private String appSecret;
private String mchId;
private String mchKey;
private String notifyUrl;
private String keyPath;
private String token;
private String aesKey;
微信类初始化
项目运行时将部分微信类初始化,后面需要时直接 @Autowired 引入对应类即可
具体代码:
@AllArgsConstructor
@Configuration
public class WxConfig {
private final LogHandler logHandler;
private final NullHandler nullHandler;
private final KfSessionHandler kfSessionHandler;
private final StoreCheckNotifyHandler storeCheckNotifyHandler;
private final LocationHandler locationHandler;
private final MenuHandler menuHandler;
private final MsgHandler msgHandler;
private final UnsubscribeHandler unsubscribeHandler;
private final SubscribeHandler subscribeHandler;
private final ScanHandler scanHandler;
@Autowired
private WxProperties properties;
@Bean
public WxMaConfig wxMaConfig() {
WxMaInMemoryConfig config = new WxMaInMemoryConfig();
config.setAppid(properties.getAppId());
config.setSecret(properties.getAppSecret());
return config;
}
@Bean
public WxMaService wxMaService(WxMaConfig maConfig) {
WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(maConfig);
return service;
}
@Bean
public WxPayConfig wxPayConfig() {
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(properties.getAppId());
payConfig.setMchId(properties.getMchId());
payConfig.setMchKey(properties.getMchKey());
payConfig.setNotifyUrl(properties.getNotifyUrl());
payConfig.setKeyPath(properties.getKeyPath());
payConfig.setTradeType("JSAPI");
payConfig.setSignType("MD5");
return payConfig;
}
@Bean
public WxPayService wxPayService(WxPayConfig payConfig) {
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(payConfig);
return wxPayService;
}
@Bean
public WxMpService wxMpService() {
WxMpService service = new WxMpServiceImpl();
WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl();
configStorage.setAppId(properties.getAppId());
configStorage.setSecret(properties.getAppSecret());
configStorage.setToken(properties.getToken());
configStorage.setAesKey(properties.getAesKey());
Map<String, WxMpConfigStorage> configStorages = Maps.newHashMap();
configStorages.put("wxConfig",configStorage);
service.setMultiConfigStorages(configStorages);
return service;
}
@Bean
public WxMpMessageRouter messageRouter(WxMpService wxMpService) {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
// 记录所有事件的日志 (异步执行)
newRouter.rule().handler(this.logHandler).next();
// 接收客服会话管理事件
newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION)
.handler(this.kfSessionHandler).end();
newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION)
.handler(this.kfSessionHandler).end();
newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION)
.handler(this.kfSessionHandler).end();
// 门店审核事件
newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end();
// 自定义菜单事件
newRouter.rule().async(false).msgType(EVENT).event(CLICK).handler(this.menuHandler).end();
// 点击菜单连接事件
newRouter.rule().async(false).msgType(EVENT).event(VIEW).handler(this.nullHandler).end();
// 关注事件
newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end();
// 取消关注事件
newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end();
// 上报地理位置事件
newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.LOCATION).handler(this.locationHandler).end();
// 接收地理位置消息
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION).handler(this.locationHandler).end();
// 扫码事件
newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.SCAN).handler(this.scanHandler).end();
// 默认
newRouter.rule().async(false).handler(this.msgHandler).end();
return newRouter;
}
初始化中各个事件的具体代码
这里直接放在一起了,使用自行区分创建各个类
public abstract class AbstractHandler implements WxMpMessageHandler {
protected Logger logger = LoggerFactory.getLogger(getClass());
}
@Component
public class KfSessionHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
//TODO 对会话做处理
return null;
}
}
@Component
public class LocationHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.LOCATION)) {
//TODO 接收处理用户发送的地理位置消息
try {
String content = "感谢反馈,您的的地理位置已收到!";
return new TextBuilder().build(content, wxMessage, null);
} catch (Exception e) {
this.logger.error("位置消息接收处理失败" , e);
return null;
}
}
//上报地理位置事件
this.logger.info("上报地理位置,纬度 : {},经度 : {},精度 : {}" ,
wxMessage.getLatitude(), wxMessage.getLongitude(), String.valueOf(wxMessage.getPrecision()));
//TODO 可以将用户地理位置信息保存到本地数据库,以便以后使用
return null;
}
}
@Component
public class LogHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
this.logger.info("\n接收到请求消息,内容:{}" , JsonUtils.toJson(wxMessage));
return null;
}
}
@Component
public class MenuHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService weixinService,
WxSessionManager sessionManager) {
String msg = String.format("type:%s, event:%s, key:%s" ,
wxMessage.getMsgType(), wxMessage.getEvent(),
wxMessage.getEventKey());
if (WxConsts.MenuButtonType.VIEW.equals(wxMessage.getEvent())) {
return null;
}
return WxMpXmlOutMessage.TEXT().content(msg)
.fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
.build();
}
}
@Component
public class MsgHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService weixinService,
WxSessionManager sessionManager) {
if (!wxMessage.getMsgType().equals(WxConsts.XmlMsgType.EVENT)) {
//TODO 可以选择将消息保存到本地
}
//当用户输入关键词如“你好”,“客服”等,并且有客服在线时,把消息转发给在线客服
try {
if (StringUtils.startsWithAny(wxMessage.getContent(), "你好" , "客服")
&& weixinService.getKefuService().kfOnlineList()
.getKfOnlineList().size() > 0) {
return WxMpXmlOutMessage.TRANSFER_CUSTOMER_SERVICE()
.fromUser(wxMessage.getToUser())
.toUser(wxMessage.getFromUser()).build();
}
} catch (WxErrorException e) {
e.printStackTrace();
}
//TODO 组装回复消息
String content = "收到信息内容:" + JsonUtils.toJson(wxMessage);
return new TextBuilder().build(content, wxMessage, weixinService);
}
}
@Component
public class NullHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
return null;
}
}
@Component
public class ScanHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map<String, Object> map,
WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {
this.logger.info("新关注用户 OPENID: " + wxMpXmlMessage.getFromUser());
// 扫码事件处理
if (wxMpXmlMessage.getEventKey() != null) {
System.out.println(wxMpXmlMessage.getEventKey());
}
return null;
}
}
@Component
public class StoreCheckNotifyHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
// TODO 处理门店审核事件
return null;
}
}
@Component
public class SubscribeHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService weixinService,
WxSessionManager sessionManager) throws WxErrorException {
this.logger.info("新关注用户 OPENID: " + wxMessage.getFromUser());
// 获取微信用户基本信息
try {
WxMpUser userWxInfo = weixinService.getUserService()
.userInfo(wxMessage.getFromUser(), null);
// TODO 可以添加关注用户到本地数据库
} catch (WxErrorException e) {
if (e.getError().getErrorCode() == 48001) {
this.logger.info("该公众号没有获取用户信息权限!");
}
}
WxMpXmlOutMessage responseResult = null;
try {
responseResult = this.handleSpecial(wxMessage);
} catch (Exception e) {
this.logger.error(e.getMessage(), e);
}
if (responseResult != null) {
return responseResult;
}
try {
return new TextBuilder().build("感谢关注" , wxMessage, weixinService);
} catch (Exception e) {
this.logger.error(e.getMessage(), e);
}
return null;
}
/**
* 处理特殊请求,比如如果是扫码进来的,可以做相应处理
*/
private WxMpXmlOutMessage handleSpecial(WxMpXmlMessage wxMessage)
throws Exception {
//TODO
return null;
}
}
@Component
public class UnsubscribeHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
String openId = wxMessage.getFromUser();
this.logger.info("取消关注用户 OPENID: " + openId);
// TODO 可以更新本地数据库为取消关注状态
return null;
}
}
门户入口代码
对应微信公众平台—-》开发—-》基本配置 中的 服务器配置
代码:
/**
* @desc: 门户入口
* @author:
* @date:
*/
@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping("/wx/portal/{appid}")
@Validated
public class WxPortalController {
@Autowired
private WxMpService wxService;
@Autowired
private WxMpMessageRouter messageRouter;
@GetMapping(produces = "text/plain;charset=utf-8")
public String authGet(@PathVariable String appid,
@RequestParam(name = "signature" , required = false) String signature,
@RequestParam(name = "timestamp" , required = false) String timestamp,
@RequestParam(name = "nonce" , required = false) String nonce,
@RequestParam(name = "echostr" , required = false) String echostr) {
log.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]" , signature,
timestamp, nonce, echostr);
if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
throw new IllegalArgumentException("请求参数非法,请核实!");
}
if (wxService.checkSignature(timestamp, nonce, signature)) {
return echostr;
}
return "非法请求";
}
@PostMapping(produces = "application/xml; charset=UTF-8")
public String post(@PathVariable String appid,
@RequestBody String requestBody,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("openid") String openid,
@RequestParam(name = "encrypt_type" , required = false) String encType,
@RequestParam(name = "msg_signature" , required = false) String msgSignature) {
log.info("\n接收" +
"微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}],"
+ " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] " ,
openid, signature, encType, msgSignature, timestamp, nonce, requestBody);
if (!this.wxService.switchover(appid)) {
throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!" , appid));
}
if (!wxService.checkSignature(timestamp, nonce, signature)) {
throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
}
String out = null;
if (encType == null) {
// 明文传输的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
WxMpXmlOutMessage outMessage = this.route(inMessage);
if (outMessage == null) {
return "";
}
out = outMessage.toXml();
} else if ("aes".equalsIgnoreCase(encType)) {
// aes加密的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(),
timestamp, nonce, msgSignature);
log.debug("\n消息解密后内容为:\n{} " , inMessage.toString());
WxMpXmlOutMessage outMessage = this.route(inMessage);
if (outMessage == null) {
return "";
}
out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage());
}
log.debug("\n组装回复信息:{}" , out);
return out;
}
private WxMpXmlOutMessage route(WxMpXmlMessage message) {
try {
return this.messageRouter.route(message);
} catch (Exception e) {
log.error("路由消息时出现异常!" , e);
}
return null;
}
}
微信菜单功能
代码:
@Slf4j
@RestController
@RequestMapping("/admin/wx")
@Validated
public class AdminWXController {
private final Log logger = LogFactory.getLog(AdminWXController.class);
@Autowired
private WxMpService wxService;
@RequiresPermissions("admin:wx:getMenu")
@RequiresPermissionsDesc(menu = {"获取微信菜单"}, button = "配置")
@GetMapping("/getMenu")
public Object getMenu() throws WxErrorException {
WxMpMenu.WxMpConditionalMenu menu = wxService.getMenuService().menuGet().getMenu();
return ResponseUtil.ok(menu);
}
@RequiresPermissions("admin:wx:saveMenu")
@RequiresPermissionsDesc(menu = {"保存微信菜单"}, button = "配置")
@PostMapping("/saveMenu")
public Object saveMenu(@RequestBody WxMenuModel wxMenuModel) throws WxErrorException {
WxMenu wxMenu = new WxMenu();
wxMenu.setButtons(wxMenuModel.getButton());
log.info("本次微信菜单配置参数{}", wxMenu.toString());
return ResponseUtil.ok(wxService.getMenuService().menuCreate(wxMenu));
}
}