因为公司需要开发微信公众号模块,所以在网上看了很多关于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[i] & 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 吧,用的时候,感觉也很不错。就分享给大家吧