|
因为公司需要开发微信公众号模块,所以在网上看了很多关于spring boot微信公众号的开发,都感觉不能满足自己对代码简单处理。
按照我的思路,完成了一个微信公众号的业务功能的开发,并总结如下:
1,创建用于接收和返回的对象
import lombok.Data;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@Data
@XmlRootElement(name="xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class WeChatMessageBo {
private String ToUserName;
private String FromUserName;
private long CreateTime;
private String MsgType;
private String Event;
// 消息id
protected Long MsgId;
// 文本内容
private String Content;
// 图片链接(由系统生成)
private String PicUrl;
// 图片消息媒体id,可以调用多媒体文件下载接口拉取数据
private String MediaId;
private String EventKey;
}
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
@Data
@XmlRootElement(name="xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class WeChatMessageVo {
// 发送方的账号
protected String FromUserName;
// 接收方的账号(OpenID)
protected String ToUserName;
// 消息创建时间
protected Long CreateTime;
/**
* 消息类型
* text 文本消息
* image 图片消息
* voice 语音消息
* video 视频消息
* music 音乐消息
* news 图文消息
*/
protected String MsgType;
// 语音
@XmlElementWrapper(name="Voice")
private String[] MediaId ;
// 文本内容
private String Content;
private int ArticleCount;
//图文消息
@XmlElementWrapper(name="Articles")
private List<NewItems> item;
}
笔者把请求对象全部塞到bo里面,把返回的数据全部塞到vo里面,把数据简单处理,不再根据请求类型拆分不同的vo和bo
2创建controller,直接采用jdk中原装的 unmarshaller 和 marshaller 对事件的报文进行解析和封装
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.IOException;
@RestController
@Api(tags = "微信公众号服务")
@RequestMapping(value = "/weChart")
@Slf4j
public class WeChartController {
@Autowired(required = false)
private WeChartService weChartService;
private Unmarshaller unmarshaller;
private Marshaller marshaller;
@PostConstruct
private void init(){
JAXBContext context = null;
try {
context = JAXBContext.newInstance(WeChatMessageBo.class);
unmarshaller = context.createUnmarshaller();
} catch (JAXBException e) {
log.info(e.toString());
}
try {
context = JAXBContext.newInstance(WeChatMessageVo.class);
marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_ENCODING,"UTF-8");
} catch (JAXBException e) {
log.info(e.toString());
}
}
@ApiOperation(value = "验证")
@RequestMapping(value = "auth",method = RequestMethod.GET)
public String auth( @RequestParam(name="signature") String signature,
@RequestParam(name="timestamp") String timestamp,
@RequestParam(name="nonce") String nonce,
@RequestParam(name="echostr") String echostr){
return weChartService.auth(signature,timestamp,nonce,echostr);
}
@ApiOperation(value = "接收事件")
@RequestMapping(value = "auth",method = RequestMethod.POST, produces = MediaType.APPLICATION_XML_VALUE)
public void event(HttpServletRequest request,HttpServletResponse response ){
try {
WeChatMessageBo weChatMessageBo = (WeChatMessageBo) unmarshaller.unmarshal(request.getInputStream());
Object res = weChartService.response(weChatMessageBo);
response.setCharacterEncoding("UTF-8");
marshaller.marshal(res, response.getWriter());
} catch (Exception e) {
e.printStackTrace();
}
}
}
3,建一个注解和一个对应接口,用来区分不同的请求类型,当然,其他使用if else也是可以实现的。这里为了代码更加简练 注解使用@Component,之后把引用该注解的类,就不需要再次添加该注解
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Component
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Hkey {
String name() default "";
}
创建一个接口,用来处理微信调用的不同类型
public interface Hander {
public Object Hander(WeChatMessageBo bo);
}
4:service 层逻辑处理
public class WeChartService implements ApplicationContextAware {
/*
* 自定义token, 用作生成签名,从而验证安全性
* */
@Value("${weChart.token}")
private String token ;
@Value("${weChart.appid}")
private String appid;
@Value("${weChart.secret}")
private String secret;
@Autowired
private WeChartApi weChartApi;
@Autowired
private CacheAsyncService cacheAsyncService;
public String auth(String signature, String timestamp, String nonce, String echostr) {
String sortStr = sort(token,timestamp,nonce);
String mySignature = shal(sortStr);
if(!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)){
return echostr;
}else {
return null;
}
}
public String sort(String token, String timestamp, String nonce) {
String[] strArray = {token, timestamp, nonce};
Arrays.sort(strArray);
StringBuilder sb = new StringBuilder();
for (String str : strArray) {
sb.append(str);
}
return sb.toString();
}
public String shal(String str){
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(str.getBytes());
byte messageDigest[] = digest.digest();
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
private HashMap<String, Hander> handers=new HashMap<>();
public Object response(WeChatMessageBo msg) {
String msgType = msg.getMsgType();
String Event = msg.getEvent();
String EventKey = msg.getEventKey();
Hander hander = handers.get(msgType + "." + Event + "." + EventKey);
if (hander!=null){
return hander.Hander(msg);
}
return null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Hander> beansOfType = applicationContext.getBeansOfType(Hander.class);
for ( Hander hander: beansOfType.values()) {
Hkey hkey = hander.getClass().getAnnotation(Hkey.class);
handers.put(hkey.name(),hander);
}
}
}
该步使用利用了spring的ApplicationContext,对之后使用的hander 进行处理,方便responer方法的使用(该service 为节选,功能都存在,拷贝时可能代码可能出现问题)
5具体方法的实现: 用户回复,回复用户图文消息或者文子消息
@Hkey(name="text.null.null")
public class Replay implements Hander {
@Autowired
private Mapper mapper;
private String unfind= "对不起,没找到你搜索的数据。\n要不,联系一下客服?或者我给你笑一个?\uE057";
@Override
//回复用户和图文消息
public Object Hander(WeChatMessageBo msg) {
WeChatMessageVo out = new WeChatMessageVo();
//把原来的发送方设置为接收方
out.setToUserName(msg.getFromUserName());
//把原来的接收方设置为发送方
out.setFromUserName(msg.getToUserName());
//获取接收的消息类型
String msgType = msg.getMsgType();
String content = msg.getContent();
//设置消息创建时间
out.setCreateTime(System.currentTimeMillis());
out.setMsgType("news");
//用户自定义数据
List<Data> list = mapper.selectByNameLike(content);
if(CollectionUtils.isEmpty(gameList)){
out.setMsgType("text");
out.setContent(unfind);
return out;
}else{
ArrayList<NewItems> items = new ArrayList<NewItems>(list.size());
list.stream().forEach(item->{
NewItems newItems = new NewItems();
newItems.setTitle(item.getName());
newItems.setUrl(item.getUrl());
newItems.setDescription(item.getDescription());
newItems.setPicUrl(item.getBackgroundPicture());
items.add(newItems);
});
out.setArticleCount(gameList.size());
out.setItem(items);
return out;
}
}
}
菜单按钮,作者菜单中有个按钮V31_HELP,其他使用可能会有很多按钮,就直接修改注解名字即可
@Hkey(name="event.CLICK.V31_HELP")
public class ForHelp implements Hander {
private String value= "你好,请拨打电话\n";
//联系客服
@Override
public Object Hander(WeChatMessageBo msg) {
WeChatMessageVo out = new WeChatMessageVo();
//把原来的发送方设置为接收方
out.setToUserName(msg.getFromUserName());
//把原来的接收方设置为发送方
out.setFromUserName(msg.getToUserName());
//获取接收的消息类型
String msgType = msg.getMsgType();
//设置消息创建时间
out.setCreateTime(System.currentTimeMillis());
out.setMsgType("text");
out.setContent(value);
return out;
}
}
用户订阅时代码,此处代码,笔者未做测试,使用的用户要看一下注解名字是否正确
@Hkey(name = "event.subscribe.null")
@Service
public class Subscribe implements Hander {
//用户订阅
@Autowired
private Mapper mapper;
@Override
public Object Hander(WeChatMessageBo msg) {
WeChatMessageVo out = new WeChatMessageVo();
//把原来的发送方设置为接收方
out.setToUserName(msg.getFromUserName());
//把原来的接收方设置为发送方
out.setFromUserName(msg.getToUserName());
//获取接收的消息类型
//String msgType = msg.getMsgType();
//设置消息创建时间
out.setCreateTime(System.currentTimeMillis());
out.setMsgType("news");
out.setArticleCount(4);
ArrayList<NewItems> items = new ArrayList<NewItems>(4);
//用户自定义数据
List<Data> list = mapper.selectSubScribeData();
list .stream().forEach(item->{
NewItems newItems = new NewItems();
newItems.setTitle(item.getTitle());
newItems.setUrl(item.getUrl());
newItems.setDescription(item.getDes());
newItems.setPicUrl(item.getPicUrl());
items.add(newItems);
});
out.setItem(items);
return out;
}
}
查询数据库的就不再写了,大概工程结构就是这个样子的,我不想写一堆的xml解析,也不想拷贝的或者是引入各种包,既然如此,那就直接原生的marshaller 吧,用的时候,感觉也很不错。就分享给大家吧
来源:https://www.cnblogs.com/see-saw/p/11243206.html |