spring boot 开发微信公众号

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

posted @ 2019-07-25 11:39  anscript  阅读(4291)  评论(0编辑  收藏  举报