微信小程序通过公众号推送模板消息
程序员文章站
2023-12-28 14:51:16
...
微信官方API:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/uniform-message/uniformMessage.send.html
配置文件:
# 服务端口
server:
port: 8110
spring:
application:
# 服务名
name: ybrain-wxmini
# 环境设置:dev、test、prod
profiles:
active: dev
wx:
mp:
appId: 公众号appId
secret: 公众号secret
mini:
appId: 小程序appId
secret: 小程序secret
所需maven依赖:
<!--===========bean 动态 添加 字段==================-->
<!--(必须显式添加)-->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<!--(用spring的间接依赖也可以,不必显式添加)-->
<!-- <dependency>-->
<!-- <groupId>cglib</groupId>-->
<!-- <artifactId>cglib-nodep</artifactId>-->
<!-- <version>3.2.4</version>-->
<!-- </dependency>-->
<!--===========bean 动态 添加 字段==================-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 使用bootstrap需要导入 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
小程序和公众号appId配置:
小程序配置:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author: Murphy
* @date: 2020-12-30
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "wx.mini")
public class WxMiniProperties {
/**
* 设置微信小程序的appid
*/
private String appId;
/**
* 设置微信小程序 secret
*/
private String secret;
}
公众号配置:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author: Murphy
* @date: 2020-12-30
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "wx.mp")
public class WxMpProperties {
/**
* 设置微信公众号的appid
*/
private String appId;
/**
* 设置微信公众号的app secret
*/
private String secret;
}
根据API中的请求参数创建对应实体:
WxMiniTemplate.java
import lombok.Data;
/**
* @author: Murphy
* @date: 2020-12-30
*/
@Data
public class WxMiniTemplate<E> {
private String touser; // 用户openid,可以是小程序的openid,也可以是mp_template_msg.appid对应的公众号的openid
private E weapp_template_msg; // 小程序模板消息相关的信息,可以参考小程序模板消息接口; 有此节点则优先发送小程序模板消息
private E mp_template_msg; // 公众号模板消息相关的信息,可以参考公众号模板消息接口;有此节点并且没有weapp_template_msg节点时,发送公众号模板消息
public WxMiniTemplate(){
}
public WxMiniTemplate(String touser,E weapp_template_msg,E mp_template_msg){
this.touser = touser;
this.weapp_template_msg = weapp_template_msg;
this.mp_template_msg = mp_template_msg;
}
}
WeAppTemplateMsgData.java
import lombok.Data;
/**
* @author: Murphy
* @date: 2020-12-30
*/
@Data
public class WeAppTemplateMsgData<E> {
private String template_id; // 小程序模板ID
private String page; // 小程序页面路径
private String form_id; // 小程序模板消息formid
private E data; // 小程序模板数据
public WeAppTemplateMsgData(){
}
public WeAppTemplateMsgData(String template_id,String page,String form_id,E data){
this.template_id = template_id;
this.page = page;
this.form_id = form_id;
this.data = data;
}
}
MpTemplateMsgData.java
import lombok.Data;
/**
* @author: Murphy
* @date: 2020-12-30
*/
@Data
public class MpTemplateMsgData<E> {
private String appid; // 公众号appid,要求与小程序有绑定且同主体
private String template_id; // 公众号模板id
private String url; // 公众号模板消息所要跳转的url
private E miniprogram; // 公众号模板消息所要跳转的小程序,小程序的必须与公众号具有绑定关系
private E data; // 公众号模板消息的数据
public MpTemplateMsgData(){
}
public MpTemplateMsgData(String appid,String template_id,String url,E miniprogram,E data){
this.appid = appid;
this.template_id = template_id;
this.url = url;
this.miniprogram = miniprogram;
this.data = data;
}
}
Miniprogram.java
import lombok.Data;
/**
* @author: Murphy
* @date: 2020-12-30
*/
@Data
public class Miniprogram {
private String appid; // 小程序appid
private String pagepath; // 要跳转的小程序路径
public Miniprogram(){
}
public Miniprogram(String appid,String pagepath){
this.appid = appid;
this.pagepath = pagepath;
}
}
WxTemplateData.java
import lombok.Data;
/**
* @author: Murphy
* @date: 2020-12-29
*/
@Data
public class WxTemplateData {
private DataValue first;
private DataValue remark;
}
DataValue.java
import lombok.Data;
/**
* @author: Murphy
* @date: 2020-12-29
*/
@Data
public class DataValue {
private Object value;
private String color = "#173177";
public DataValue(Object value){
this.value = value;
}
}
工具类:
对象动态添加字段并赋值
PropertyAppender.java
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.springframework.cglib.beans.BeanGenerator;
import org.springframework.cglib.beans.BeanMap;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
/**
* 参考链接 https://www.cnblogs.com/JaxYoun/p/13923703.html
* @author: Murphy
* @date: 2020-12-29
*/
@Slf4j
public class PropertyAppender {
private static final class DynamicBean {
private Object target;
private BeanMap beanMap;
private DynamicBean(Class superclass, Map<String, Class> propertyMap) {
this.target = generateBean(superclass, propertyMap);
this.beanMap = BeanMap.create(this.target);
}
private void setValue(String property, Object value) {
beanMap.put(property, value);
}
private Object getValue(String property) {
return beanMap.get(property);
}
private Object getTarget() {
return this.target;
}
/**
* 根据属性生成对象
*/
private Object generateBean(Class superclass, Map<String, Class> propertyMap) {
BeanGenerator generator = new BeanGenerator();
if (null != superclass) {
generator.setSuperclass(superclass);
}
BeanGenerator.addProperties(generator, propertyMap);
return generator.create();
}
}
public static Object generate(Object dest, Map<String, Object> newValueMap) throws InvocationTargetException, IllegalAccessException {
PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
//1.获取原对象的字段数组
PropertyDescriptor[] descriptorArr = propertyUtilsBean.getPropertyDescriptors(dest);
//2.遍历原对象的字段数组,并将其封装到Map
Map<String, Class> oldKeyMap = new HashMap<>(4);
for (PropertyDescriptor it : descriptorArr) {
if (!"class".equalsIgnoreCase(it.getName())) {
oldKeyMap.put(it.getName(), it.getPropertyType());
newValueMap.put(it.getName(), it.getReadMethod().invoke(dest));
}
}
//3.将扩展字段Map合并到原字段Map中
newValueMap.forEach((k, v) -> oldKeyMap.put(k, v.getClass()));
//4.根据新的字段组合生成子类对象
DynamicBean dynamicBean = new DynamicBean(dest.getClass(), oldKeyMap);
//5.放回合并后的属性集合
newValueMap.forEach((k, v) -> {
try {
dynamicBean.setValue(k, v);
} catch (Exception e) {
log.error("动态添加字段【值】出错", e);
}
});
return dynamicBean.getTarget();
}
}
HttpUtils.java
/**
* 发送请求工具类
* @author ly
* @create 2020-06-16-16:37
*/
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
public class HttpUtils {
private static CloseableHttpClient httpClient;
static {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(100);
cm.setDefaultMaxPerRoute(20);
cm.setDefaultMaxPerRoute(50);
httpClient = HttpClients.custom().setConnectionManager(cm).build();
}
public static String get(String url) {
CloseableHttpResponse response = null;
BufferedReader in = null;
String result = "";
try {
HttpGet httpGet = new HttpGet(url);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(30000).setConnectionRequestTimeout(30000).setSocketTimeout(30000).build();
httpGet.setConfig(requestConfig);
httpGet.setConfig(requestConfig);
httpGet.addHeader("Content-type", "application/json; charset=utf-8");
httpGet.setHeader("Accept", "application/json");
response = httpClient.execute(httpGet);
in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
result = sb.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != response) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
public static String post(String url, String jsonString) {
CloseableHttpResponse response = null;
BufferedReader in = null;
String result = "";
try {
HttpPost httpPost = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(30000).setConnectionRequestTimeout(30000).setSocketTimeout(30000).build();
httpPost.setConfig(requestConfig);
httpPost.setConfig(requestConfig);
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.setHeader("Accept", "application/json");
httpPost.setEntity(new StringEntity(jsonString, Charset.forName("UTF-8")));
response = httpClient.execute(httpPost);
in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
result = sb.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != response) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
}
Controller层:
WxMiniController.java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.murphy.ybrain.config.WxMiniProperties;
import com.murphy.ybrain.config.WxMpProperties;
import com.murphy.ybrain.entity.*;
import com.murphy.ybrain.utils.HttpUtils;
import com.murphy.ybrain.utils.PropertyAppender;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
/**
* @author: Murphy
* @date: 2020-12-30
*/
@AllArgsConstructor
@RestController
@RequestMapping("/wx/mini")
@Slf4j
public class WxMiniController {
@Autowired
private WxMiniProperties wxMiniProperties;
@Autowired
private WxMpProperties wxMpProperties;
// 获取小程序AccessToken URL
private static String URL_ACCESS_TOKEN_TEMP = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
// 发送消息 URL
private static String URL_SEND_MSG = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=";
// 停课通知模板ID
private static String STOP_CLASS_ID = "6qUZaz8QFrM0PZ0pQRflqqlS0F7CEokU0ul_9-ljpm8";
/**
* 获取 小程序 AccessToken
* @return
*/
@RequestMapping("/getAccessToken")
public String getAccessToken() {
String URL_ACCESS_TOKEN = URL_ACCESS_TOKEN_TEMP+"&appid="+wxMiniProperties.getAppId()+"&secret="+wxMiniProperties.getSecret();
log.info("URL_ACCESS_TOKEN ===> {}",URL_ACCESS_TOKEN);
String res = HttpUtils.get(URL_ACCESS_TOKEN);
return res;
}
/**
* 发送停课通知
* @param openid
* @param title
* @param teacherName
* @param className
* @param classTime
* @param remark
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@GetMapping("/stopclass/send")
public String sendStopClassMsg(@RequestParam String openid,
@RequestParam String title,
@RequestParam String teacherName,
@RequestParam String className,
@RequestParam String classTime,
@RequestParam Object remark) throws InvocationTargetException, IllegalAccessException {
// 配置公众号模板推送数据
WxTemplateData wxTemplateData = new WxTemplateData();
wxTemplateData.setFirst(new DataValue(title));
wxTemplateData.setRemark(new DataValue(remark));
// 向wxTemplateData 添加 新字段并赋值
Map<String, Object> propertiesMap = new HashMap<>(3);
propertiesMap.put("keyword1",new DataValue(teacherName) );
propertiesMap.put("keyword2",new DataValue(className) );
propertiesMap.put("keyword3",new DataValue(classTime) );
// 添加后新生成的 Object
Object obj = PropertyAppender.generate(wxTemplateData, propertiesMap);
// 配置 点击消息跳转首页
Miniprogram miniprogram = new Miniprogram(wxMiniProperties.getAppId(),"pages/schedule/schedule");
// 公众号推送消息模板
MpTemplateMsgData mpTemplateMsgData = new MpTemplateMsgData(wxMpProperties.getAppId(),STOP_CLASS_ID,null,miniprogram,obj);
// 小程序 openid oHQZc5OkJbV9nhBX88t0staHUAfc
// 公众号 openid oPtXz52kMwEaAoMtO4qtiy2hyzg4
WxMiniTemplate wxMiniTemplate = new WxMiniTemplate(openid, null, mpTemplateMsgData);
log.info("wxMiniTemplate ===> {}", JSON.toJSONString(wxMiniTemplate));
JSONObject jsonObject = JSON.parseObject(getAccessToken());
String URL_SEND_STOP_CLASS_MSG = URL_SEND_MSG + jsonObject.get("access_token");
log.info("URL_SEND_STOP_CLASS_MSG ===> {}",URL_SEND_STOP_CLASS_MSG);
String res = HttpUtils.post(URL_SEND_STOP_CLASS_MSG,JSON.toJSONString(wxMiniTemplate));
return res;
}
}