JAVA实现 springMVC方式的微信接入、实现消息自动回复

前段时间小忙了一阵,微信公众号的开发,从零开始看文档,踩了不少坑,也算是熬过来了,最近考虑做一些总结,方便以后再开发的时候回顾,也给正在做相关项目的同学做个参考。
  1. 思路

    微信接入:用户消息和开发者需要的事件推送都会通过微信方服务器发起一个请求,转发到你在公众平台配置的服务器url地址,微信方将带上signature,timestamp,nonce,echostr四个参数,我们自己服务器通过拼接公众平台配置的token,以及传上来的timestamp,nonce进行SHA1加密后匹配signature,返回ture说明接入成功。

 

   消息回复:当用户给公众号发送消息时,微信服务器会将用户消息以xml格式通过POST请求到我们配置好的服务器对应的接口,而我们要做的事情就是根据消息类型等做相应的逻辑处理,并将最后的返回结果也通过xml格式return给微信服务器,微信方再传达给用户的这样一个过程。 

    

 

  1. 公众平台配置



  2. Controller

    复制代码
    @Controller
    @RequestMapping("/wechat")
    publicclass WechatController {
        @Value("${DNBX_TOKEN}")
        private String DNBX_TOKEN;
        
        private static final Logger LOGGER = LoggerFactory.getLogger(WechatController.class);
        
        @Resource
        WechatService wechatService;
        
        /**
         * 微信接入
         * @param wc
         * @return
         * @throws IOException 
         */
        @RequestMapping(value="/connect",method = {RequestMethod.GET, RequestMethod.POST})
        @ResponseBody
        publicvoid connectWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException{
            // 将请求、响应的编码均设置为UTF-8(防止中文乱码)  
            request.setCharacterEncoding("UTF-8");  //微信服务器POST消息时用的是UTF-8编码,在接收时也要用同样的编码,否则中文会乱码;
            response.setCharacterEncoding("UTF-8"); //在响应消息(回复消息给用户)时,也将编码方式设置为UTF-8,原理同上;boolean isGet = request.getMethod().toLowerCase().equals("get"); 
          
            PrintWriter out = response.getWriter();
             
            try {
                if (isGet) {
                    String signature = request.getParameter("signature");// 微信加密签名  
                    String timestamp = request.getParameter("timestamp");// 时间戳  
                    String nonce = request.getParameter("nonce");// 随机数  
                    String echostr = request.getParameter("echostr");//随机字符串  
                    
                    // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败  if (SignUtil.checkSignature(DNBX_TOKEN, signature, timestamp, nonce)) {  
                        LOGGER.info("Connect the weixin server is successful.");
                        response.getWriter().write(echostr);  
                    } else {  
                        LOGGER.error("Failed to verify the signature!"); 
                    }
                }else{
                    String respMessage = "异常消息!";
                    
                    try {
                        respMessage = wechatService.weixinPost(request);
                        out.write(respMessage);
                        LOGGER.info("The request completed successfully");
                        LOGGER.info("to weixin server "+respMessage);
                    } catch (Exception e) {
                        LOGGER.error("Failed to convert the message from weixin!"); 
                    }
                    
                }
            } catch (Exception e) {
                LOGGER.error("Connect the weixin server is error.");
            }finally{
                out.close();
            }
        }
    }
    复制代码

 

    3.签名验证 checkSignature

   从上面的controller我们可以看到,我封装了一个工具类SignUtil,调用了里面的一个叫checkSignature,传入了四个值,DNBX_TOKEN, signature, timestamp, nonce。这个过程非常重要,其实我们可以理解为将微信传过来的值进行一个加解密的过程,很多大型的项目所有的接口为保证安全性都会有这样一个验证的过程。DNBX_TOKEN我们在微信公众平台配置的一个token字符串,主意保密哦!其他三个都是微信服务器发送get请求传过来的参数,我们进行一层sha1加密:
复制代码
public class SignUtil {  
  
    /** 
     * 验证签名 
     * 
     * @param token 微信服务器token,在env.properties文件中配置的和在开发者中心配置的必须一致 
     * @param signature 微信服务器传过来sha1加密的证书签名
     * @param timestamp 时间戳
     * @param nonce 随机数 
     * @return 
     */  
    public static boolean checkSignature(String token,String signature, String timestamp, String nonce) {  
        String[] arr = new String[] { token, timestamp, nonce };  
        // 将token、timestamp、nonce三个参数进行字典序排序  
        Arrays.sort(arr);  
        
        // 将三个参数字符串拼接成一个字符串进行sha1加密  
        String tmpStr = SHA1.encode(arr[0] + arr[1] + arr[2]);  
        
        // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信  
        return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;  
    }  
    
}  
复制代码

SHA1:

复制代码
/** 
 * 微信公众平台(JAVA) SDK 
 * 
 * SHA1算法
 * 
 * @author helijun 2016/06/15 19:49
 */  
public final class SHA1 {  
  
    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',  
                           '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };  
  
    /** 
     * Takes the raw bytes from the digest and formats them correct. 
     * 
     * @param bytes the raw bytes from the digest. 
     * @return the formatted bytes. 
     */  
    private static String getFormattedText(byte[] bytes) {  
        int len = bytes.length;  
        StringBuilder buf = new StringBuilder(len * 2);  
        // 把密文转换成十六进制的字符串形式  
        for (int j = 0; j < len; j++) {  
            buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);  
            buf.append(HEX_DIGITS[bytes[j] & 0x0f]);  
        }  
        return buf.toString();  
    }  
  
    public static String encode(String str) {  
        if (str == null) {  
            return null;  
        }  
        try {  
            MessageDigest messageDigest = MessageDigest.getInstance("SHA1");  
            messageDigest.update(str.getBytes());  
            return getFormattedText(messageDigest.digest());  
        } catch (Exception e) {  
            throw new RuntimeException(e);  
        }  
    }  
}  
复制代码

 

当你在公众平台提交保存,并且看到绿色的提示“接入成功”之后,恭喜你已经完成微信接入。这个过程需要细心一点,注意加密算法里的大小写,如果接入不成功,大多数情况都是加密算法的问题,多检查检查。

    

   4. 实现消息自动回复service

复制代码
/**
     * 处理微信发来的请求
     * 
     * @param request
     * @return
     */
    public String weixinPost(HttpServletRequest request) {
        String respMessage = null;
        try {

            // xml请求解析
            Map<String, String> requestMap = MessageUtil.xmlToMap(request);

            // 发送方帐号(open_id)
            String fromUserName = requestMap.get("FromUserName");
            // 公众帐号
            String toUserName = requestMap.get("ToUserName");
            // 消息类型
            String msgType = requestMap.get("MsgType");
            // 消息内容
            String content = requestMap.get("Content");
            
            LOGGER.info("FromUserName is:" + fromUserName + ", ToUserName is:" + toUserName + ", MsgType is:" + msgType);

            // 文本消息
            if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
                //这里根据关键字执行相应的逻辑,只有你想不到的,没有做不到的
                if(content.equals("xxx")){
                    
                }
                
                //自动回复
                TextMessage text = new TextMessage();
                text.setContent("the text is" + content);
                text.setToUserName(fromUserName);
                text.setFromUserName(toUserName);
                text.setCreateTime(new Date().getTime() + "");
                text.setMsgType(msgType);
                
                respMessage = MessageUtil.textMessageToXml(text);
               
            } /*else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {// 事件推送
                String eventType = requestMap.get("Event");// 事件类型
                
                if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {// 订阅
                    respContent = "欢迎关注xxx公众号!";
                    return MessageResponse.getTextMessage(fromUserName , toUserName , respContent);
                } else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {// 自定义菜单点击事件
                    String eventKey = requestMap.get("EventKey");// 事件KEY值,与创建自定义菜单时指定的KEY值对应
                    logger.info("eventKey is:" +eventKey);
                    return xxx;
                }
            }
            //开启微信声音识别测试 2015-3-30
            else if(msgType.equals("voice"))
            {
                String recvMessage = requestMap.get("Recognition");
                //respContent = "收到的语音解析结果:"+recvMessage;
                if(recvMessage!=null){
                    respContent = TulingApiProcess.getTulingResult(recvMessage);
                }else{
                    respContent = "您说的太模糊了,能不能重新说下呢?";
                }
                return MessageResponse.getTextMessage(fromUserName , toUserName , respContent); 
            }
            //拍照功能
            else if(msgType.equals("pic_sysphoto"))
            {
                
            }
            else
            {
                return MessageResponse.getTextMessage(fromUserName , toUserName , "返回为空"); 
            }*/
            // 事件推送
            else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
                String eventType = requestMap.get("Event");// 事件类型
                // 订阅
                if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {
                    
                    TextMessage text = new TextMessage();
                    text.setContent("欢迎关注,xxx");
                    text.setToUserName(fromUserName);
                    text.setFromUserName(toUserName);
                    text.setCreateTime(new Date().getTime() + "");
                    text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
                    
                    respMessage = MessageUtil.textMessageToXml(text);
                } 
                // TODO 取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息
                else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {// 取消订阅
                    
                    
                } 
                // 自定义菜单点击事件
                else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {
                    String eventKey = requestMap.get("EventKey");// 事件KEY值,与创建自定义菜单时指定的KEY值对应
                    if (eventKey.equals("customer_telephone")) {
                        TextMessage text = new TextMessage();
                        text.setContent("0755-86671980");
                        text.setToUserName(fromUserName);
                        text.setFromUserName(toUserName);
                        text.setCreateTime(new Date().getTime() + "");
                        text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
                        
                        respMessage = MessageUtil.textMessageToXml(text);
                    }
                }
            }
        }
        catch (Exception e) {
            Logger.error("error......")
        }
        return respMessage;
    }
    
复制代码

 

先贴代码如上,大多都有注释,读一遍基本语义也懂了不需要多解释。

 

有一个地方格外需要注意:

上面标红的fromUserName和toUserName刚好相反,这也是坑之一,还记得我当时调了很久,明明都没有问题就是不通,最后把这两个一换消息就收到了!其实回过头想也对,返回给微信服务器这时本身角色就变了,所以发送和接收方也肯定是相反的。

 

    5.MessageUtil

    

复制代码
public class MessageUtil {
    
    /** 
     * 返回消息类型:文本 
     */  
    public static final String RESP_MESSAGE_TYPE_TEXT = "text";  
  
    /** 
     * 返回消息类型:音乐 
     */  
    public static final String RESP_MESSAGE_TYPE_MUSIC = "music";  
  
    /** 
     * 返回消息类型:图文 
     */  
    public static final String RESP_MESSAGE_TYPE_NEWS = "news";  
  
    /** 
     * 请求消息类型:文本 
     */  
    public static final String REQ_MESSAGE_TYPE_TEXT = "text";  
  
    /** 
     * 请求消息类型:图片 
     */  
    public static final String REQ_MESSAGE_TYPE_IMAGE = "image";  
  
    /** 
     * 请求消息类型:链接 
     */  
    public static final String REQ_MESSAGE_TYPE_LINK = "link";  
  
    /** 
     * 请求消息类型:地理位置 
     */  
    public static final String REQ_MESSAGE_TYPE_LOCATION = "location";  
  
    /** 
     * 请求消息类型:音频 
     */  
    public static final String REQ_MESSAGE_TYPE_VOICE = "voice";  
  
    /** 
     * 请求消息类型:推送 
     */  
    public static final String REQ_MESSAGE_TYPE_EVENT = "event";  
  
    /** 
     * 事件类型:subscribe(订阅) 
     */  
    public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";  
  
    /** 
     * 事件类型:unsubscribe(取消订阅) 
     */  
    public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";  
  
    /** 
     * 事件类型:CLICK(自定义菜单点击事件) 
     */  
    public static final String EVENT_TYPE_CLICK = "CLICK";  
}
复制代码

 

这里为了程序可读性、扩展性更好一点,我做了一些封装,定义了几个常量,以及将微信传过来的一些参数封装成java bean持久化对象,核心代码如上。重点讲下xml和map之间的转换

其实这个问题要归咎于微信是用xml通讯,而我们平时一般是用json,所以可能短时间内会有点不适应

1.引入jar包
复制代码
<!-- 解析xml -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.9</version>
        </dependency>
复制代码
2.xml转map集合对象
复制代码
/**
     * xml转换为map
     * @param request
     * @return
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException{
        Map<String, String> map = new HashMap<String, String>();
        SAXReader reader = new SAXReader();
        
        InputStream ins = null;
        try {
            ins = request.getInputStream();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        Document doc = null;
        try {
            doc = reader.read(ins);
            Element root = doc.getRootElement();
            
            List<Element> list = root.elements();
            
            for (Element e : list) {
                map.put(e.getName(), e.getText());
            }
            
            return map;
        } catch (DocumentException e1) {
            e1.printStackTrace();
        }finally{
            ins.close();
        }
        
        return null;
    }
复制代码
3.文本消息对象转换成xml 
复制代码
/** 
     * 文本消息对象转换成xml 
     *  
     * @param textMessage 文本消息对象 
     * @return xml 
     */ 
    public static String textMessageToXml(TextMessage textMessage){
        XStream xstream = new XStream();
        xstream.alias("xml", textMessage.getClass());
        return xstream.toXML(textMessage);
    }
复制代码

 

到此为止已经大功告成了,这个时候可以在公众号里尝试发送“测试”,你会收到微信回复的“the text is 测试”,这也是上面代码里做的回复处理,当然你也可以发挥你的想象用他做所有你想做的事了,比如回复1查天气,2查违章等等....

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java SpringMVC 可以通过以下步骤实现登录功能: 1. 创建一个登录页面,包含用户名和密码输入框以及登录按钮。 2. 在 SpringMVC 中创建一个控制器,用于处理登录请求。 3. 在控制器中,使用 @RequestMapping 注解来映射登录请求的 URL。 4. 在控制器中,使用 @ModelAttribute 注解来获取登录页面提交的用户名和密码。 5. 在控制器中,使用业务逻辑来验证用户名和密码是否正确。 6. 如果验证通过,将用户信息存储到 session 中,并跳转到登录成功页面。 7. 如果验证失败,返回登录页面,并提示用户输入的用户名或密码错误。 以上是 Java SpringMVC 实现登录的基本步骤,具体实现可以根据具体需求进行调整。 ### 回答2: Java SpringMVC 是一个基于Java 的MVC框架,它允许开发者通过使用注释来实现一个模块化的应用程序,以此来简化代码的开发,提高代码的可读性和可维护性,并且提高整个项目的开发效率。 实现登录功能,在Java SpringMVC框架下,需要遵循以下步骤: 1. 建立一个控制器 在控制器中,我们可以定义一个处理器来处理用户请求,比如一个登录请求。在控制器的方法中,我们可以使用@RequestMapping 注解来处理请求,并返回一个视图名称或者一个json字符串。 @Controller public class LoginController { //登录请求处理 @RequestMapping(value="/login", method=RequestMethod.POST) public String login(@RequestParam("username") String userName,@RequestParam("password") String password,Model model) { //处理请求 if(userName.equals("admin") && password.equals("admin")) { //登录成功,返回主页视图名称 return "main"; } else { //登录失败,返回登陆页视图名称 model.addAttribute("fail", "用户名或密码错误"); return "login"; } } } 2. 创建一个视图 在视图中展示用户界面,以引导用户进行登录操作。在视图页面中,我们可以使用form标签来定义一个表单,用于接收用户的登录信息。 <form action="login" method="post" > <label>用户名:</label> <input type="text" name="username"><br/> <label>密码:</label> <input type="password" name="password"><br/> <input type="submit" value="登录" /> </form> 3. 配置SpringMVC 在配置文件中开启SpringMVC的注解驱动,同时配置视图解析器,并引入需要扫描注解的包路径。 <!--开启注解驱动--> <mvc:annotation-driven /> <!--视图解析器--> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> <!--扫描包路径--> <context:component-scan base-package="com.example.controller" /> 4. 运行 运行程序后,在浏览器中访问登录页面,输入正确的用户名和密码即可登录成功或失败。 综上所述,实现登录功能的步骤比较简单,请开发者参考上述步骤进行开发。需要注意的是,实际开发中可能会涉及到更加详细的实现及安全性问题,需要更加细致的控制,开发者需要具备一定的开发经验及相关的知识概念。 ### 回答3: Java Spring MVC 是一种用于开发 Web 应用程序的框架,它提供了一种模型视图控制(MVC)的架构来分离应用程序的组成部分。 Spring MVC 实现登录是指在用户访问 Web 应用程序时需要进行身份验证时,以编程方式验证用户身份并为其提供访问授权。此过程涉及许多不同的组成部分,其中包括 Spring Security 和 Controller 层。 要实现登录,需要在设计时确定验证的方式。可以使用基于表单的验证和基于 HTTP 认证的验证。基于表单的验证需要在表单中获取用户名和密码,然后在后台进行验证。基于 HTTP 认证的验证则需要将用户名和密码附加到 HTTP 请求的请求头中,并将它们发送到后台进行验证。 在 Spring MVC 中,可以使用 Spring Security 框架来处理登录和授权逻辑。 Spring Security 的主要功能是处理身份验证和授权。要使用 Spring Security 实现登录,需要在 Spring 配置文件中对其进行配置,并将其添加到应用程序中。 在 Controller 层中,可以使用 @RequestMapping 注释将 URL 映射到处理请求的方法上。在登录请求中,需要检查传递的用户名和密码是否与用户的实际用户名和密码匹配。如果匹配,则在服务器上创建会话以在用户访问其他页面时跟踪其状态。 如果不匹配,则将用户重定向到登录页面。 在进行身份验证时,还需要考虑安全性问题。必须确保在传递密码时使用安全协议,例如 HTTPS。还需要防止 SQL 注入攻击,可以使用预处理语句来防止此攻击。 在总结方面,Java Spring MVC 实现登录的主要步骤包括:设计验证方式,配置 Spring Security,使用 @RequestMapping 注释将 URL 映射到相应的 Controller 方法,进行身份验证并创建会话,确保安全性。以上述步骤实现登录可以增强应用程序安全性,防止未授权访问和数据泄漏等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值