微信订阅号发送模板消息

参考的链接:

微信公众平台测试号——模板消息发送Demo_a816120的博客-优快云博客

开放接口 | 微信开放文档

微信公众平台

功能一:代码实现发送微信公众平台配置的模板消息

1、事先获取好appID和appsecret

2、书写发送的工具类

package com.talk915.common.templateMsg;

import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.talk915.common.redis.RedisUtil;
import com.talk915.model.pojo.WxAccessToken;
import com.talk915.model.pojo.WxOpenIdInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName : WxTemplateMsgUtil
 * @Author : cong
 * @Date : 2021/11/16 17:33
 * @Description : 微信模板消息工具类
 */

@Slf4j
public class WxTemplateMsgUtil {

    /**
     * @param
     * @return
     * @Author LK
     * @Description 获取accessToken
     * @Date 2021/7/26 17:36
     */
    public static WxAccessToken getAccessToken() {
        WxAccessToken cacheInfo = RedisUtil.getWxAccessToken();
        if(cacheInfo != null){
            Long expiresIn = cacheInfo.getExpiresIn();
            if (expiresIn > 150){
                return cacheInfo;
            }
        }
        //重新请求
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + WxTemplateMsgConstant.WX_TEMPLATE_MSG_APP_ID + "&secret=" + WxTemplateMsgConstant.WX_TEMPLATE_MSG_APP_SECRET;
        String ret = HttpUtil.get(url);
        if (StringUtils.isBlank(ret)) {
            return null;
        }
        Map<String, Object> map = JSONObject.parseObject(ret, Map.class);
        String errCode = String.valueOf(map.get("errcode"));
        if (StringUtils.isNotBlank(errCode) && !"null".equals(errCode)) {
            String errMsg = String.valueOf(map.get("errmsg"));
            log.error("微信获取AssessToken失败,错误码:" + errCode + ";错误消息:" + errMsg);
            return null;
        }
        String accessToken = String.valueOf(map.get("access_token"));
        String expiresIn = String.valueOf(map.get("expires_in"));
        WxAccessToken wxAccessToken = new WxAccessToken();
        wxAccessToken.setAccessToken(accessToken);
        wxAccessToken.setExpiresIn(Long.parseLong(expiresIn));
        //设置缓存
        RedisUtil.setWxAccessToken(wxAccessToken);
        return wxAccessToken;
    }


    /**
     * @param
     * @return
     * @Author LK
     * @Description 模板消息
     * @Date 2021/7/26 18:37
     */
    public static String sendMsg(TemplateMessage templateMessage) {
        WxAccessToken accessTokenInfo = getAccessToken();
        String accessToken = accessTokenInfo.getAccessToken();
        String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken;
        String paramStr = JSON.toJSONString(templateMessage);
        String ret = HttpUtil.post(url, paramStr);
        if (StringUtils.isBlank(ret)) {
            log.error("微信模板消息推送失败!");
            return "";
        }
        Map<String, Object> retMap = JSONObject.parseObject(ret, Map.class);
        Integer errCode = (Integer) retMap.get("errcode");
        if (errCode != 0) {
            String errMsg = String.valueOf(retMap.get("errmsg"));
            log.error("微信模板消息推送失败!,错误信息:" + errMsg);
            return "";
        }
        return String.valueOf(retMap.get("msgid"));
    }

}

3、书写模板类

 

package com.talk915.common.templateMsg;

import org.springframework.beans.factory.annotation.Value;

/**
 * @ClassName : TemplateMsgContent
 * @Author : cong
 * @Date : 2021/11/16 16:54
 * @Description :
 */

public class TemplateMsgContent {

    private static boolean allowSiteEnvironment;


    //补偿课时通知(测试)
    public static String CLASS_COMPENSATE_MSG = "ygJSffhiKZOp5BjreTk3GSurJ8vqWticSbzaTEW5Nwc";

    //课程取消通知(测试)
    private static String CLASS_CANCEL_MSG = "7L3Zel6GFltH0xrf4-qNxC_wE22f_9t-Yi_-O8hT0xw";


    /**
     * @param
     * @return
     * @Author cong
     * @Description 课时补偿通知
     * @Date 2021/11/11 16:43
     */
    public static TemplateMessage getCompensateClassTime(String compensationNum, String userName, String openId) {
        First first = new First("亲,您有" + compensationNum + "个课时返还信息!");
        Keyword[] keywords = new Keyword[2];
        Keyword keyword1 = new Keyword(compensationNum, "#173177");
        Keyword keyword2 = new Keyword(userName, "#173177");
        keywords[0] = keyword1;
        keywords[1] = keyword2;
        Remark remark = new Remark("感谢您的支持!");
        Data data = new Data(first, remark, keywords);
        return new TemplateMessage(openId, CLASS_COMPENSATE_MSG, data);
    }

    /**
     * @param
     * @return
     * @Author cong
     * @Description 取消课程通知
     * @Date 2021/11/11 16:43
     */
    public static TemplateMessage getCancelClass(String className, String userName, String reason, String classTime, String openId) {
        First first = new First("你好,你的课程已被取消!");
        Keyword[] keywords = new Keyword[4];
        Keyword keyword1 = new Keyword(className, "#173177");
        Keyword keyword2 = new Keyword(userName, "#173177");
        Keyword keyword3 = new Keyword(reason, "#173177");
        Keyword keyword4 = new Keyword(classTime, "#173177");
        keywords[0] = keyword1;
        keywords[1] = keyword2;
        keywords[2] = keyword3;
        keywords[3] = keyword4;
        Remark remark = new Remark("敬请谅解,有疑问请联系管理员!");
        Data data = new Data(first, remark, keywords);
        return new TemplateMessage(openId, CLASS_CANCEL_MSG, data);
    }

    /**
     * @param
     * @return
     * @Author cong
     * @Description 正式服的相关配置
     * @Date 2021/3/20 10:21
     */
    @Value("${allow.site.environment}")
    private void setAllowSend(boolean allowSiteEnvironment) {
        TemplateMsgContent.allowSiteEnvironment = allowSiteEnvironment;
        if (allowSiteEnvironment) {
            CLASS_COMPENSATE_MSG = "***";
            CLASS_CANCEL_MSG = "***";
        }
    }

}
public class First {

    private String value;

    private String color;

    public First (String value){
        this.value = value;
    }
}


public class Remark {

    private String value;

    private String color;

    public Remark (String value){
        this.value = value;
    }
}

public class Data {

    private  First first;

    private Keyword keyword1;

    private Keyword keyword2;

    private Keyword keyword3;

    private Keyword keyword4;

    private Remark remark;


    public Data(First first, Remark remark, Keyword... keyword) {
        this.first = first;
        int count = 1;
        for (Keyword keyword1 : keyword) {
            if (count == 1) {
                this.keyword1 = keyword1;
            } else if (count == 2) {
                this.keyword2 = keyword1;
            } else if (count == 3) {
                this.keyword3 = keyword1;
            } else if (count == 4) {
                this.keyword4 = keyword1;
            }
            count++;
        }
        this.remark = remark;
    }

功能二:代码实现输入关键字推送对应的内容

1、首先在微信公众平台配置回调地址         url/weChat/check

2、编写接收的接口逻辑

package com.talk915.async.controller.wx;

import cn.hutool.crypto.SecureUtil;
import com.talk915.async.service.UserBindService;
import com.talk915.common.templateMsg.AesException;
import com.talk915.common.templateMsg.SHA1;
import com.talk915.common.templateMsg.WxMsgInfo;
import com.talk915.common.templateMsg.WxTemplateMsgConstant;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Date;

/**
 * @ClassName : WeChatController
 * @Author : cong
 * @Date : 2021/11/12 14:41
 * @Description : 微信服务号使用
 */
@RestController
@RequestMapping(value = "/weChat")
public class WeChatController {

    @Autowired
    private UserBindService userBindService;

    /**
     * 微信加密字符串
     */
    private static final String token = "talk915";


    /**
     * @param
     * @return
     * @Author LK
     * @Description 接收微信发送数据
     * @Date 2021/8/12 14:44
     */
    @PostMapping(value = "/check",produces = MediaType.APPLICATION_XML_VALUE)
    public String wxPostConnect(HttpServletRequest request) {

        try {
            Marshaller marshaller;
            Unmarshaller unmarshal;
            //解析对象
            JAXBContext jaxbContext = JAXBContext.newInstance(WxMsgInfo.class);
            unmarshal = jaxbContext.createUnmarshaller();
            //xml解码成bean对象
            WxMsgInfo wxMsgInfo = (WxMsgInfo) unmarshal.unmarshal(request.getInputStream());
            String event = wxMsgInfo.getEvent();
            String msgType = wxMsgInfo.getMsgType();
            //关注用户openId
            String fromUserName = wxMsgInfo.getFromUserName();
            //接收的内容
            String msg = wxMsgInfo.getContent();
            //判断 1.关注/取关  2.发送消息 3.找不到相应的就推送客服链接
            // 判断是否为关注,subscribe(订阅)、unsubscribe(取消订阅)
            String content = "";
            if (StringUtils.isNotBlank(event)){
                //关注
                boolean isSubscribe = "subscribe".equals(event);
                //关注或取关
                boolean subscribe = "subscribe".equals(event) || "unsubscribe".equals(event);
                //推送默认消息
                if (isSubscribe) {
                    content = WxTemplateMsgConstant.createTextMsg(1,"");
                }
                //添加/编辑 关注/取关记录
                if (subscribe){
                    userBindService.addOrEditSubscribeStatus(fromUserName,isSubscribe);
                }
            } else if (StringUtils.isNotBlank(msgType) && "text".equals(msgType) && WxTemplateMsgConstant.BIND_TEXT.contains(msg)) {
                content = WxTemplateMsgConstant.createTextMsg(2, SecureUtil.md5(fromUserName + "TaLk#915"));
            } else if (StringUtils.isNotBlank(msgType) && "text".equals(msgType) && WxTemplateMsgConstant.UN_BIND_TEXT.contains(msg)){
                content = WxTemplateMsgConstant.createTextMsg(3,"");
            } else if (StringUtils.isNotBlank(msgType) && "text".equals(msgType) && StringUtils.isNotBlank(WxTemplateMsgConstant.checkUnBindText(msg))){
               //解除绑定
               content = userBindService.unBindAccount(WxTemplateMsgConstant.checkUnBindText(msg),fromUserName);
            } else {
                content = WxTemplateMsgConstant.createTextMsg(4,"");
            }
            WxMsgInfo msgInfo = new WxMsgInfo();
            msgInfo.setFromUserName(wxMsgInfo.getToUserName());
            msgInfo.setToUserName(wxMsgInfo.getFromUserName());
            msgInfo.setCreateTime(new Date().getTime());
            msgInfo.setMsgType("text");
            msgInfo.setContent(content);
            marshaller = jaxbContext.createMarshaller();
            StringWriter writer = new StringWriter();
            marshaller.marshal(msgInfo, writer);
            return writer.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}
package com.talk915.common.templateMsg;

import com.talk915.common.pattern.PatternCheckUtil;
import com.talk915.common.util.FileUrlConstant;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;

import java.util.Arrays;
import java.util.List;

/**
 * @ClassName : WxTemplateMsgConstant
 * @Author : cong
 * @Date : 2021/11/10 18:14
 * @Description : 微信模板信息
 */

public class WxTemplateMsgConstant {

    private static boolean allowSiteEnvironment;

    /**
     * 测试服appId
     */
    public static String WX_TEMPLATE_MSG_APP_ID = "wx11d4e0f54f0cbbbb";

    /**
     * 测试服appSecret
     */
    public static String WX_TEMPLATE_MSG_APP_SECRET = "774b8b69b473fd2b03930f6bfe80010b";

    /**
     * 客服地址
     */
    public static final String DEFAULT_CS_LINK = "服务号暂时不支持其他功能,正在完善中!若您有其他问题,您可以咨询 <a href=\"https://platform.usongshu.com/im/index.html#/robot?platformType=0&platformUserId=&platformUserType=7&platformPhone=&platformNickName=&platformTerminalType=1\">在线客服</a>";

    /**
     * 账号绑定基础链接
     */
    private static final String  BIND_BASE_URL = FileUrlConstant.getPrefixUrl() + "h5/wxBind?code=";



    //前缀
    private static final String BIND_URL_PREFIX = "<a href=\"";

    //后缀
    private static final String BIND_URL_SUFFIX = "\">点击完成账号绑定</a>";

    /**
     * 账号绑定关键词
     */
    public static final List<String> BIND_TEXT = Arrays.asList("账号绑定","绑定账号","账号关联","关联","上课提醒","绑定","通知");

    /**
     * 账号解绑
     */
    public static final List<String> UN_BIND_TEXT = Arrays.asList("账号解绑","解绑账号","解绑");



    /**
     * @Author  LK
     * @Description  创建文本消息
     * @Date 2021/8/16 10:18
     * @param
     * @return
     */
    public static String createTextMsg(int type,String baseContent) {
        String content = "";
        switch (type) {
            //1.关注默认消息
            case 1:
                //content = "感谢您关注说客英语服务号!目前服务号提供上课提醒功能,回复 绑定账号,根据提示完成账号绑定即可!";
                content = "感谢您关注说客英语服务号!" +
                        "目前服务号提供课前提醒和上课迟到提醒,回复:绑定,根据提示完成帐号绑定即可接收推送信息." +
                        "如需解绑,回复:解绑_帐号,例如:解绑_12345678901";
                break;
            //2.账号绑定
            case 2:
                content = BIND_URL_PREFIX + BIND_BASE_URL + baseContent + BIND_URL_SUFFIX;
                break;
            //账号解绑
            case 3:
                content = "按照如下格式输入您需要解绑的账号即可:解绑_13700000001";
                break;
            //其他内容暂时推送客服链接
            default:
                content = DEFAULT_CS_LINK;
        }
        return content;
    }


    /**
     * @Author  LK
     * @Description  检测解绑格式
     * @Date 2021/8/24 18:06
     * @param
     * @return
     */
    public static String checkUnBindText(String text){
        if (StringUtils.isBlank(text)){
            return "";
        }
        boolean contains = text.contains("_");
        if(!contains){
            return "";
        }
        String[] arr = StringUtils.split(text, "_");
        if (!arr[0].equals("解绑")){
            return "";
        }
        String account = arr[1];
        if (StringUtils.isBlank(account)){
            return "";
        }
        boolean checked = PatternCheckUtil.isPhone(account);
        if (!checked){
            return "";
        }
        return account;
    }

    /**
     * @param
     * @return
     * @Author cong
     * @Description 正式服的相关配置
     * @Date 2021/3/20 10:21
     */
    @Value("${allow.site.environment}")
    private void setAllowSend(boolean allowSiteEnvironment) {
        WxTemplateMsgConstant.allowSiteEnvironment = allowSiteEnvironment;
        if (allowSiteEnvironment) {
            WX_TEMPLATE_MSG_APP_ID = "***";
            WX_TEMPLATE_MSG_APP_SECRET = "***";
        }
    }
}
<think>我们注意到用户的问题是关于如何在前端实现微信小程序向微信服务发送模板消息的功能。但根据微信的官方规则,小程序和服务属于不同的账体系,它们之间不能直接互相发送消息。不过,我们可以通过以下方式间接实现: 1. 用户在小程序前端触发某些操作(例如提交表单、支付成功等)时,小程序前端将需要发送消息内容传递给小程序后端。 2. 小程序后端将消息内容通过微信提供的服务模板消息接口(需要用户已关注服务且授权)发送到服务,再由服务推送给用户。 但是,用户要求在前端实现。这里需要明确:微信服务模板消息发送必须由后端发起,因为需要调用微信的接口(需要access_token,而前端暴露appsecret是极其不安全的)。因此,前端无法直接推送服务模板消息。 不过,我们可以考虑另一种方式:使用微信统一服务消息(原模板消息已弃用,现在服务也使用订阅消息机制,但服务目前仍支持模板消息,但新规则建议使用订阅消息)。但请注意,小程序和服务之间用户身份通过unionid关联,因此需要用户同时关注服务且在小程序内授权过。 因此,实现步骤大致如下: 1. 小程序前端收集用户操作,并获取用户在小程序内的openid(或unionid)。 2. 小程序前端将需要发送消息内容、用户openid(或unionid)发送给小程序后端。 3. 小程序后端通过微信服务模板消息接口(或订阅消息接口)发送消息。这里需要服务的appid和appsecret获取access_token,然后调用发送模板消息的API。 所以,前端部分只负责收集用户操作和触发后端调用,而实际的消息发送由后端完成。 但是,用户的问题特别强调“前端实现”,这可能是因为用户希望在前端触发消息推送。那么,前端需要做的工作是: 1. 确保用户已经授权并获取了用户的openid(通过wx.login获取code,然后由后端换取openid)。 2. 当用户触发某个事件(如点击按钮)时,调用小程序的wx.request将事件数据(包括用户openid、模板消息所需的数据)发送到小程序后端的一个API。 3. 后端接收到请求后,验证数据,然后使用服务模板消息API发送消息。 另外,需要注意:服务模板消息发送要求用户必须关注了该服务,并且用户在小程序和服务之间是通过同一个微信开放平台账下的unionid关联的。 因此,前端代码示例(微信小程序)如下: ```javascript // 假设用户已经登录,获取了用户的openid(实际开发中,openid通常由后端返回并存储在小程序storage中) const openid = wx.getStorageSync('openid'); // 从缓存中获取openid // 用户点击发送消息按钮 function sendTemplateMessage() { // 准备模板消息所需的数据,这里需要根据服务模板的要求来组织数据 const data = { touser: openid, // 接收消息的用户openid(注意:这个openid是用户在小程序中的openid,但服务发送模板消息需要的是用户在该服务下的openid。因此,实际应用中,后端需要根据小程序的openid去查询对应的服务openid,或者使用unionid关联。所以这里更合理的做法是传递unionid,或者由后端去转换) // 但是,由于小程序和服务在同一个开放平台下,后端可以通过unionid来关联用户,所以这里我们传递小程序的openid,由后端去获取对应的服务openid(如果后端已经存储了用户的unionid和服务openid的关联关系) // 因此,前端只需要传递小程序的openid,以及模板消息所需的具体内容 template_id: '服务模板消息模板ID', // 这个模板ID需要在服务后台申请 url: '点击消息跳转的链接', // 可选 data: { // 模板消息的具体内容,根据模板的关键字来设置 keyword1: { value: '内容1' }, keyword2: { value: '内容2' } // ... 其他关键字 } }; // 调用后端API wx.request({ url: 'https://your-backend-domain.com/send-template-message', method: 'POST', data: data, success(res) { if (res.data.code === 0) { wx.showToast({ title: '发送成功', }); } else { wx.showToast({ title: '发送失败:' + res.data.message, icon: 'none' }); } }, fail() { wx.showToast({ title: '请求失败', icon: 'none' }); } }); } ``` 但是,请注意:上述代码中的template_id和data结构都是服务模板消息的格式,而服务模板消息发送需要用户关注服务。如果用户没有关注服务,则发送失败。 另外,由于微信官方已经将小程序模板消息废弃,改为订阅消息,而服务目前仍支持模板消息(但新开发建议使用订阅消息)。但服务订阅消息和小程序的订阅消息是独立的,不能通过小程序直接触发服务订阅消息。因此,通常的做法还是通过服务模板消息。 但是,如果用户希望使用服务订阅消息,那么需要用户在小程序前端触发时,同时引导用户跳转到服务进行订阅(这通常需要用户主动订阅,且订阅一次只能发送一条消息)。流程较为复杂,不在本题讨论范围内。 因此,总结:前端无法直接推送服务模板消息,必须通过后端。前端只负责收集数据并调用后端API。 另外,根据引用[3]和引用[4],现在小程序推荐使用订阅消息,而服务目前也支持订阅消息(但服务订阅消息的触发需要用户主动订阅,且订阅一次只能发送一条消息,或者长期订阅需要特定行业)。 如果用户希望实现的是小程序自己的消息推送,那么应该使用小程序的订阅消息(即用户在小程序内订阅,然后后端发送,用户在小程序的服务通知中收到)。但用户的问题明确要求是“向微信服务发送模板消息”,所以这里我们按照服务模板消息的实现方式。 注意:在微信开放平台中,小程序和服务绑定在同一主体下,那么用户在小程序和服务中的unionid是相同的,因此可以通过unionid来关联用户。后端需要存储用户的unionid和服务openid的对应关系(当用户关注服务时,服务后台可以获取到用户的openid和unionid,并存储到数据库)。 因此,前端需要传递小程序的openid(或者unionid,如果前端能获取到的话)和消息内容。后端根据unionid查询到该用户在服务的openid,然后发送模板消息。 由于小程序前端无法直接获取unionid(需要用户授权且后端解密),所以通常前端只传递小程序的openid,后端通过小程序的openid去查询用户的unionid(前提是后端已经存储了该用户在小程序的登录信息,包括openid和unionid)。 因此,前端代码中,我们只需要传递小程序的openid即可。 最后,需要强调的是,服务模板消息发送接口是:https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN 而后端需要先获取服务的access_token(通过服务的appid和appsecret)。 所以,整个流程中,前端的工作就是触发事件并传递数据到后端,后端完成消息发送。 如果用户坚持要在前端完成,那么只能通过跳转到服务的方式(比如通过服务菜单或者客服消息引导用户去服务操作),但这样就不是推送模板消息了。 因此,我们只能按照上述流程实现。 注意:如果用户没有关注服务,则无法发送模板消息。此时,可以考虑使用小程序的订阅消息(用户在小程序内订阅,然后后端发送小程序订阅消息,用户在小程序的服务通知中收到),但这样就不是服务消息了。 如果用户的需求是希望用户能在微信的服务通知中收到类似服务模板消息的卡片,那么使用小程序的订阅消息也可以实现类似的效果(用户收到的是小程序的服务通知,但样式类似)。所以,如果用户不介意消息来源是小程序而不是服务,那么使用小程序的订阅消息是更简单的方式。 这里,我们假设用户确实需要发送服务模板消息。 所以,前端实现的部分就是调用后端API。后端实现的部分需要包括: 1. 获取服务access_token(需要缓存,因为每日获取次数有限)。 2. 根据小程序的openid查询用户对应的服务openid(通过unionid关联)。 3. 调用微信服务模板消息接口发送消息。 因此,回答用户:微信小程序前端无法直接推送微信服务模板消息,但可以通过调用后端API的方式触发后端发送。前端需要收集用户openid和模板消息所需的数据,然后通过wx.request发送到后端。 下面,我们按照要求生成相关问题。</think>### 微信小程序前端实现推送服务模板消息的步骤 根据微信官方规则,小程序**无法直接从前端推送服务模板消息**,因为: 1. 小程序和服务属于不同账体系(openid不同) 2. 模板消息发送需后端调用敏感接口(需access_token) 3. 需用户授权关联(unionid机制) 但可通过以下流程间接实现: ```mermaid graph LR A[小程序前端] -->|1. 触发事件| B[小程序后端] B -->|2. 获取unionid| C[微信开放平台] C -->|3. 关联用户| D[服务后端] D -->|4. 发送模板消息| E[用户服务] ``` #### 前端核心实现步骤 1. **获取用户授权** ```javascript // 获取用户手机(用于关联服务) wx.login({ success: (res) => { wx.getPhoneNumber({ success: (res) => { const { code } = res wx.request({ url: '后端API', data: { code } }) } }) } }) ``` 2. **传递模板参数** ```javascript // 提交模板消息所需数据 wx.request({ url: '后端消息推送API', method: 'POST', data: { template_id: '服务模板ID', formData: { keyword1: { value: '订单内容' }, keyword2: { value: '2023-08-01' } } } }) ``` 3. **处理跳转引导(可选)** ```javascript // 引导用户关注服务 wx.showModal({ title: '温馨提示', content: '需关注服务接收消息', success: (res) => { if (res.confirm) { wx.navigateToMiniProgram({ appId: '服务appid', path: '关注页路径' }) } } }) ``` #### 后端关键配合 1. **unionid关联用户** ```python # Python示例(使用微信开放平台API) def get_unionid(appid, secret, code): url = f'https://api.weixin.qq.com/sns/oauth2/access_token?appid={appid}&secret={secret}&code={code}&grant_type=authorization_code' response = requests.get(url) return response.json().get('unionid') ``` 2. **发送模板消息[^2][^4]** ```python def send_template_message(openid, template_id, data): access_token = get_access_token() # 获取服务access_token url = f'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={access_token}' payload = { "touser": openid, # 服务openid "template_id": template_id, "data": data # 与模板占位符严格一致 } requests.post(url, json=payload) ``` #### 注意事项 1. **账关联要求** - 小程序和服务必须在[微信开放平台](https://open.weixin.qq.com/)绑定 - 用户需同时使用小程序并关注服务 - 需获取用户**unionid**实现账关联[^1] 2. **消息类型限制** ```mermaid pie title 消息推送类型对比 “服务模板消息” : 45 “小程序订阅消息” : 55 ``` 3. **合规性要求** - 模板需在服务后台申请审核 - 禁止营销类内容推送 - 需用户主动触发事件[^3] > ⚠️ 实际开发中,**前端仅负责收集数据和触发事件**,核心推送逻辑必须由后端实现,主要原因: > 1. 避免暴露AppSecret(高风险) > 2. 需要unionid关联计算 > 3. 需服务access_token(2小时刷新) ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值