基于SpringBoot微信公众号的开发

22 篇文章 5 订阅
4 篇文章 0 订阅

一、注册公众号

1、公众号介绍

微信公众号分为服务号、订阅号、企业号;订阅号可以个人申请,服务号和企业号要有企业资质才可以。所以我们这里说的公众号开发指的是订阅号,订阅号每天可群发1条消息;

2、注册微信公众号

注册微信公众号
与注册其他网站大同小异,需要注意的一点是,到了下边这步时选择"订阅号",其他不再详细介绍了(因为太简单了)
在这里插入图片描述
注册后在微信订阅号里就可以搜索到自己的公众号了

3、注册测试公众号

对于开发者而言,个人订阅号有很多接口是没有权限,也就是说个人订阅号无法使用一些高级的权限接口,如生成二维码、网页授权、自定义菜单、微信支付等,但是,为了方便开发者学习,微信公众平台提供了测试公众账号,测试公众号有很多个人订阅号不具备的权限, 测试公众号的注册地址为:
测试公众号注册
注册步骤略过(太简单),注册后得到"测试号信息"如下
在这里插入图片描述

4、内外网穿透

开发基于微信公众号的应用最大的痛苦之处就是调试问题,每次实现一个功能后都需要部署到一个公网服务器进行测试,因为微信用户每次向公众号发起请求时,微信服务器会先接收到用户的请求,然后再转发到我们的服务器上,也就是说,微信服务器是要和我们的服务器进行网络交互,所以我们必须保证我们的服务器外网可以访问到,这种部署到公网服务器进行测试的做法对于我们开发者来说简直是噩梦。所以我们要想一个办法可以做到本地部署、本地调试代码,而要做到这一点,那么我们要解决的问题就是将内网的部署服务器映射到外网,让微信服务器可以正常访问到,幸运的是,借助于第三方软件Ngrok,我们就可以做得到。Ngrok是一个免费的软件Ngrok,使用Ngrok后,我们就可以实现内网穿透,也就是说我们可以将内网的服务器映射到外网给别人访问,这对于我们在本地开发环境中调试微信代码是以及给用户演示一些东西非常快速和有帮助的,因为可以直接使用我们自己的内网的电脑作为服务器,不过Ngrok需要翻墙访问.
不会翻墙的同学在下边做评论,可以获取翻墙资料;
当然,国内也提供了不需要翻墙的技术natapp官网
进入官网主要目的是获取token,步骤如下:
在这里插入图片描述
需要注册账号来获取属于自己的token:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们的目的就是获取这个token
在这里插入图片描述
配置环境变量path:
在这里插入图片描述
双击natapp.exe,输入以下地址:

natapp -authtoken  yourtoken

在这里插入图片描述
回车:
在这里插入图片描述
外网访问上边Forwarding后边的那个地址,就能访问到我们本地的地址了;
至此,内外网穿透完成,后边可以调试我们的公众号代码了;

5、微信公众号接入

官方介绍的步骤
按这里的步骤执行即可,非常简单
在这里插入图片描述
官方文档不明白的可以看下边的步骤
1、登录自己的公众号后台,选择 开发–基本配置
在这里插入图片描述
2、选择 同意 后,点击 成为开发者
在这里插入图片描述
3、启用开发者密码
在这里插入图片描述
4、点击修改配置
在这里插入图片描述
填写下边的信息,写完后点击 提交
在这里插入图片描述
提交时会有下边这些错误:
“系统发生错误,请稍后重试”、“token验证失败”、“请求URL超时”,解决办法看这里
公众号配置时遇到的问题及解决办法

进入测试号里测试公众号,填写下边的信息后点击提交
在这里插入图片描述
提交后向下拉取页面会看到属于自己的测试二维码,扫描关注,这个测试公众号就是你的测试账号了,在这个公众号里发消息的话就会进入我们的后台代码里
在这里插入图片描述
至此,我们的微信公众号已经可以和我们的本地服务器(本地代码)进行交互了;

6、access_token介绍和获取

1、简介

access_token是公众号的全局唯一接口调用凭据,在使用微信公众号各个接口中都需要一个各自的access_token;我们开发人员对这个值需要进行妥善保存;access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时(因为access_token有2个小时的时效性,所以要有一个机制保证最长2个小时重新获取一次),需定时刷新,重复获取将导致上次获取的access_token失效;且所有接口调用每天限制2000次,所以调用接口不能太频繁;

2、获取access_token

根据AppID和AppSecret可获取access_token(顺便将自己的本地服务器ip添加到下边的白名单里)
在这里插入图片描述
然后访问该地址,即可调用接口了

https请求方式: GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

调用结果说明:
在这里插入图片描述
返回40125:官网没有这个错误码的介绍
在这里插入图片描述
解决40125错误码:重置AppSecret即可
在这里插入图片描述
最后返回正确结果:得到了我们想要的access_token值(后边的7200指的是这个access_token有效时间为7200秒,大约1.944小时)
在这里插入图片描述

3、代码里获取access_token

AccessToken 实体类

/**
 * Author: sgw
 * Date 2019/4/5
 * Description:保存微信返回的access_token值
 **/
public class AccessToken {
    /**
     * 获取到的凭证
     */
    private String tokenName;
    /**
     * 凭证有效时间  单位:秒
     */
    private int expireSecond;

    public String getTokenName() {
        return tokenName;
    }

    public void setTokenName(String tokenName) {
        this.tokenName = tokenName;
    }

    public int getExpireSecond() {
        return expireSecond;
    }

    public void setExpireSecond(int expireSecond) {
        this.expireSecond = expireSecond;
    }
}

AccessToken 封装类

/**
 * Author: sgw
 * Date 2019/4/5
 * Description:封装微信返回的access_token值
 **/
public class AccessTokenInfo {
    public static AccessToken accessToken = null;
}

向微信发送请求的工具类

package com.example.maltose.wexin.utils;
import javax.net.ssl.*;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
 * Author: sgw
 * Date 2019/4/5
 * Description:用于发送http请求的工具类(向微信发送http请求,获取access_token)
 **/
public class NetWorkUtil {
    /**
     * 发起HTTPS请求
     * @param reqUrl
     * @param requestMethod 请求方式,传null的话默认是get请求
     * @return 相应字符串
     */
    public String getHttpsResponse(String reqUrl, String requestMethod) {
        URL url;
        InputStream is;
        String result ="";

        try {
            url = new URL(reqUrl);
            HttpsURLConnection con = (HttpsURLConnection) url.openConnection();

            TrustManager[] tm = {xtm};
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(null, tm, null);

            con.setSSLSocketFactory(ctx.getSocketFactory());
            con.setHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String arg0, SSLSession arg1) {
                    return true;
                }
            });
            //允许输入流,即允许下载
            con.setDoInput(true);

            //在android中必须将此项设置为false,允许输出流,即允许上传
            con.setDoOutput(false);
            //不使用缓冲
            con.setUseCaches(false);
            if (null != requestMethod && !requestMethod.equals("")) {
                //使用指定的方式
                con.setRequestMethod(requestMethod);
            } else {
                //使用get请求
                con.setRequestMethod("GET");
            }
            //获取输入流,此时才真正建立链接
            is = con.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader bufferReader = new BufferedReader(isr);
            String inputLine;
            while ((inputLine = bufferReader.readLine()) != null) {
                result += inputLine + "\n";
            }
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    X509TrustManager xtm = new X509TrustManager() {
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        @Override
        public void checkServerTrusted(X509Certificate[] arg0, String arg1)
                throws CertificateException {
        }

        @Override
        public void checkClientTrusted(X509Certificate[] arg0, String arg1)
                throws CertificateException {
        }
    };
}

随系统的启动就启动的类,即系统启动的时候就去获取access_token

package com.example.maltose.wexin.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.maltose.wexin.domain.AccessToken;
import com.example.maltose.wexin.domain.AccessTokenInfo;
import com.example.maltose.wexin.utils.NetWorkUtil;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;



/**
 * Author: sgw
 * Date 2019/4/5
 * Description: 默认启动项目的时候就启动该类,用来向微信后台定期获取access_token值
 * 继承ApplicationRunner接口的话,项目启动时就会执行里边的run方法
 **/

//@Order定义组件加载顺序
@Order(value = 1)
@Component
public class StartService implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("开始获取微信里的access_token");
        /*final String appId = getInitParameter("appId");
        final String appSecret = getInitParameter("appSecret");*/
        final String appId = "wx940842767f562278";
        final String appSecret = "018b2ffb9141c926aedebcd5a23c718f";
        //获取accessToken
        AccessTokenInfo.accessToken = getAccessToken(appId, appSecret);

    }
    private AccessToken getAccessToken(String appId, String appSecret) {
        NetWorkUtil netHelper = new NetWorkUtil();
        /**
         * 接口地址为https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,其中grant_type固定写为client_credential即可。
         */
        String Url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret);
        //此请求为https的get请求,返回的数据格式为{"access_token":"ACCESS_TOKEN","expires_in":7200}
        String result = netHelper.getHttpsResponse(Url, "");
        System.out.println("获取到的access_token="+result);


        //使用FastJson将Json字符串解析成Json对象
        JSONObject jsStr = JSONObject.parseObject(result);
        JSONObject json = JSON.parseObject(result);
        AccessToken token = new AccessToken();
        token.setTokenName(json.getString("access_token"));
        token.setExpireSecond(json.getInteger("expires_in"));
        return token;
    }
}

上边的JSONObject 需要使用阿里的fastJson依赖:

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.4</version>
</dependency>

至此,项目需要的 access_token已经成功获取;

小总结:
当我们电脑关机后,第二天继续开发的话,需要做的前几步工作
1、通过natapp开启本地外网访问的权限
在这里插入图片描述
2、微信公众号测试管理平台与微信后台基本配置里, 修改接口配置信息URL为: natapp生成的新域名/本地项目地址 , 这时就可以进行测试了
在这里插入图片描述

3、启动本地项目,启动项目的同时获取到了access_token值
在这里插入图片描述

4、进入微信测试公众号手机端,发送消息就可以在我们的后台接收到消息了

7、被动发送消息

1、介绍

被动发送消息:即当用户在手机公众号里发送消息的时候,我们要被动的做出响应,就是大家在公众号里发送关键词的时候会得到一些固定的回复;

2、写工具类获取公众号发来的信息并作出响应
package com.example.maltose.wechat.utils;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: sgw
 * @Date 2019/4/6
 * @Description:处理公众号发来的消息(XML格式) 将解析结果存储在HashMap中
 **/
public class MessageHandlerUtils {
    private static Logger logger = LoggerFactory.getLogger(MessageHandlerUtils.class);
    /**
     * 获取微信公众号里发送过来的消息
     * @param request
     * @param response
     * @return
     */
    public static Map<String,String> getMsgFromClient(HttpServletRequest request){
        logger.info("获取输入流,开始处理消息");
        // 将解析结果存储在HashMap中
        Map<String,String> map = new HashMap();
        InputStream inputStream=null;
        try {
            inputStream = request.getInputStream();
            SAXReader reader = new SAXReader();
            Document document = reader.read(inputStream);
            // 得到xml根元素
            Element root = document.getRootElement();
            // 得到根元素的所有子节点
            List<Element> elementList = root.elements();
            // 遍历所有子节点,解析打印微信发来的消息
            for (Element e : elementList) {
                logger.info(e.getName() + "|" + e.getText());
                map.put(e.getName(), e.getText());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            // 释放资源
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return map;
    }
    /**
     * 根据消息类型 构造返回消息
     */
    public static String buildXml(Map<String,String> map) {
        String result;
        String msgType = map.get("MsgType").toString();
        logger.info("消息类型:"+map.get("MsgType").toString());
        if(msgType.toUpperCase().equals("TEXT")){
            result = buildTextMessage(map, "来了老弟?");
        }else{
            String fromUserName = map.get("FromUserName");
            // 开发者微信号
            String toUserName = map.get("ToUserName");
            result = String
                    .format(
                            "<xml>" +
                                    "<ToUserName><![CDATA[%s]]></ToUserName>" +
                                    "<FromUserName><![CDATA[%s]]></FromUserName>" +
                                    "<CreateTime>%s</CreateTime>" +
                                    "<MsgType><![CDATA[text]]></MsgType>" +
                                    "<Content><![CDATA[%s]]></Content>" +
                                    "</xml>",
                            fromUserName, toUserName, getUtcTime(),
                            "请回复如下关键词:\n文本\n图片\n语音\n视频\n音乐\n图文");
        }

        return result;
    }
    /**
     * 构造文本消息
     * @param map
     * @param content
     * @return
     */
    private static String buildTextMessage(Map<String,String> map, String content) {
        //发送方帐号
        String fromUserName = map.get("FromUserName");
        // 开发者微信号
        String toUserName = map.get("ToUserName");
        /**
         * 文本消息XML数据格式
         */
        return String.format(
                "<xml>" +
                        "<ToUserName><![CDATA[%s]]></ToUserName>" +
                        "<FromUserName><![CDATA[%s]]></FromUserName>" +
                        "<CreateTime>%s</CreateTime>" +
                        "<MsgType><![CDATA[text]]></MsgType>" +
                        "<Content><![CDATA[%s]]></Content>" + "</xml>",
                fromUserName, toUserName, getUtcTime(), content);
    }

    /**
     * 获取当前时间
     * @return
     */
    private static String getUtcTime() {
        // 如果不需要格式,可直接用dt,dt就是当前系统时间
        Date dt = new Date();
        // 设置显示格式
        DateFormat df = new SimpleDateFormat("yyyyMMddhhmm");
        String nowTime = df.format(dt);
        long dd = (long) 0;
        try {
            dd = df.parse(nowTime).getTime();
        } catch (Exception e) {

        }
        logger.info("当前时间:"+String.valueOf(dd));
        return String.valueOf(dd);
    }
}
3、在入口类里引入上边的工具库
      try {
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
     
        Map<String, String> map = MessageHandlerUtils.getMsgFromClient(request);
        System.out.println("开始构造消息");
        String result = "";
        result = MessageHandlerUtils.buildXml(map);

        if (result.equals("")) {
            result = "未正确响应";
        }
        try {
            response.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }

完整的入口类:

package com.example.maltose.wechat.controller;

import com.example.maltose.wechat.utils.FileTypeJudge;
import com.example.maltose.wechat.utils.MessageHandlerUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: sgw
 * @Date 2019/4/5
 * @Description: 公众号入口
 **/
@RequestMapping("gzh")
@Controller
public class GzhController {

    /**
     * 切记:这里是自定义的token,需和你微信配置界面提交的token完全一致
     */
    private final String TOKEN = "sgwishandsome";

    static Logger logger = LoggerFactory.getLogger(GzhController.class);

    @RequestMapping("testone")
    public void checkSignature(HttpServletRequest request, HttpServletResponse response) {

        logger.info("校验签名start");
        /**
         * 接收微信服务器发送请求时传递过来的参数
         */
        //签名
        String signature = request.getParameter("signature");
        //时间戳
        String timestamp = request.getParameter("timestamp");
        //随机数
        String nonce = request.getParameter("nonce");
        //随机字符串
        String echostr = request.getParameter("echostr");
        String method = request.getMethod();

        if(method.equals("GET")){
            //get请求,说明是在配置微信后台的url过来的请求
            /**
             * 将token、timestamp、nonce三个参数进行字典序排序
             * 并拼接为一个字符串
             */
            String sortStr = this.sort(TOKEN, timestamp, nonce);
            /**
             * 对排序后的sortStr进行shal加密
             */
            String mySignature = shal(sortStr);


            /**
             * 校验"微信服务器传递过来的签名"和"加密后的字符串"是否一致, 如果一致则签名通过,否则不通过
             * 每次刚启动项目后,把下边的注释打开,与微信基本配置里的URL进行交互
             * 配置完毕后把下边代码注释掉即可
             */
            if (!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)) {

                logger.info("签名校验通过");
                try {
                    //必须响应给微信,不然会提示"token校验失败"
                    if(echostr!=null&&echostr!=""){
                        response.getWriter().write(echostr);
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
                logger.info("校验签名失败");
            }
        }else{
            //post请求,说明是微信公众号里来的请求
            try {
                request.setCharacterEncoding("UTF-8");
                response.setCharacterEncoding("UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            Map<String, String> map = MessageHandlerUtils.getMsgFromClient(request);
            System.out.println("开始构造消息");
            String result = "";
            result = MessageHandlerUtils.buildXml(map);

            if (result.equals("")) {
                result = "未正确响应";
            }
            try {
                response.getWriter().write(result);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //开始解析解析公众号里发来的消息 将解析结果存储在HashMap中


    }

    /**
     * 参数排序
     *
     * @param token
     * @param timestamp
     * @param nonce
     * @return
     */
    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();
    }

    /**
     * 字符串进行shal加密
     *
     * @param str
     * @return
     */
    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 "";
    }
}

如果是首次关注自己的测试公众号,关注的同时会收到这样一条消息:

在这里插入图片描述

在该公众号里发消息会收到如下信息:

在这里插入图片描述
如果第一次关注,后台收到的消息:MsgType为event

ToUserName|gh_44c20ba87500
FromUserName|o6jwg1JwBn48Y6I601axmJKDnsvc
CreateTime|1554532821
MsgType|event
Event|unsubscribe

如果关注后,发送文本消息的话,后台收到的消息:MsgType为text

ToUserName|gh_44c20ba87500
FromUserName|o6jwg1JwBn48Y6I601axmJKDnsvc
CreateTime|1554532840
 MsgType|text
Content|你好啊
MsgId|22255558984717693

如果发图片的话,后台收到的信息:MsgType为image

ToUserName|gh_44c20ba87500
FromUserName|o6jwg1JwBn48Y6I601axmJKDnsvc
CreateTime|1554538246
MsgType|image

如果发语音的话,后台收到的信息:MsgType为voice

ToUserName|gh_44c20ba87500
FromUserName|o6jwg1JwBn48Y6I601axmJKDnsvc
CreateTime|1554533120
MsgType|voice
Format|amr
MsgId|22255564860469296
Recognition|
4、被动返回图片给用户

1、上传图片的工具类

package com.example.maltose.wechat.utils;

import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory;
import org.apache.commons.httpclient.util.HttpURLConnection;

import java.io.*;
import java.net.URL;

/**
 * @Author: sgw
 * Date 2019/4/6
 * Description:上传图片、语音、视频的工具类,用来获取微信返回的mediaId
 **/
public class UploadMediaApiUtils {

    /**
     * token 接口(GET)
     */
    private static final String ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";

    /**
     * 素材上传(POST)URL
     */
    private static final String UPLOAD_MEDIA = "https://api.weixin.qq.com/cgi-bin/media/upload";

    /**
     * 素材下载:不支持视频文件的下载(GET)
     */
    private static final String DOWNLOAD_MEDIA = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s";

    public static String getTokenUrl(String appId, String appSecret) {
        return String.format(ACCESS_TOKEN, appId, appSecret);
    }

    public static String getDownloadUrl(String token, String mediaId) {
        return String.format(DOWNLOAD_MEDIA, token, mediaId);
    }

    /**
     * 通用接口获取token凭证
     * @param appId
     * @param appSecret
     * @return
     */
    public String getAccessToken(String appId, String appSecret) {
        NetWorkUtil netHelper = new NetWorkUtil();
        String Url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret);
        String result = netHelper.getHttpsResponse(Url, "");
        JSONObject json = JSON.parseObject(result);
        return json.getString("access_token");
    }

    /**
     * 素材上传到微信服务器
     * @param file  File file = new File(filePath); // 获取本地文件
     * @param token access_token
     * @param type type只支持四种类型素材(video/image/voice/thumb)
     * @return
     */
    public  JSONObject uploadMedia(File file, String token, String type) {
        if(file == null || token == null || type == null){
            return null;
        }
        if(!file.exists()){
            System.out.println("上传文件不存在,请检查!");
            return null;
        }
        JSONObject jsonObject = null;
        PostMethod post = new PostMethod(UPLOAD_MEDIA);
        post.setRequestHeader("Connection", "Keep-Alive");
        post.setRequestHeader("Cache-Control", "no-cache");
        FilePart media;
        HttpClient httpClient = new HttpClient();
        //信任任何类型的证书
        Protocol myhttps = new Protocol("https", new SSLProtocolSocketFactory(), 443);
        Protocol.registerProtocol("https", myhttps);

        try {
            media = new FilePart("media", file);
            Part[] parts = new Part[]{
                    new StringPart("access_token", token),
                    new StringPart("type", type),
                    media
            };
            MultipartRequestEntity entity = new MultipartRequestEntity(parts,post.getParams());
            post.setRequestEntity(entity);
            int status = httpClient.executeMethod(post);
            if (status == HttpStatus.SC_OK) {
                String text = post.getResponseBodyAsString();
                jsonObject = JSONObject.parseObject(text);
            } else {
                System.out.println("upload Media failure status is:" + status);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (HttpException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return jsonObject;
    }
    public static File downloadMedia(String fileName, String token, String mediaId) {
        String path = getDownloadUrl(token, mediaId);
        //return httpRequestToFile(fileName, url, "GET", null);

        if (fileName == null || path == null) {
            return null;
        }
        File file = null;
        HttpURLConnection conn = null;
        InputStream inputStream = null;
        FileOutputStream fileOut = null;
        try {
            URL url = new URL(path);
            conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestMethod("GET");

            inputStream = conn.getInputStream();
            if (inputStream != null) {
                file = new File(fileName);
            } else {
                return file;
            }

            //写入到文件
            fileOut = new FileOutputStream(file);
            if (fileOut != null) {
                int c = inputStream.read();
                while (c != -1) {
                    fileOut.write(c);
                    c = inputStream.read();
                }
            }
        } catch (Exception e) {
        } finally {
            if (conn != null) {
                conn.disconnect();
            }

            try {
                inputStream.close();
                fileOut.close();
            } catch (IOException execption) {
            }
        }
        return file;
    }
}

2、上传图片到微信服务器

    @RequestMapping("upload")
    public void uploadImg(HttpServletRequest request, HttpServletResponse response) {
        UploadMediaApiUtils uploadMediaApiUtil = new UploadMediaApiUtils();
        //这里写成大家自己的appID与appSecret,不要写我这个哈
        String appId = "wx94084342767f342562278";
        String appSecret = "018b2ffb9141c926547gebcd5a23c18f";
        String accessToken = uploadMediaApiUtil.getAccessToken(appId,appSecret);

        String filePath = "F:aa.jpg";
        File file = new File(filePath);
        String type = "IMAGE";
        JSONObject jsonObject = uploadMediaApiUtil.uploadMedia(file,accessToken,type);
        System.out.println("json值:"+jsonObject.toString());
    }

上传成功后微信会返回我们需要的media_id:
在这里插入图片描述
在消息处理类MessageHandlerUtils.java里编写返回图片的逻辑
在这里插入图片描述

/**
     * 返回图片给用户
     * @param map
     * @return
     */
    private static String buildImageMessage(Map<String, String> map) {
        String fromUserName = map.get("FromUserName");
        String toUserName = map.get("ToUserName");
        /*返回指定的图片,这个media_id就是上传的时候返回的值,这里如果要给用户返回固定的图片就按下边的这样写就行
        //String media_id = "4wAKxILRJ0tM2gyHT38xOTzjJeLz8AoJQapuEzh2tgcWWpeAJZ3aMZwKo7ULIg";

        /*返回用户发过来的图片*/
        String media_id = map.get("MediaId");

        return String.format(
                "<xml>" +
                        "<ToUserName><![CDATA[%s]]></ToUserName>" +
                        "<FromUserName><![CDATA[%s]]></FromUserName>" +
                        "<CreateTime>%s</CreateTime>" +
                        "<MsgType><![CDATA[image]]></MsgType>" +
                        "<Image>" +
                        "   <MediaId><![CDATA[%s]]></MediaId>" +
                        "</Image>" +
                        "</xml>",
                fromUserName, toUserName, getUtcTime(), media_id
        );
    }

返回音频给用户

 /**
     * 返回语音消息给公众号
     * @param map
     * @return iPB5f0tt78B8fKME0gN48eSgfZ4pDt8Y-8HxNd8SmVg4NoHqxux3tnThsh8rVn9h
     */
    private static String sendVoiceMessage(Map<String, String> map) {
        String fromUserName = map.get("FromUserName");
        String toUserName = map.get("ToUserName");
        /*返回用户发过来的语音*/
       // String media_id = map.get("MediaId");
        String media_id = "1jmI3WfxvrWHwiX8Y4h8XvcWvMhxhgiFClo7Z_KBAZMgclAU8gRwQuKfKVNkVGfd";
        return String.format(
                "<xml>" +
                        "<ToUserName><![CDATA[%s]]></ToUserName>" +
                        "<FromUserName><![CDATA[%s]]></FromUserName>" +
                        "<CreateTime>%s</CreateTime>" +
                        "<MsgType><![CDATA[voice]]></MsgType>" +
                        "<Voice>" +
                        "   <MediaId><![CDATA[%s]]></MediaId>" +
                        "</Voice>" +
                        "</xml>",
                fromUserName,toUserName, getUtcTime(),media_id
        );
    }

返回视频给用户

 /**
     * 返回视频消息
     * @param map
     * @return
     */
    private static String sendVideoMessage(Map<String, String> map) {
        String fromUserName = map.get("FromUserName");
        String toUserName = map.get("ToUserName");
        String title = "老弟发来视频了哈";
        String description = "小老弟挺能玩儿呀";
        //返回用户发过来的视频,这种方式目前这样写不支持,后续再研究
        //String media_id = map.get("MediaId");
        String media_id = "D9ii_yDGFrKrkcf_VBoTOzC7pgDIGmuKOnMYYJbtW0S4JSIta5kS0LShBcxNy_k2";
        return String.format(
                "<xml>" +
                        "<ToUserName><![CDATA[%s]]></ToUserName>" +
                        "<FromUserName><![CDATA[%s]]></FromUserName>" +
                        "<CreateTime>%s</CreateTime>" +
                        "<MsgType><![CDATA[video]]></MsgType>" +
                        "<Video>" +
                        "   <MediaId><![CDATA[%s]]></MediaId>" +
                        "   <Title><![CDATA[%s]]></Title>" +
                        "   <Description><![CDATA[%s]]></Description>" +
                        "</Video>" +
                        "</xml>",
                fromUserName,toUserName, getUtcTime(),media_id,title,description
        );
    }

返回在线音乐给用户:

 /**
     * 返回音乐消息
     * 微信接收不到公众号传来的MP3文件,提示不支持该类型
     * 但是我们可以返回MP3格式的文件给客户
     * @param map
     * @return
     */
    private static String sendMusicMessage(Map<String, String> map) {
        String fromUserName = map.get("FromUserName");
        String toUserName = map.get("ToUserName");
        String title = "music";
        String description = "good music";
        String hqMusicUrl ="http://www.kugou.com/song/20qzz4f.html?frombaidu#hash=20C16B9CCCCF851D1D23AF52DD963986&album_id=0";
        return String.format(
                "<xml>" +
                        "<ToUserName><![CDATA[%s]]></ToUserName>" +
                        "<FromUserName><![CDATA[%s]]></FromUserName>" +
                        "<CreateTime>%s</CreateTime>" +
                        "<MsgType><![CDATA[music]]></MsgType>" +
                        "<Music>" +
                        "   <Title><![CDATA[%s]]></Title>" +
                        "   <Description><![CDATA[%s]]></Description>" +
                        "   <MusicUrl>< ![CDATA[%s] ]></MusicUrl>" +  //非必须项 音乐链接
                        "   <HQMusicUrl><![CDATA[%s]]></HQMusicUrl>"+ //非必须项 高质量音乐链接,WIFI环境优先使用该链接播放音乐
                        "</Music>" +
                        "</xml>",
                fromUserName,toUserName, getUtcTime(),title,description,hqMusicUrl,hqMusicUrl
        );
    }

返回图文消息(网页链接)

 /**
     * 返回图文消息
     * @param map
     * @return
     */
    private static String buildNewsMessage(Map<String, String> map) {
        String fromUserName = map.get("FromUserName");
        String toUserName = map.get("ToUserName");
        String title1 = "centos7.0下安装配置redis5.0的详细步骤";
        String description1 = "redis最新发布的5.0版本,变化较大,这里做一下安装配置的最新总结";
        String picUrl1 ="https://img-blog.csdnimg.cn/20190331101636249.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNDE3MzIx,size_16,color_FFFFFF,t_70";
        String textUrl1 = "https://blog.csdn.net/qq_33417321/article/details/88924934";

        String title2 = "SpringBoot从入门到放弃";
        String description2 = "SpringBoot与SpringCloud已经非常成熟了,使用率也在逐年攀升";
        String picUrl2 ="https://img-blog.csdn.net/20181019204349772?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNDE3MzIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70";
        String textUrl2 = "https://blog.csdn.net/qq_33417321/article/details/83098210";

        return String.format(
                "<xml>" +
                "<ToUserName><![CDATA[%s]]></ToUserName>" +
                "<FromUserName><![CDATA[%s]]></FromUserName>" +
                "<CreateTime>%s</CreateTime>" +
                "<MsgType><![CDATA[news]]></MsgType>" +
                "<ArticleCount>2</ArticleCount>" + //图文消息个数,限制为8条以内
                "<Articles>" + //多条图文消息信息,默认第一个item为大图,注意,如果图文数超过8,则将会无响应
                    "<item>" +
                        "<Title><![CDATA[%s]]></Title> " +
                        "<Description><![CDATA[%s]]></Description>" +
                        "<PicUrl><![CDATA[%s]]></PicUrl>" + //图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200
                        "<Url><![CDATA[%s]]></Url>" + //点击图文消息跳转链接
                    "</item>" +
                        "<item>" +
                        "<Title><![CDATA[%s]]></Title>" +
                        "<Description><![CDATA[%s]]></Description>" +
                        "<PicUrl><![CDATA[%s]]]></PicUrl>" +
                        "<Url><![CDATA[%s]]]></Url>" +
                    "</item>" +
                "</Articles>" +
                "</xml>"

                ,
                fromUserName,toUserName, getUtcTime(),
                title1,description1,picUrl1,textUrl1,
                title2,description2,picUrl2,textUrl2
        );
    }

上传音频、视频、图片的代码

 @RequestMapping("upload")
    public void uploadImg(HttpServletRequest request, HttpServletResponse response) {
        UploadMediaApiUtils uploadMediaApiUtil = new UploadMediaApiUtils();
        String appId = "wxa41292f8f201fac3";
        String appSecret = "f8ad19d4d51d973d6a065a8a4796d281";
        String accessToken = uploadMediaApiUtil.getAccessToken(appId,appSecret);

        //上传图片
        /*String filePath = "F:aa.jpg";
        String type = "IMAGE";*/
        
        //上传视频
       /* String filePath = "F:bb.mp4";
        String type = "VIDEO";*/
       
       //上传音频
        String filePath = "F:gg.mp3";
        String type = "VOICE";
        
        File file = new File(filePath);
        JSONObject jsonObject = uploadMediaApiUtil.uploadMedia(file,accessToken,type);
        System.out.println("json值:"+jsonObject.toString());
    }

返回emoji表情:

package com.example.maltose.wechat.utils;

import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * Author: sgw
 * Date 2019/4/6
 * Description:表情处理
 **/
public class EmojiUtils {
    /**
     * 显示不可见字符的Unicode
     *
     * @param input
     * @return
     */
    public static String escapeUnicode(String input) {
        StringBuilder sb = new StringBuilder(input.length());
        @SuppressWarnings("resource")
        Formatter format = new Formatter(sb);
        for (char c : input.toCharArray()) {
            if (c < 128) {
                sb.append(c);
            } else {
                format.format("\\u%04x", (int) c);
            }
        }
        return sb.toString();
    }

    /**
     * 将emoji替换为unicode
     * @param source
     * @return
     */
    public  String filterEmoji(String source) {
        if (source != null) {
            Pattern emoji = Pattern.compile("[\ue000-\uefff]", Pattern.CASE_INSENSITIVE);
            Matcher emojiMatcher = emoji.matcher(source);
            Map<String, String> tmpMap = new HashMap<>();
            while (emojiMatcher.find()) {
                String key = emojiMatcher.group();
                String value = escapeUnicode(emojiMatcher.group());
                tmpMap.put(key, value);
            }
            if (!tmpMap.isEmpty()) {
                for (Map.Entry<String, String> entry : tmpMap.entrySet()) {
                    String key = entry.getKey().toString();
                    String value = entry.getValue().toString();
                    source = source.replace(key, value);
                }
            }
        }
        return source;
    }
}

想要知道下边的第81行,各个表情的字符怎么写,可以使用微信发一个表情到后台,后台获取到的Content值就是表情对应的字符
在这里插入图片描述

Spring Boot 是一种用于开发 Java 应用程序的框架,它简化了传统 Java 开发的繁琐过程,使开发人员可以更快速地构建高效的应用程序。UniApp 是一个跨平台的开发框架,它可以同时开发 Android、iOS 和 Web 应用程序,极大地提高了开发效率和项目的可维护性。 微信公众号开发是指基于微信平台的应用程序开发,通过微信公众号,我们可以实现与用户的互动交流、推送消息、提供各种服务等。 在使用 Spring Boot 和 UniApp 进行微信公众号开发时,可以采用前后端分离的开发模式。前端使用 UniApp 进行界面设计和用户交互的开发,后端使用 Spring Boot 进行业务逻辑的处理和数据的存储。 首先,我们需要在微信公众平台注册一个开发者账号,获取到相应的公众号信息和接口权限。 接下来,前端开发人员可以使用 UniApp 进行公众号的界面设计和交互逻辑的编写。UniApp 提供了丰富的组件和模板,可以方便地实现各种界面效果,并且可以使用 Vue.js 进行数据的绑定与处理。 后端开发人员使用 Spring Boot 进行接口的开发和业务逻辑的处理。可以使用 Spring Boot 提供的丰富的功能和插件来简化开发,比如使用 Spring Data JPA 来操作数据库,使用 Spring Security 来实现用户认证与权限控制等。 最后,前后端通过接口进行数据的传输和交互,前端将用户的操作发送到后端进行处理,并将后端返回的数据展示给用户。 通过采用 Spring Boot 和 UniApp 进行微信公众号开发,可以充分发挥两者的优势,快速构建高效的应用程序,实现与用户的互动和服务。同时,由于使用的是跨平台的开发框架,可以方便地同时开发多个平台的应用程序,提高开发效率和项目的可维护性。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

麦芽糖0219

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值