微信公众号消息推送


微信公众号消息推送开发

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后,建议保存入库;

可参考微信官方文档:开发前必读 | 微信开放文档

这里将会指引你如何将用户进行网页授权也是就

  1. 引导用户进入授权页面同意授权,获取code

  2. 通过 code 换取网页授权access_token、openId

  3. 根据通过网页授权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());
    }
}

文章作者: wmg
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 wmg !
  目录