微信消息的集成(微信公众号)
场景:主要用于商城购买提示,金额提醒,活动广告等
申请微信测试号(和微信小程序有区别)
https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
拿到信息
查看具体文档
获取 access_token(这里区别其他的 token)
本质就是发送一个请求
发送消息模板文档
消息模板
拿到模板 ID
总结需要的东西
appID,appsecret,模板 ID
流程:
1.根据 appid 和 appsecret 拿到 access_token
2.发一个请求(请求中包含模板内容,access_token)
pom.xml(需要用到消息微服务的api----因为AliSmsModel类在消息api中)
<dependency>
<groupId>com.xiaoge</groupId>
<artifactId>message-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
启动类
@SpringBootApplication
@EnableEurekaClient
public class MemberServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MemberServiceApplication.class, args);
}
}
UserController
package com.xiaoge.controller;
import com.alibaba.fastjson.JSON;
import com.xiaoge.constant.QueueConstant;
import com.xiaoge.domain.User;
import com.xiaoge.domain.UserCollection;
import com.xiaoge.model.WxMsgModel;
import com.xiaoge.service.UserCollectionService;
import com.xiaoge.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* @Classname UserController
* @Date 2022/6/6 下午8:23
* @Created by xiaoge
* @Description TODO
*/
@Api(tags = "前台用户管理接口")
@RestController
@RequestMapping
public class UserController {
@Autowired
private UserService userService;
@Autowired
private RabbitTemplate rabbitTemplate;
@PostMapping("p/sms/wxSend")
@ApiOperation("测试发送微信公众号消息")
public ResponseEntity<String> wxSend(String openId, String name) {
// 构建一个对象
WxMsgModel wxMsgModel = new WxMsgModel();
wxMsgModel.setToUser(openId);
// 公纵号用户点击信息时去往地址
wxMsgModel.setUrl("https://www.baidu.com");
// 模版id
wxMsgModel.setTemplateId("XIL5vtqbdBZbejJ2xfXv-cqXxLni9RzjG32Qjpg_OoY");
// 头颜色
wxMsgModel.setTopColor("#FF0000");
HashMap<String, Map<String, String>> data = new HashMap<>();
data.put("userName", WxMsgModel.buildMap(name, "#FF0000"));
data.put("time", WxMsgModel.buildMap(LocalDateTime.now().toString(), "#FF0000"));
data.put("product", WxMsgModel.buildMap("女朋友", "#FF0000"));
data.put("money", WxMsgModel.buildMap("0.1", "#FF0000"));
wxMsgModel.setData(data);
// 用mq发消息
rabbitTemplate.convertAndSend(QueueConstant.WX_CHANGE_EX, QueueConstant.WX_CHANGE_KEY, JSON.toJSONString(wxMsgModel));
return ResponseEntity.ok("发送成功");
}
}
message-api中模型类 用户微服务用到了该模型类
package com.xiaoge.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WxMsgModel {
@JsonProperty(value = "touser")
private String toUser;
@JsonProperty(value = "template_id")
private String templateId;
@JsonProperty(value = "url")
private String url;
@JsonProperty(value = "topcolor")
private String topColor;
@JsonProperty(value = "data")
private Map<String, Map<String, String>> data;
/**
* 专门构建参数的
*
* @param value
* @param color
* @return
*/
public static Map<String, String> buildMap(String value, String color) {
HashMap<String, String> map = new HashMap<>();
map.put("value", value);
map.put("color", color);
return map;
}
}
RabbitMQConfig(发送短信mq配置 PHONE_CHANGE_QUEUE都是自己随便定义的常量值)
package com.xiaoge.config;
import com.xiaoge.constant.QueueConstant;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Classname RabbitMQConfig
* @Date 2022/6/25 下午2:56
* @Created by xiaoge
* @Description TODO
*/
@Configuration
public class RabbitMQConfig {
/* 微信mq配置 */
@Bean
public Queue wxChangeQueue() {
return new Queue(QueueConstant.WX_CHANGE_QUEUE);
}
@Bean
public DirectExchange wxChangeExchange() {
return new DirectExchange(QueueConstant.WX_CHANGE_EX);
}
@Bean
public Binding wxBind() {
return BindingBuilder
.bind(wxChangeQueue())
.to(wxChangeExchange())
.with(QueueConstant.WX_CHANGE_KEY);
}
}
application.yml
# 微信公纵号信息
wechat:
app-id: wx010a59592d9c7234
app-secret: dd6508eb89c7509db542c4b8a0cb062e
token-url: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s
message-url: https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s
WechatProperties
package com.xiaoge.config;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @Classname WechatProperties
* @Date 2022/6/25 下午5:31
* @Created by xiaoge
* @Description TODO
*/
@Data
@ConfigurationProperties(prefix = "wechat")
public class WechatProperties {
@ApiModelProperty("微信的应用的 id")
private String appId;
@ApiModelProperty("微信的密钥")
private String appSecret;
@ApiModelProperty("获取 Tokenurl 的地址")
private String tokenUrl;
@ApiModelProperty("发送微信模板消息的地址")
private String messageUrl;
}
WechatAutoConfiguration
package com.xiaoge.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
/**
* @Classname WechatAutoConfiguration
* @Date 2022/6/25 下午5:32
* @Created by xiaoge
* @Description TODO
*/
@Slf4j
@Configuration
@EnableConfigurationProperties(value = WechatProperties.class)
public class WechatAutoConfiguration {
@Autowired
private RestTemplate restTemplate;
@Autowired
private WechatProperties wechatProperties;
/**
* volatile(jvm提供的轻量级的同步机制) 三大特性:
* 1. 保证可见性
* 2. 不保证原子性
* 3. 禁止指令重排
*
* 1. 验证volatile的可见性
* 1.1 假如int number = 0; number变量之前根本没有添加volatile关键字修饰, 没有可见性
* 1.2 添加了volatile, 可以解决可见性问题.
*
* 2. 验证volatile不保证原子性
* 2.1 原子性指的是什么意思?
* 不可分割, 完整性, 也即某个线程正在做某个具体业务时, 中间不可以被加塞或者被分割, 需要整体完整
* 要么同时成功, 要么同时失败.
*
* 2.2 volatile不保证原子性 案例演示
*
* number ++ 在多线程下是非线程安全的, 如何不加synchronized解决?
*
* 2.3 why?
* 因为在线程自己工作内存写回主内存的时候, 太快了, 通知其他线程还没到, 其他线程也直接写回主内存了, 所以少于20000次, 所以出现写丢的情况.
*
* 2.4 如何解决原子性?
* * 加synchronized
* * 使用我们的juc 下 AtomicInteger
*
*/
//因为我们需要定时刷新 token,为了让其他线程可以看到,我们使用 volatile 来修饰
private volatile String accessToken;
/**
* 这个spring实现了这个注解的规范
* 所有的bean对象创建完以后执行, 在项目启动完全之前执行
* 限制 不能有返回值, 也不能有参数
*
* 因为消费者直接监听者生产者,
* 当消费者挂了以后,
* 在次启动时他会直接消费,
* 但是这时候token是没有获取到的,
* 这要会报尖token, 所以要在这里加这个注解
*/
@PostConstruct
public void tokenInit() {
refushToken();
}
/**
* 刷新 token 的方法 两个小时以内获取一次
* initialDelay项目启动完后执行, 所以才会导致token失效, 因为消费者和他是同时执行的
*/
@Scheduled(initialDelay = 7100 * 1000, fixedRate = 7100 * 1000)
public void refushToken() {
String tokenUrl = String.format(wechatProperties.getTokenUrl(),
wechatProperties.getAppId(),
wechatProperties.getAppSecret());
String jsonStr = restTemplate.getForObject(tokenUrl, String.class);
JSONObject jsonObject = JSON.parseObject(jsonStr);
String accessToken = jsonObject.getString("access_token");
if (!StringUtils.isEmpty(accessToken)) {
log.info("token 获取成功");
this.accessToken = accessToken;
return;
}
log.error("token 获取失败");
}
public String getAccessToken() {
return this.accessToken;
}
}
WechatListener(监听器, 并且发送提示信息)
package com.xiaoge.listener;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import com.xiaoge.config.WechatAutoConfiguration;
import com.xiaoge.config.WechatProperties;
import com.xiaoge.constant.QueueConstant;
import com.xiaoge.model.WxMsgModel;
import com.xiaoge.service.SmsLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
/**
* @Classname WechatListener
* @Date 2022/6/25 下午5:46
* @Created by xiaoge
* @Description TODO
*/
@Slf4j
@Component
public class WechatListener {
@Autowired
private WechatProperties wechatProperties;
@Autowired
private WechatAutoConfiguration wechatAutoConfiguration;
@Autowired
private SmsLogService smsLogService;
@Autowired
private RestTemplate restTemplate;
@RabbitListener(queues = QueueConstant.WX_CHANGE_QUEUE, concurrency = "3-5")
public void handlerWechatMessage(Channel channel, Message message) {
String wxStr = new String(message.getBody());
// 拿到消息
WxMsgModel wxMsgModel = JSON.parseObject(wxStr, WxMsgModel.class);
// 发型微信公纵号消息
realSendWxMsg(wxMsgModel);
smsLogService.saveWxMsg(wxMsgModel);
try {
// 签收消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
log.error("签收失败!");
}
}
/**
* 真正发微信消息的方法
* 发送微信公纵号信息
* @param wxMsgModel
*/
private void realSendWxMsg(WxMsgModel wxMsgModel) {
// 发送公纵号信息的url
String messageUrl = String.format(wechatProperties.getMessageUrl(),
wechatAutoConfiguration.getAccessToken());
// 发送消息
String result = restTemplate.postForObject(messageUrl, wxMsgModel, String.class);
System.out.println(result);
}
}
SmsLogService
package com.xiaoge.service;
import com.aliyuncs.CommonResponse;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaoge.domain.SmsLog;
import com.xiaoge.model.AliSmsModel;
import com.xiaoge.model.WxMsgModel;
/**
* @Classname SmsLogService
* @Date 2022/6/25 下午3:19
* @Created by xiaoge
* @Description TODO
*/
public interface SmsLogService extends IService<SmsLog>{
/**
* 把wx信息保存进数据库
* @param wxMsgModel
*/
void saveWxMsg(WxMsgModel wxMsgModel);
}
SmsLogServiceImpl(保存发送过的短信到数据库)
package com.xiaoge.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonResponse;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaoge.domain.SmsLog;
import com.xiaoge.mapper.SmsLogMapper;
import com.xiaoge.model.AliSmsModel;
import com.xiaoge.model.WxMsgModel;
import com.xiaoge.service.SmsLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* @Classname SmsLogServiceImpl
* @Date 2022/6/25 下午3:19
* @Created by xiaoge
* @Description TODO
*/
@Service
public class SmsLogServiceImpl extends ServiceImpl<SmsLogMapper, SmsLog> implements SmsLogService{
@Autowired
private SmsLogMapper smsLogMapper;
/**
* 把wx信息保存进数据库
*
* @param wxMsgModel
*/
@Override
public void saveWxMsg(WxMsgModel wxMsgModel) {
SmsLog smsLog = new SmsLog();
smsLog.setContent("发微信消息成功");
smsLog.setUserId(wxMsgModel.getToUser());
smsLog.setResponseCode("200");
smsLog.setRecDate(new Date());
smsLog.setStatus(0);
smsLog.setType(2);
// 插入数据库
smsLogMapper.insert(smsLog);
}
}
打开微信小程序, 随便一个请求都带着token, 拿过来就行了