微信公众号消息推送开发
1.前期准备
首先登录公司的微信公众号开发平台:微信公众平台
1.获取AppID和AppSecret,可向同事获取,禁止重置密码,否则将影响所有在用服务
2.设置域名以及ip白名单
ip白名单主要是针对获取acces_token,一般设置成自己服务器的ip地址,因为还需要设置自定义域名,可以关联到服务器地址,从而微信接口请求返回可以有对应的请求地址
设置域名主要做回调地址使用,例如:test.soft.com,需要根据官方指引,将对应的txt文件放到web服务器的根目录下即可
3.选择合适的消息模板,添加模板后记住其模板id和点击详情后的内容格式,因为发送模板消息,需要模板id以及内容格式
4.添加开发者,否则后续测试会出现报错;并且进入开发者文档-微信网页开发-web开发者工具 页面内下载微信开发者工具(用于测试)
2.获取用户openId、access_token
这一步非常重要,只有是跟微信对接,都需要得到用户的openId,才能把模板消息发送到对应的用户中,且每个微信用户都有一个唯一的openId,当获取到openId后,建议保存入库;
可参考微信官方文档:开发前必读 | 微信开放文档
这里将会指引你如何将用户进行网页授权也是就
引导用户进入授权页面同意授权,获取code
通过 code 换取网页授权access_token、openId
根据通过网页授权access_token和 openid 获取用户基本信息
@Controller // 此处不能使用RestController
@Api(tags = "微信公众号")
@RequestMapping("wx")
@Slf4j
public class WXAccountController {
@Autowired
private RedisUtil redisUtil;
@Value("${account.app-id}")
private String appId;
@Value("${account.app-secret}")
private String appSecret;
@Value("${account.message-template}")
private String templeteId;
/**
*根据需求自定义重定向的路径,域名是须的
*前端根据此路径进行访问
**/
@ApiOperation("查询code的url")
@GetMapping("getCode")
public String getCode(){
// 官方地址
String urlFir = "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid=";
// 自定义跳转方法
String redirectMethod = "http://iot.henengsoft.com/uav-water/wx/oauth";
// 地址进行encode转译 (未转译的地址是:http://iot.henengsoft.com/uav-water/wx/oauth)
String encoderUrl = "";
try {
encoderUrl = java.net.URLEncoder.encode(redirectMethod,"GBK");
}catch (Exception e){
log.error("编码失败:{}",e.getLocalizedMessage());
}
return urlFir +appId + "&redirect_uri=" + encoderUrl +"&response_type=code&scope=snsapi_base" + "&state=STATE" + "#wechat_redirect";
}
@ApiOperation("根据code获取openId")
@GetMapping("/oauth")
public Result wxOauth(@RequestParam String code, @RequestParam String state){
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="
+ appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code";
Map<String, Object> paramMap = null;
String res = HttpUtil.get(url, paramMap);
String openId = JSONObject.parseObject(res).getString("openid");
log.info("openId: {}",openId);
return Result.ok(openId);
}
@ApiOperation("获取token")
@GetMapping("/getToken")
public void getAccessToken() throws Exception{
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+ appId +"&secret=" + appSecret;
String res = HttpUtil.get(url);
JSONObject jsonObject = JSONObject.parseObject(res);
String accessToken = jsonObject.getString("access_token");
log.info("accessToken:{}", accessToken);
}
}
3.测试
打开微信
开发者工具,使用已配置了开发者权限的账号进行登录
在输入框输入上面接口的全路径后回车,查看请求的信息即可获得code和state,使用postman等工具访问接口即可获得openId和access_token
access_token的有效时间为两小时,可放入redis,设置过期时间小于2小时
4.调用微信模板方法发送消息
1.自定义dto
@Data
@ToString
public class WeChatTemplateMsg {
/**
* 消息
*/
private String value;
/**
* 消息颜色
*/
private String color;
public WeChatTemplateMsg(String value) {
this.value = value;
this.color = "#173177";
}
public WeChatTemplateMsg(String value, String color) {
this.value = value;
this.color = color;
}
}
2.构建消息并发送
@GetMapping("/sendMessage")
public String sendMessage() {
// 模板参数
Map<String, WeChatTemplateMsg> sendMag = new HashMap<String, WeChatTemplateMsg>();
// openId代表一个唯一微信用户,即微信消息的接收人
String openId = "oNB9p1BpVJEquxxxxxxxxx";
// 公众号的模板id(也有相应的接口可以查询到)
String templateId = "B0YStqTYdjHhY9Da9Sy2NM7xxxxxxxxxxx";
// 微信的基础accessToken
String accessToken = "57_LubK-8NKQc6C7jsLMxvdHaI0ju4x3-HPWEFhh7GKkw9fKbWhuxxoZyX4GaVIn6y4yO7RKfSlCyHdedKJlHUMZkd8457nKm0TOoaVkbzK1HCZ4g4gZdrmAGBylGBOZu9yxxxxxxxxxxxxxxxx";
String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken;
/**
* 其他模板可以从模板库中自己添加
* 模板ID
* B0YStqTYdjHhY9Da9Sy2NM7HXxxxxxxxxxxxxxx
* 开发者调用模板消息接口时需提供模板ID
* 标题
* 产品兑付成功提醒
* 行业
* 金融业 - 证券|基金|理财|信托
* 详细内容
* {{first.DATA}}
* 产品名称:{{keyword1.DATA}}
* 当期兑付本金:{{keyword2.DATA}}
* 当期兑付利息:{{keyword3.DATA}}
* 已兑付期数:{{keyword4.DATA}}
* 兑付日期:{{keyword5.DATA}}
* {{remark.DATA}}
*/
sendMag.put("first", new WeChatTemplateMsg("f123"));
sendMag.put("keyword1", new WeChatTemplateMsg("111"));
sendMag.put("keyword2", new WeChatTemplateMsg("222"));
sendMag.put("keyword3", new WeChatTemplateMsg("333"));
sendMag.put("keyword4", new WeChatTemplateMsg("444"));
sendMag.put("remark", new WeChatTemplateMsg("r555"));
RestTemplate restTemplate = new RestTemplate();
//拼接base参数
Map<String, Object> sendBody = new HashMap<>();
sendBody.put("touser", openId); // openId
sendBody.put("url", "www.baidu.com"); // 点击模板信息跳转地址
sendBody.put("topcolor", "#FF0000"); // 顶色
sendBody.put("data", sendMag); // 模板参数
sendBody.put("template_id", templateId); // 模板Id
ResponseEntity<String> forEntity = restTemplate.postForEntity(url, sendBody, String.class);
log.info("结果是: {}",forEntity.getBody());
JSONObject jsonObject = JSONObject.parseObject(forEntity.getBody());
// 0
String messageCode = jsonObject.getString("errcode");
// 2431260672639467520
String msgId = jsonObject.getString("msgid");
System.out.println("messageCode : " + messageCode + ", msgId: " +msgId);
return forEntity.getBody();
}
上述步骤已测试完成消息推送的全流程,项目中根据需求自行封装即可。以下是我个人的封装仅供参考
5.上述功能的封装
1.实体定义:WxMsgDataVO、WxMsgVO、WxtemplateDataVO
@ApiModel(description = "微信公众号推送的消息数据,属性名称不能改")
@Data
public class WxMsgDataVO {
@ApiModelProperty("内容")
private String value;
@ApiModelProperty("模板内容字体颜色,不写默认黑色")
private String color;
public WxMsgDataVO(String value) {
this.value = value;
this.color = "#173177";
}
public WxMsgDataVO(String value,String color) {
this.value = value;
this.color = color;
}
}
@Data
@ApiModel(description = "微信公众号推送消息所需数据,属性名称不能改")
@Accessors(chain = true)
public class WxMsgVO implements Serializable {
@ApiModelProperty("openId")
private String touser;
@ApiModelProperty("模板Id")
private String template_id;
@ApiModelProperty("点击模板信息跳转地址")
private String url;
@ApiModelProperty("模板参数")
private Map<String,WxMsgDataVO> data;
}
// 不同的模板这个类的属性不一样
@Data
@Accessors(chain = true)
@ApiModel("微信模板数据")
public class WxtemplateDataVO {
@ApiModelProperty("用户Id")
private String userId;
@ApiModelProperty("详细内容")
private String details;
@ApiModelProperty("设备名称")
private String deviceName;
@ApiModelProperty("设备编号")
private String deviceNum;
@ApiModelProperty("报警时间")
private String time;
@ApiModelProperty("报警防区")
private String area;
@ApiModelProperty("警情类型")
private String type;
@ApiModelProperty("备注")
private String remarks;
}
2.接口定义
/**
* @Description: 微信公众号消息
* @Author: wmg
* @Version: V1.0
*/
public interface IWxMessageService {
/**
* 将系统用户账号绑定微信用户的openId
* */
Result bindingUser(WxBinding wxBinding);
/**
* 发送消息给对应用户
* */
Result sendMsg(WxtemplateDataVO wxtemplateDataVO);
}
3.接口实现
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.message.entity.WxBinding;
import org.jeecg.modules.message.service.IWxMessageService;
import org.jeecg.modules.message.vo.WxMsgDataVO;
import org.jeecg.modules.message.vo.WxMsgVO;
import org.jeecg.modules.message.vo.WxtemplateDataVO;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
public class WxMessageServiceImpl implements IWxMessageService {
@Autowired
private RedisUtil redisUtil;
@Autowired
private ISysUserService userService;
@Value("${account.app-id}")
private String appId;
@Value("${account.app-secret}")
private String appSecret;
@Value("${account.message-template}")
private String templeteId;
public static String wxTokenKey = "wxAccessToken";
@Override
public Result bindingUser(WxBinding wxBinding) {
if (StrUtil.isBlank(wxBinding.getCode())||StrUtil.isBlank(wxBinding.getPhone())) {
return Result.error("手机号码或code值不能为空!");
}
SysUser sysUser = userService.getOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getPhone, wxBinding.getPhone()));
if (sysUser==null){
return Result.error("未找到此号码的用户,请先在系统绑定电话号码再绑定微信!");
}
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="
+ appId + "&secret=" + appSecret + "&code=" + wxBinding.getCode() + "&grant_type=authorization_code";
Map<String, Object> paramMap = null;
String res = HttpUtil.get(url, paramMap);
String openId = JSONObject.parseObject(res).getString("openid");
if (StrUtil.isBlank(openId)){
return Result.error("绑定用户微信失败!信息:"+res);
}
sysUser.setOpenId(openId);
userService.updateById(sysUser);
return Result.ok("绑定微信用户成功!");
}
@Override
public Result sendMsg(WxtemplateDataVO wxtemplateDataVO) {
// 1.验证用户信息
if (StrUtil.isBlank(wxtemplateDataVO.getUserId())){
return Result.error("用户id不能为空!");
}
SysUser sysUser = userService.getById(wxtemplateDataVO.getUserId());
if (sysUser==null||StrUtil.isBlank(sysUser.getOpenId())){
return Result.error("未找到此用户或用户未绑定微信!");
}
// 2。获取wx的accessToken
String accessToken;
boolean hasKey = redisUtil.hasKey(wxTokenKey);
if (hasKey){
accessToken = (String) redisUtil.get(wxTokenKey);
}else {
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+ appId +"&secret=" + appSecret;
String res = HttpUtil.get(url);
JSONObject jsonObject = JSONObject.parseObject(res);
accessToken = jsonObject.getString("access_token");
if (StrUtil.isNotBlank(accessToken)){
// accessToken有效期为2小时,此处为1h55m
redisUtil.set(wxTokenKey,accessToken,6900);
}else {
return Result.error("无法获取到微信的accessToken");
}
}
String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken;
// 模板参数
Map<String, WxMsgDataVO> sendMsg = new HashMap<>();
/**
详细内容
{{first.DATA}}
设备名称:{{keyword1.DATA}}
设备编号:{{keyword2.DATA}}
报警时间:{{keyword3.DATA}}
报警防区:{{keyword4.DATA}}
警情类型:{{keyword5.DATA}}
{{remark.DATA}}
*/
sendMsg.put("first", new WxMsgDataVO(wxtemplateDataVO.getDetails()));
sendMsg.put("keyword1", new WxMsgDataVO(wxtemplateDataVO.getDeviceName()));
sendMsg.put("keyword2", new WxMsgDataVO(wxtemplateDataVO.getDeviceNum()));
sendMsg.put("keyword3", new WxMsgDataVO(wxtemplateDataVO.getTime()));
sendMsg.put("keyword4", new WxMsgDataVO(wxtemplateDataVO.getArea()));
sendMsg.put("keyword5", new WxMsgDataVO(wxtemplateDataVO.getType()));
sendMsg.put("remark", new WxMsgDataVO(wxtemplateDataVO.getRemarks()));
WxMsgVO wxMsgVO = new WxMsgVO();
wxMsgVO.setTouser(sysUser.getOpenId())
.setUrl("www.baidu.com") // TODO 跳转路径需修改
.setTemplate_id(templeteId)
.setData(sendMsg);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> forEntity = restTemplate.postForEntity(url, wxMsgVO, String.class);
log.info("结果是: {}",forEntity.getBody());
JSONObject jsonObject = JSONObject.parseObject(forEntity.getBody());
// 0
String messageCode = jsonObject.getString("errcode");
// 2431260672639467520
String msgId = jsonObject.getString("msgid");
System.out.println("messageCode : " + messageCode + ", msgId: " +msgId);
return Result.ok(forEntity.getBody());
}
}