微信公众号_微信公众号开发 - CSDN
微信公众号 订阅
微信公众号是开发者或商家在微信公众平台上申请的应用账号,该帐号与QQ账号互通,通过公众号,商家可在微信平台上实现和特定群体的文字、图片、语音、视频的全方位沟通、互动 。形成了一种主流的线上线下微信互动营销方式。2016年1月18日,腾讯在北京发布消息称,中国政务微信公号已逾10万。2018年4月,腾讯已查处9.9万多个违规公众号 [1]  。11月16日,微信公众平台发布公告称,个人注册公众号数量上限调整为1个 [2]  。2019年12月26日,微信公众号文章无法打开,点击文章链接后显示空白。随后,微信团队道歉,公众号后台大部分功能已经修复。 [3-4] 展开全文
微信公众号是开发者或商家在微信公众平台上申请的应用账号,该帐号与QQ账号互通,通过公众号,商家可在微信平台上实现和特定群体的文字、图片、语音、视频的全方位沟通、互动 。形成了一种主流的线上线下微信互动营销方式。2016年1月18日,腾讯在北京发布消息称,中国政务微信公号已逾10万。2018年4月,腾讯已查处9.9万多个违规公众号 [1]  。11月16日,微信公众平台发布公告称,个人注册公众号数量上限调整为1个 [2]  。2019年12月26日,微信公众号文章无法打开,点击文章链接后显示空白。随后,微信团队道歉,公众号后台大部分功能已经修复。 [3-4]
信息
中文名
微信公众号
认证收费
服务号、订阅号认证均需300元/年
隶属企业
腾讯公司
外文名
Wechat Official Account
类    型
开放应用平台
分    类
订阅号和服务号
微信公众号发展历程
2018年6月27日,微信官方宣布,微信公众平台上线开放转载功能,文章可以直接被转载,不需要人工再次确认。 [5]  2018年11月16日,微信公众平台发布公告称,即日起,公众号注册将做调整:个人主体注册公众号数量上限由2个调整为1个;企业类主体注册公众号数量上限由5个调整为2个。 [2]  2018年12月29日,微信公众平台运营功能再次升级,修改已发送文章的错别字上限由5个上调至10个,同时支持增、删和替换,但标题和摘要依然无法修改,修改机会依然有且仅有一次。 [6]  2019年8月,滴滴出行宣布,与万达酒店及度假村达成战略合作。用户可在万达酒店微信公众号内使用滴滴叫车。 [7]  2020年6月29日,微信公众号增加两项新功能,在文章底部新增了“分享”和“赞”。其中,用户点击“分享”可“分享到朋友圈”或“发送给朋友”。这两项新功能已陆续全量开放。 [8] 
收起全文
  • 课程以微信公众号开发视角,讲解JAVA开发微信公众号所需的框架、第三方工具。 购买套餐还赠送经典微信开发课程——[微信公众号_独立知识点]环境搭建。该课程针对各种复杂的网络环境,讲解如何构建开发环境,已解决...
  • 微信公众号+小程序开发》是结合了部分《微信开发深度解析》内容录制的系列课程,独立于书本,包含了更多的操作演示和案例演示。 课程内容分为两大部分: 第一部分:微信公众号及小程序开发基础技能 第二部分:...
  • 通过热门框架spring boot来学习微信公众号开发,帮助同学们快速入门微信公众号开发。
  • 看完昨天的文章「开发微信公众号(一)」 估计不少人已经开始期待我继续更新了,这不赶紧马不停蹄,加班加点给你们更新了第二篇。现在的你已经有了一个属于自己的微信公众号,那么你想如果按照微信公众号的自动回复...


                            


    看完昨天的文章「开发微信公众号(一)」 估计不少人已经开始期待我继续更新了,这不赶紧马不停蹄,加班加点给你们更新了第二篇。


    现在的你已经有了一个属于自己的微信公众号,那么你想如果按照微信公众号的自动回复,来实现用户发送电影名就可以获得想要的电影链接,那是得设置多少的呀,这显然是不可能的,那么我们就要用到服务器来帮我们实现了的,腾讯,阿里云的都可以,当然如果你是学生那么就可以享有购买服务器时的福利了的,有10元/月支持学生购买服务器的,个人感觉还是蛮划算的。那么问题又来了,不是学生党购买似乎会觉得有点贵,这里我直接给大家一个福利,关注我的微信公众号,回复「令牌」,不知道令牌是什么?没关系,先接着往下看的!我直接把我的令牌给大家用,直到服务器崩掉,先到先得,崩了之后我再修改令牌! 但是你如果想做好一个项目的有收益的话,肯定是要付出的,就像你加入张哥的星球也是通过付费获得知识,这是同样的一个道理,就算之后可能会失败,没有收益,但是你收获到了技术上的知识,我想这也是不错的! 


    购买服务器(我以腾讯服务器作为例子)


    1、百度搜索「腾讯云」,学生党百度搜索「腾讯云校园服务」进入腾讯云网站首页


                        


    2、点击--产品--计算--云服务器


                                


    3、进入云服务器选购界面点击--立即购买,如果没有登录的话需要登录,自己的QQ号就可以;


                                


    5、选择需要的配置,提交订单支付即可

    一定要选择选择 Ubuntn Serve !

    一定要选择选择 Ubuntn   Serve !

    一定要选择选择 Ubuntn   Serve !


                            


    重要的事情说三遍,使用 Ubuntn 会比 window 方便太多了的,而且我也是用的Ubuntn,不怎么清楚其他的服务器,如果不知道用没关系,我会详细描述好操作的,而且作为一位优秀的程序员,你迟早的接触到 Linux ,没有图形界面的,这个时候了解一下,绝对是没有坏处的,只会让你变的更优秀!


    接口微信公众号如果你有了自己的服务器之后呢,你需要你的服务器与微信之间产生联系的,这里我们需要用到别人已经开发好的微信公众号开发框架  WeRoBot 建议看到这里了,去百度一下 WeRoBot 官方文档了解,否则可能会有点不知道什么意思!如果现在的你还处于迷茫的状态,也请硬着头皮操作下去,登录进入你的服务器, 现在我们先直接部署好,让你看到效果再解释原因的,看到现在的不懂 Ubuntn 下的 Vim 操作,可以现在就学下的,简单的基本操作,现在请执行好以下命令:


    vim rebot.py     //创建一个rebot.py 

    键盘按下 i      //切换到 vim 的输入模式   复制以下代码   


    import werobot

    robot = werobot.WeRoBot(token='tokenhere') 

    // tokenhere 这里自己可以随便填写的,比如 token = 'aaa'


    @robot.handler

    def hello(message):

        return 'Hello World!'


    # 让服务器监听在 0.0.0.0:80


    robot.config['HOST'] = '0.0.0.0'

    robot.config['PORT'] = 80

    robot.run()


    按下 Esc    然后按下  Shift + :      

      

    //切换到 Vim 下的命令模式 


    输入 wq                     

                     

    //保存并退出


    sudo python3 rebot.py       

       

    //运行程序rebot 接下来不用动了的,按下 Ctr + C 可以取消,但是现在不要动


                            


                           


    回到微信公众号的后台了,基本配置里面,将你的服务器IP 与令牌(token)

    就是你之前代码里写的那个 token ,填入微信公众号后台,消息加密,点随机生成,明文模式,点击启用,OK 到这里应该就是部署好了的,发送消息,微信就会自动回复hello world 是不是特别的激动,没错到了这里就说明接口配置已经成功完成好了的!甚至你还可以回复图文消息,将代码改成这样!相信某些聪明的人大概已经知道实现原理是什么了吧!


    import werobot

    robot = werobot.WeRoBot(token='tokenhere')

    @robot.text

    def articles(message):

        return [

            [

                "title",

                "description",

                "img",

                "url"

            ],

            [

                "whtsky",

                "I wrote WeRoBot",

                "https://secure.gravatar.com/avatar/0024710771815ef9b74881ab21ba4173?s=420",

                "http://whouz.com/"

            ]

        ]

    robot.run()





    这些代码在 Werobot 都可以看得到,所以要多看文档的,如果已经配置成功了的,可以留言扣个1 ,如果存在疑惑,不懂,请多看微信的官方文档以及 Werobot 的官方文档,多看官方文档是最好的学习方法,或者直接私聊我!!

    有人说:我把服务器退出了就实现不了呀,那么 Linux 的强大就体现出来了


    sudo nohup python3 rebot.py &


    一条命令,这样退出服务器,后台也是可以运行的!

    最后,你需要学什么:Vim 的基本操作,Ubuntn 的基本操作,以及 Werobot!终于完成好了服务器接口微信公众号后台了的,下次将会介绍如何建立好自己的数据库!




    推荐阅读:  开发微信公众号(一)



    PS:如果觉得文章还不错的话,还请大家点赞分享下。算是对「fightjiang」最大的支持!



           每 天 分 享 Python 干 货

         

    展开全文
  • 微信公众号使用教程

    2018-11-27 16:29:08
    微信公众号使用,是一套基础课程,为后面的《PHP微信公众号开发》做铺垫。微信几乎是智能手机上必装的软件,很多的商家在微信上开通公众号,想要把公众号的价值发挥大,必须学会公众号的使用,开发和营销。这一套...
  • 项目实现地址。查看我的[Github地址]... 目前具体可获取指标(包含但不局限):阅读数、点赞数(在看)、评论内容及总数、正文内容及图片、是否为头条、是否为原创。

    项目实现地址。查看我的Github地址

    一个公众号数据暂定10元起,量大优惠;另亦可爬取文章具体内容整理成完整PDF。

    目前具体可获取指标(包含但不局限):阅读数、点赞数(在看)、评论内容及总数、正文内容及图片、是否为头条、是否为原创。
    在这里插入图片描述

    项目已经实现,比本文介绍的更加方便简单,可直接调用。

    关于批量关注公众号的问题已解决,见我的另一篇csdn文章:自动批量关注微信公众号(非逆向)

    不求完美,只求能够用。。。

    截至2019年4月项目可正常运行, 方法已更新。

    3月1号更新:

    1. 获取阅读点赞时,每篇文章间隔10s 一次性可获取500篇文章以上
    2. 从公众号获取永久链接时,间隔3分钟,可以连续获取几小时(网友测试)

    公开已爬取的公众号历史文章的永久链接,数据上传至GitHub,日期均截止commit时间。

    需求

    某某微信公众号历史的所有文章的阅读数和点赞数

    难点

    1. 微信公众号历史的所有文章(来源???)
    2. 每篇文章的阅读量和点赞量(电脑上浏览文章只显示内容,没有阅读量、点赞量、评论……)

    突破难点一

    1. 搜狗微信搜索,可以搜索微信公众号文章。但是貌似只能显示该公众号最近十篇的文章。放弃……
    2. 利用抓包工具(Fiddler),抓取文章。成本有点大……,且貌似只能抓取原创文章。不符合个人需求,放弃……
    3. 利用微信个人订阅号进行爬取,神奇的操作。

    操作

    1. 拥有一个微信个人订阅号,附上登陆和注册链接。微信公众平台

    2. 好在之前无聊注册过一次,所以就可以直接登陆操作。没有注册的童鞋可以用自己的微信号注册一下,过程十分简单,在此就不赘述了

    3. 登陆之后,点击左侧菜单栏“管理”-“素材管理”。再点击右边的“新建图文素材”

    pic1

    1. ​弹出一个新的标签页,在上面的工具栏找到“超链接”并点击
      pic2

    2. 弹出了一个小窗口,选择“查找文章”,输入需要查找的公众号,这里用“科技美学”公众号作为例子
      pic3

    3. 点击之后,可以弹出该公众号的所有历史文章

    pic4

    代码实现

    # -*- coding: utf-8 -*-
    import requests
    import time
    import json
    
    
    # 目标url
    url = "https://mp.weixin.qq.com/cgi-bin/appmsg"
    
    # 使用Cookie,跳过登陆操作
    headers = {
      "Cookie": yourcookie,
      "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
    }
    
    """
    需要提交的data
    以下个别字段是否一定需要还未验证。
    注意修改yourtoken,number
    number表示从第number页开始爬取,为5的倍数,从0开始。如0、5、10……
    token可以使用Chrome自带的工具进行获取
    fakeid是公众号独一无二的一个id,等同于后面的__biz
    """
    data = {
        "token": yourtoken,
        "lang": "zh_CN",
        "f": "json",
        "ajax": "1",
        "action": "list_ex",
        "begin": number,
        "count": "5",
        "query": "",
        "fakeid": yourfakeid,
        "type": "9",
    }
    
    # 使用get方法进行提交
    content_json = requests.get(url, headers=headers, params=data).json()
    # 返回了一个json,里面是每一页的数据
    for item in content_json["app_msg_list"]:
        # 提取每页文章的标题及对应的url
        print(item["title"], "url": item["link"])
    

    以上,即可爬取微信公众号的一页数据,如果是爬取所有页的数据,则需要改变number进行爬取。

    注:每次抓取完一页之后,最好设定time.sleep(3)。过快会导致爬取失败

    突破难点二

    这里我使用的方法是在电脑上登陆微信客户端,进行抓包分析。从客户端看推文可以看到阅读量、点赞量。

    我使用的是Fiddler。Fiddller具体使用就不赘述了。下面直接演示操作

    操作

    1. 打开fiddler开始监控
    2. 登陆微信客户端,浏览该公众号的任意一篇推文
    3. 可以观察到这里的内容显示会有阅读量、点赞量、评论等
    4. 观察fiddler的监控数据,如下图显示

    pic5
    5. 其中/mp/getappmgsext?...是我们推文内容的url,双击之后,fiddler界面右边出现如下图数据

    pic6
    6. 上图下侧的json里面的read_numlike_num分别是阅读量和点赞量

    ####代码实现

    import time
    import requests
    import json
    
    
    # 目标url
    url = "http://mp.weixin.qq.com/mp/getappmsgext"
    # 添加Cookie避免登陆操作,这里的"User-Agent"最好为手机浏览器的标识
    headers = {
        "Cookie": yourcookie,
        "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 MicroMessenger/6.5.2.501 NetType/WIFI WindowsWechat QBCore/3.43.27.400 QQBrowser/9.0.2524.400"
    }
    
    
    data = {
        "is_only_read": "1",
        "is_temp_url": "0",                
        "appmsg_type": "9", # 新参数,不加入无法获取like_num
    }
    """
    添加请求参数
    __biz对应公众号的信息,唯一
    mid、sn、idx分别对应每篇文章的url的信息,需要从url中进行提取
    key、appmsg_token从fiddler上复制即可
    pass_ticket对应的文章的信息,貌似影响不大,也可以直接从fiddler复制
    """
    params = {
        "__biz": your__biz,
        "mid": article_mid,
        "sn": article_sn,
        "idx": article_idx,
        "key": yourkey,
        "pass_ticket": pass_ticket,
        "appmsg_token": yourappmsg_token,
    }
    # 使用post方法进行提交
    content = requests.post(url, headers=headers, data=data, params=params).json()
    
    # 由于上面这种方法可能会获取数据失败,可以采取字符串拼接这种方法
    origin_url = "https://mp.weixin.qq.com/mp/getappmsgext?"
    appmsgext_url = origin_url + "__biz={}&mid={}&sn={}&idx={}&appmsg_token={}&x5=1".format(your__biz, article_mid, article_sn, article_idx, yourappmsg_token)
    content = requests.post(appmsgext_url, headers=headers, data=data).json()
    
    # 提取其中的阅读数和点赞数
    print(content["appmsgstat"]["read_num"], content["appmsgstat"]["like_num"])
    

    以上即可获取到一篇文章的阅读量、点赞量。
    Cookiereq_idpass_ticketkeyappmsg_token__biz利用fiddler获取
    如果是需要多篇文章,需要更改的参数midsnidx
    如果是不同公众号,就需要根据url修改__biz
    多篇文章的爬取,需要根据之前爬取到的url进行提取关键信息,分别爬取。
    注:每次抓取完一页之后,最好设定time.sleep(3)。过快会导致爬取失败。

    写在最后

    以上就是这次微信爬虫的经历。

    需要掌握的基本技能:

    1. python爬虫的语法
    2. Chrome、Fiddler基本使用
    3. 网络爬虫基本常识

    缺陷:

    1. 使用Cookie登陆,未实现自动登陆
    2. key、appmsg_token一些关键参数需要进行手动获取
    3. 实际运行之后,就算设定了爬取间隙时间,还是会被封禁(获取链接时)。

    说明:

    1. 网上一些说法,key半小时过期,我好像没有遇到。
    2. 代码中若有细节问题,欢迎指出讨论

    Github上已经实现了第1、2点,欢迎回到文章开头看github上的实现过程。

    关于被封禁的问题,已有两个解决方案,均放在github上,在这不做讲解。

    1. selenium解决方案,提高约十倍抓取量(不确定)。优点:提高抓取量;缺点:速度慢,不一定能完全抓取完整,抓取量不确定。test_seleinum.py
    2. 利用个人微信号的查看公众号历史消息,抓取量在500条以上,具体未测试。优点:抓取量最多的方案;缺点:短时间内(5-10分钟)无法查看历史消息,或者说无法持续抓取;不保证微信号会被封号。test_GetUrls.py
    展开全文
  • 前往微信公众平台(https://mp.weixin.qq.com/)获取开发权限和开发账号,公众号分为好几种,小程序,订阅号,服务号,企业号,个人只能用订阅号,权限比较少(api接口权限); 二 想要自定义模块功能或者是回复...

    一 

    前往微信公众平台(https://mp.weixin.qq.com/)获取开发权限和开发账号,公众号分为好几种,小程序,订阅号,服务号,企业号,个人只能用订阅号,权限比较少(api接口权限);

    想要自定义模块功能或者是回复信息啥的,就得有一个自己的后台来写接口,所以要配置自己的服务器

    基本配置:服务器配置(首页-开发-基本配置-服务器配置)

    进入修改配置,这里的服务器地址必须是外网(没有服务器的小伙伴如果这里分不清。。外网特征就是别人可以通过你的本机ip直接访问到,在简单点就是,你如果使用WiFi连接的网络那就肯定是内网或者说是局域网,查看方式,window键加R键,输入cmd回车,然后输入ipconfig 查看你当前的ip,然后百度本机ip,不一样的话就不是外网,解决办法是使用natapp进行内网穿透或者是用路由器进行端口映射,将服务器的某个端口映射出去,在最后会有详细步骤)而且是80端口,格式为ip/域名+wx(接口名 ,固定,但是前面可以加路由比如ip/home/wx)Token随便写个;

    然后是后台不多说先上代码:

    /// <summary>
    /// 配合微信服务器验证域名或者是ip的可用行或者是获取消息的推送--pxj
    /// </summary>
    [AtSkipAuthorize]
    public void wx()
    {
    	//获取消息推送,或者是事件推送,类型是xml文件流
    	if (Request.RequestType == "POST")
    	{
    
    	}
    	else
    	{
    		#region 验证请求来源是否是微信
    		string signature = Request["signature"].ToString();
    		string timestamp = Request["timestamp"].ToString();
    
    		string nonce = Request["nonce"].ToString();
    		string echostr = Request["echostr"].ToString();
    		string token = "hellowb";
    		List<string> list = new List<string>() { token, timestamp, nonce };
    		list.Sort();
    		string data = string.Join("", list);
    		byte[] temp1 = Encoding.UTF8.GetBytes(data);
    		SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider();
    		byte[] temp2 = sha.ComputeHash(temp1);
    
    		var hashCode = BitConverter.ToString(temp2);
    		hashCode = hashCode.Replace("-", "").ToLower();
    
    		if (hashCode == signature)
    		{
    			Response.Write(echostr);
    			Response.End();
    		}
    		LogHelper.WriteLog("signature:" + signature + " timestamp:" + timestamp + " nonce:" + nonce + " echostr:" + echostr);
    		#endregion
    	}
    }

    写好后台配置好服务器就可以在微信公众号网站也就是刚才的那个页面点击提交了,点击提交后微信的服务器会向你所配置的服务器80端口的这个接口发送GET请求,验证接口是否正确,如果提交成功,恭喜你可以进入下一个坑了;

    当你在写微信公众号的页面的时候,你有时候会用到上传图片或者打开摄像机等要求,这时候要接入JS_SDK,配置签名等等,这个签名应该算是配置里最难受的一块了。。。

    签名的获取有几个步骤,先是要通过appid和appSecret来获取access_token,然后用access_token来获取jsapi_ticket,代码如下

     //获取access_token
    string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appId, appSecret);
    var wc = new WebClient();
    string access_tokenRet = wc.DownloadString(url);
    string access_token = JsonHelper.JsonToObj<AccessTokenModel>(access_tokenRet).Access_Token;
    accesstokenstatic = access_token;
    
    //获取jsapi_ticket
    string Jsapi_TicketUrl = string.Format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=jsapi", access_token);
    var jsapiRet = wc.DownloadString(Jsapi_TicketUrl);
    string ticket = JsonHelper.JsonToObj<TickModel>(jsapiRet).ticket;
    ticketstatic = ticket;

    然后获取十位的时间戳和自己写的随机字符串和页面传来的url,将jsapi_ticket,url,timestamp(时间戳),nocestr(随机字符串随便写一个就行)sort方法排序,然后根据这几个的顺序进行赋值并用sha1加密如下

    string signaturestring = "jsapi_ticket=" + ticket + "&noncestr=" + nocestr + "&timestamp=" + timestamp + "&url=" + url2;
    byte[] temp1 = Encoding.UTF8.GetBytes(signaturestring);
    
    SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider();
    byte[] temp2 = sha.ComputeHash(temp1);
    var hashCode = BitConverter.ToString(temp2);
    hashCode = hashCode.Replace("-", "").ToLower();

     如此一来全部的都有了,返回这些就行了,如果报错了说url不合法就要取配置以下域名,二级域名也能用;

    四 ,带参二维码的扫描和事件的推送

    当你的公众号涉及到分享二维码给其他人,每一张二维码上还得要有你的信息的时候,需要在公众号的二维码上添加上一个场景值,比如你的用户id,当客户或者是其他用户通过你分享的二维码进入公众号的时候,微信服务器会推送给你一个xml包,其中一个子节点上就存着那个场景值;

    直接上代码--获取部分,

     public void GetQrCode()
     {
    	 string accesstoken = accesstokenstatic;
    	 if (string.IsNullOrEmpty(accesstoken))
    	 {
    		 //先主动取获取以下所需要的变量
    		 getaccesstoken(null, null);
    		 accesstoken = accesstokenstatic;
    		 Timer();
    	 }
    	//使用匿名对象作为post的参数然后再序列化
    	 var datap = new
    	 {
    		 //二维码的有效期(10天)
    		 expire_seconds = 864000,
    		 //类型动态的二维码
    		 action_name = "QR_SCENE",
    		 //信息
    		 action_info = new
    		 {
    			 scene = new {
    				 //自定义参数数值型为scene_id ,字符为scene_str
    				 scene_id = 15552300793
    			 }
    		 }
    	 };
    	 //使用流发送post带参请求
    	 string url = string.Format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={0}",accesstoken);
    	 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    	 request.Method = "POST";
    	 request.ContentType = "application/json";
    	 byte[] data = Encoding.UTF8.GetBytes(JsonHelper.ModelToJson(datap));
    	 request.ContentLength = data.Length;
    	 using (Stream strea = request.GetRequestStream())
    	 {
    		 strea.Write(data,0,data.Length);
    		 strea.Close();
    	 }
    	 HttpWebResponse respose = (HttpWebResponse)request.GetResponse();
    	 Stream stream = respose.GetResponseStream();
    	 string stringRespose = "";
    	 using (StreamReader sr = new StreamReader(stream, Encoding.UTF8))
    	 {
    		 stringRespose = sr.ReadToEnd();
    	 }
    	 if (string.IsNullOrEmpty(stringRespose))
    	 {
    		 Response.Write("sorry");
    	 }
    	 else { 
    		 //此时已经获取到二维码所使用的ticket
    		 string qrticket = JsonHelper.JsonToObj<QrCoderTicektModel>(stringRespose).ticket; 
    		 //二维码解析后的地址(可以自己转化为二维码)
    		 string qrurl =  JsonHelper.JsonToObj<QrCoderTicektModel>(stringRespose).url; 
    		 Response.Write("hhhhhh");
    		 LogHelper.WriteLog("getsometing:" + stringRespose);
    	 }
     }

    现在是获得了二维码的解析后的url,和获取二维码的ticket;有两种方式可以得到二维码,一种是直接将这个地址url返给前端,前端自己生成,二是后台获取到这个二维码后保存到服务器,前端自己去取;

    获取微信的推送,如刚才所说,扫码的时候会发送一个xml包;

    代码如下

    public void wx()
    {
    	//获取消息推送,或者是事件推送,类型是xml文件流
    	if (Request.RequestType == "POST")
    	{
    		#region 推送
    		//加载xml包,获取推送的内容
    		StreamReader sr = new StreamReader(Request.InputStream);
    		string xmlData = sr.ReadToEnd();
    		LogHelper.WriteLog(xmlData);
    		var doc = new XmlDocument();
    		doc.LoadXml(xmlData);
    		//获取xml包里的消息类型和事件类型,分类处理
    		var msgTypeNode = doc.SelectSingleNode("xml/MsgType");
    		var evenTypeNode = doc.SelectSingleNode("xml/Event");
    		string msgTypeString = msgTypeNode.InnerText;
    		if (evenTypeNode == null)
    		{
    			//此时为普通的消息推送
    		}
    		else
    		{
    			//此时为事件推送
    			string evenTypeString = evenTypeNode.InnerText;
    			//扫描带参的二维码的时候和关注和取关的推送
    			if (evenTypeString == "SCAN" || evenTypeString == "subscribe")
    			{
    				string SaoMaRenOpenId = doc.SelectSingleNode("xml/FromUserName").InnerText;
    				var BeiSaoRenCanShuNode = doc.SelectSingleNode("xml/EventKey");
    				if (BeiSaoRenCanShuNode != null)
    				{
    					string BeiSaoRenCanShuSting = BeiSaoRenCanShuNode.InnerText.Split('_')[1];
    					//已经获取扫码人和被扫人的个人id,下面就是存到数据库做关联就ok了,用到的时候取
    				}
    			}
    
    			//地理位置推送
    			if (evenTypeString == "LOCATION")
    			{
    
    			}
    
    			//点击自定义菜单拉去信息的时候的推送
    			if (evenTypeString == "CLICK")
    			{
    
    			}
    		}
    		#endregion
    	}

    五,

    natapp内网穿透:

    https://natapp.cn/注册,登录,获取自己的隧道,下载客户端,解压,cmd进入客户端的文件夹,natapp.exe -authtoken = "你的autotoken",

    如下则成功了

     

     

    展开全文
  • 1、OAuth2.0简介  OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该... ...每一个令牌授权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某...

    1、OAuth2.0简介

      OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。

      允许用户提供一个令牌而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要分享他们的访问许可或他们数据的所有内容。

      我们这里主要模拟在微信公众号中使用OAuth2.0进行授权,获取用户的基本信息的过程。详细的开发文档可查看微信的官方文档。  

    微信公众平台开发者文档:

    http://mp.weixin.qq.com/wiki/14/89b871b5466b19b3efa4ada8e577d45e.html

     

    2、获取测试公众账号及其相关配置

    1)、公众测试账号获取

      访问上面的连接,选择“接口测试号申请”获得直接打开http://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index通过微信客户端扫码登录即可登录。

      登录完即可获取到一个测试公众账号的信息。主要有appId和appsecret两个参数,这将唯一标示一个公众号,并且需要将他们作为参数获取用户的信息。

    2)、关注公众号

       用户只有关注了这个公众号了,才能通过打开有公众号信息的链接去授权第三方登录,并获取用户信息的操作。故我们还需要用我们的微信关注微信号,操作如下:

      还是刚刚那个登录成功后跳转的页面,我们可以看到,该页面有一个二维码,我们可以通过扫描该二维码进行关注,关注成功在右边的“用户列表”会多一个用户的信息。如下图所示:

     3)、配置回调函数

      我们在微信客户端访问第三方网页(即我们自己的网页)的时候,我们可以通过微信网页授权机制,我们不仅要有前面获取到的appid和appsecret还需要有当用户授权之后,回调的域名设置,即用户授权后,页面会跳转到哪里。具体的配置如下:

      还是在刚刚的页面,有一个“网页授权获取用户基本信息”,点击后面的修改

     

       填写回调的域名:

      如果你的网址没有被列入过黑名单,就会在顶部出现

     

     然后,域名配置就成功了!

    注意

    1、这里填写的是域名(是一个字符串),而不是URL,因此请勿加http://等协议头;
    2、授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com无法进行OAuth2.0鉴权

      到这里,我们就获取到我们必须用到的测试信息了,包括

    • 公众号appID、appsecret的获取;
    • 关注我们测试的公众号;
    • 配置扫码用户授权后回调的域名。

    3、微信授权登录并获取用户基本信息

      微信授权使用的是OAuth2.0授权的方式。主要有以下简略步骤

      第一步:用户同意授权,获取code

      第二步:通过code换取网页授权access_token

      第三步:刷新access_token(如果需要)

      第四步:拉取用户信息(需scope为 snsapi_userinfo)

     

      详细的步骤如下:

      1.用户关注微信公众账号。

      2.微信公众账号提供用户请求授权页面URL。

      3.用户点击授权页面URL,将向服务器发起请求

      4.服务器询问用户是否同意授权给微信公众账号(scope为snsapi_base时无此步骤)

      5.用户同意(scope为snsapi_base时无此步骤)

      6.服务器将CODE通过回调传给微信公众账号

      7.微信公众账号获得CODE

      8.微信公众账号通过CODE向服务器请求Access Token

      9.服务器返回Access Token和OpenID给微信公众账号

      10.微信公众账号通过Access Token向服务器请求用户信息(scope为snsapi_base时无此步骤)

      11.服务器将用户信息回送给微信公众账号(scope为snsapi_base时无此步骤)

    1)、用户授权并获取code

      在域名(前面配置的回调域名)根目录下,新建一个文件,命名为oauth.php(名字随便你取,下面的redirect_uri做相应修改即可)该php实现的功能也很简单,只是将url上的code参数取出来并打印出来而已,方便我们进行接下来的操作。

      Oauth.php中的内容如下:

    复制代码

    复制代码

    <?php
    if (isset($_GET['code'])){
        echo $_GET['code'];
    }else{
        echo "NO CODE";
    }
    ?>

    复制代码

    复制代码

       这个php的主要目的是当用户确认授权登录之后,会调转到redirect_uri这个地址上,并带上code参数(微信生成),我们为了方便获取,这里也可以是一个空白的页面,下面有其他方法得到url上面的code参数。

      请求授权页面的构造方式:

    https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

     参数说明

    参数

    必须

    说明

    appid

    公众号的唯一标识(这个就是我们前面申请的)

    redirect_uri

    授权后重定向的回调链接地址(我们前面申请的)

    response_type

    返回类型,请填写code

    scope

    应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)

    state

    重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节,该值会被微信原样返回,我们可以将其进行比对,防止别人的攻击。

    #wechat_redirect

    直接在微信打开链接,可以不填此参数。做页面302重定向时候,必须带此参数

     

      应用授权作用域:由于snsapi_base只能获取到openid,意义不大,所以我们使用snsapi_userinfo。
      回调地址:填写为刚才上传后的oauth.php的文件地址,
      state参数:随便一个数字,这里填123

      尤其注意:由于授权操作安全等级较高,所以在发起授权请求时,微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无法正常访问

      构造请求url如下:

    https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx4a22b50d7e897f97&redirect_uri=http%3a%2f%2fad.seewo.com%2foauth.php&response_type=code&scope=snsapi_userinfo&state=123#wechat_redirect

       这个需要发到微信中,使用微信浏览器才能打开。

      点开上面的链接,点击确认登录即可跳转到刚刚配置的回调页面,并获取了微信传回的code参数,用于下面的操作。

    授权页面如下:

    授权后跳转的页面(我们前面配置的redirect_uri):

      假如我们没有在php中打印出了code,这个时候我们可以通过右上角按钮中的复制链接,得到链接如下:

    http://ad.seewo.com/oauth2.php?code=0217a07e9c194dbf539c45c266b2dcfZ&state=123

     code说明 :

    code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。

    1)、使用code换取access_token

    换取网页授权access_token页面的构造方式:

    https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

    参数说明

    参数

    是否必须

    说明

    appid

    公众号的唯一标识

    secret

    公众号的appsecret

    code

    填写第一步获取的code参数

    grant_type

    填写为authorization_code

    code:在这里填写为上一步获得的值。
    构造的url如下,在网页中打开链接就行:

    https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx41cb8dbd827a16e9&secret=d4624c36b6795d1d99dcf0547af5443d&code=00137323023ab55775be09d6d8e75ffA&grant_type=authorization_code 

      只有获取code的链接必须是在微信客户端中点开的,获取access_token和用户信息可以直接在网页打开即可。

    返回说明

    正确时返回的JSON数据包如下:

    复制代码

    复制代码

    {
    
       "access_token":"ACCESS_TOKEN",
    
       "expires_in":7200,
    
       "refresh_token":"REFRESH_TOKEN",
    
       "openid":"OPENID",
    
       "scope":"SCOPE"
    
    }

    复制代码

    复制代码

     

    参数

    描述

    access_token

    网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同

    expires_in

    access_token接口调用凭证超时时间,单位(秒)

    refresh_token

    用户刷新access_token

    openid

    用户唯一标识

    scope

    用户授权的作用域,使用逗号(,)分隔


      错误时微信会返回JSON数据包如下(示例为Code无效错误):

    {"errcode":40029,"errmsg":"invalid code"}

    2)、通过access_token、openid获取用户信息 

    请求方法:

    https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID

      参数说明

    参数

    描述

    access_token

    网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同

    openid

    用户的唯一标识

      构造url如下:

    https://api.weixin.qq.com/sns/userinfo?access_token=OezXcEiiBSKSxW0eoylIeABONBTt9gBE6cK3arF_L6aOvwU4ynS5ZxG4r6ZUIJxh7y_ClmPRkYbMeOc_r30LAGB2IEAlCFsQQvfQMJSwHcU6109-6vz603Jho4oZhdns6AOXwoxaWcLujT1RWnC_hQ&openid=oF3PcsnsrMiJzEwalZZbAfWQpxCI

      可以在浏览器中直接执行这个。

      得到的json格式数据如下:

    复制代码

    复制代码

    {
    
       "openid":" OPENID",
    
       " nickname": NICKNAME,
    
       "sex":"1",
    
       "province":"PROVINCE"
    
       "city":"CITY",
    
       "country":"COUNTRY",
    
        "headimgurl":    "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46", 
    
    "privilege":[
    
    "PRIVILEGE1"
    
    "PRIVILEGE2"
    
        ],
    
        "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
    
    }

    复制代码

    复制代码

    参数

    描述

    openid

    用户的唯一标识

    nickname

    用户昵称

    sex

    用户的性别,值为1时是男性,值为2时是女性,值为0时是未知

    province

    用户个人资料填写的省份

    city

    普通用户个人资料填写的城市

    country

    国家,如中国为CN

    headimgurl

    用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。

    privilege

    用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)

    unionid

    只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。详见:获取用户个人信息(UnionID机制)

      错误时微信会返回JSON数据包如下(示例为openid无效):

    {"errcode":40003,"errmsg":" invalid openid "}

    值得注意的地方:

      用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。

      网页授权获取用户基本信息也遵循UnionID机制。即如果开发者有在多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用UnionID机制来满足上述需求。

      UnionID机制的作用说明:如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的unionid来区分用户的唯一性,因为同一用户,对同一个微信开放平台下的不同应用(移动应用、网站应用和公众帐号),unionid是相同的。

      尤其注意:由于公众号的secret和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。后续刷新access_token、通过access_token获取用户信息等步骤,也必须从服务器发起。

    展开全文
  • 微信公众号主要有以下几个步骤 微信公众号的通讯机制 微信公众号简介 1.注册微信公众号 2.注册测试公众号 3.搭建微信本地调试环境 1)下载客户端natapp: 2)安装natapp: 4.微信公众号接入(校验签名) 第1步中...

    由于图片图床问题,文章部分图片无法预览,暂时把该文章迁移至简书,给大家带来麻烦,抱歉了。
    感兴趣的小伙伴可参考:https://www.jianshu.com/p/cc1b1050b5b4

    Author xiuhong.chen

    Date 2017/11/23

    Desc 微信公众号公注册、开发环境搭建、access_token管理、Demo实现不同类型消息发送、实现天气预报和翻译功能、natapp外网穿透

    微信公众号的通讯机制

    image.png

    微信公众号简介

    微信公众号分为服务号、订阅号、企业号,订阅号可以个人申请,服务号和企业号要有企业资质才可以。

    我们所说的微信公众号开发指的是订阅号和服务号。关于订阅号和服务器的区别,官方是这样解释的

    • 服务号**:主要偏向于服务交互(功能类似12315,114,银行,提供绑定信息,服务交互),每月可群发4条消息;服务号**适用人群:**媒体、企业、政府或其他组织。

    • 订阅号**:主要偏向于为用户传达资讯,(功能类似报纸杂志,为用户提供新闻信息或娱乐趣事),每天可群发1条消息;订阅号适用人群:个人、媒体、企业、政府或其他组织。

    1.注册微信公众号

    进入微信公众号注册页面https://mp.weixin.qq.com/点击公众号右上方的注册按钮,进入注册界面,填写基本信息,选择订阅号, 完成身份认证, 此处我选择的是个人订阅号,如下完善即可:

    image.png

    image.png

    然后注册成功之后进入微信公众平台后台,然后完善微信号名称和微信号ID:

    image.png

    image.png

    微信号名称默认是新注册公众号,还需要修改微信号名称, 修改的时候需要经过微信认证,并且审核通过之后才可以使用该公众号.

    image.png

    2.注册测试公众号

    个人订阅号有一些接口是没有权限的,也就是说个人订阅号无法调用一些高级的权限接口,如生成二维码、网页授权、自定义菜单、微信支付这样的接口权限个人订阅号是没有调用权限的, 幸运的是,微信公众平台提供了测试公众账号,测试公众号有很多个人订阅号不具备的权限, 测试公众号的注册地址为:

    http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

    用微信扫描页面中的二维码进行登录,登录成功后,就可以看到腾讯分配给我们的测试公众号的信息了,如下图所示, 接下来我们就可以搭建环境,进行开发测试了

    image.png

    测试公众号的所拥有的接口权限如下:

    image.png
    image.png
    image.png
    image.png

    3.搭建微信本地调试环境

    开发基于微信公众号的应用最大的痛苦之处就是调试问题,每次实现一个功能后都需要部署到一个公网服务器进行测试,因为微信用户每次向公众号发起请求时,微信服务器会先接收到用户的请求,然后再转发到我们的服务器上,也就是说,微信服务器是要和我们的服务器进行网络交互,所以我们必须保证我们的服务器外网可以访问到,这种部署到公网服务器进行测试的做法对于我们开发者来说简直是噩梦。所以我们要想一个办法可以做到本地部署,本地调试代码,而要做到这一点,那么我们要解决的问题就是将内网的部署服务器映射到外网,让微信服务器可以正常访问到,幸运的是,借助于第三方软件Ngrok,我们就可以做得到。Ngrok是一个免费的软件Ngrok,使用Ngrok后,我们就可以实现内网穿透,也就是说我们可以将内网的服务器映射到外网给别人访问,这对于我们在本地开发环境中调试微信代码是以及给用户演示一些东西非常快速和有帮助的,因为可以直接使用我们自己的内网的电脑作为服务器。不过需要翻墙访问.

    国内提供Ngrok服务比较好的网站是:http://natapp.cn/,如下图所示:

    image.png

    1)下载客户端natapp:

    image.png

    2)安装natapp:

    具体参考http://blog.csdn.net/xunxianren007/article/details/54954520, 这个网址里详细介绍了win/Mac/Linux下安装步骤

    • 解压缩到目录D:\Program Files\natapp

    • 直接双击打开失败,需要配置环境变量,编辑环境变量Path,新建natapp目录

      image.png

    • 打开cmd, 执行命令 natapp, 显示认证错误

      image.png

    • 这个时候是需要token认证的, 所以我们的主要工作就是如何获得authtoken

      进入https://natapp.cn/,根据提示注册并创建免费隧道, 注册的时候需要用支付宝实名认证的手机号注册

      根据《中华人民共和国网络安全法》,以及防止隧道被非法使用,Natapp实行实名认证制度.
      本站承诺身份信息仅用于身份验证识别,不做任何其他用途,且严格加密存储杜绝泄漏风险
      本实名认证系统基于阿里大数据的强个人识别验证.手机,身份证信息须匹配,比如手机号是你的支付宝实名认证的手机号,日常正常使用的.如其他小号可能无法通过验证
      目前创建免费隧道强制要求实名认证.付费隧道可通过支付宝支付来实名认证
      

      image.png

      image.png

    • 复制authtoken, cmd进入natapp目录执行 natapp -authtoken yourauthtoken 出现下图即为成功

      image.png

    • 此时外网的用户可以直接使用http://rzxjzk.natappfree.cc这个域名访问到我内网的127.0.0.1:8080服务器了,如下图所示:

      image.png

      image.png

      使用了ngrok之后,我们就可以把内网的服务器当成公网服务器来使用了.访问的速度也还在可以接受的范围内吧,截止到目前为止ngrok是可用的,微信公众号服务器是可以访问的,这样一来也就不妨碍我们做本地调式了。到此,我们的微信本地调试开发环境就算是搭建好了。

    4.微信公众号接入(校验签名)

    微信公众平台开发者文档上,关于公众号接入这一节内容在接入指南上写的比较详细的,文档中说接入公众号需要3个步骤,分别是:

    1、填写服务器配置
      2、验证服务器地址的有效性
      3、依据接口文档实现业务逻辑

    其实,第3步已经不能算做公众号接入的步骤,而是接入之后,开发人员可以根据微信公众号提供的接口所能做的一些开发。

    第1步中服务器配置包含服务器地址(URL)、令牌(Token) 和 消息加解密密钥(EncodingAESKey)。

    ​ 可在开发–>基本配置–>服务器配置中配置

    ​ 服务器地址即公众号后台提供业务逻辑的入口地址,目前只支持80端口,之后包括接入验证以及任何其它的操作的请求(例如消息的发送、菜单管理、素材管理等)都要从这个地址进入。接入验证和其它请求的区别就是,接入验证时是get请求,其它时候是post请求;

    Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性);

    EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。本例中全部以未加密的明文消息方式,不涉及此配置项。

    第2步,验证服务器地址的有效性,当点击“提交”按钮后,微信服务器将发送一个http的get请求到刚刚填写的服务器地址,并且携带四个参数:

    image.png

    接到请求后,我们需要做如下三步,若确认此次GET请求来自微信服务器,原样返回echostr参数内容,则接入生效,否则接入失败。

        1. 将token、timestamp、nonce三个参数进行字典序排序
        2. 将三个参数字符串拼接成一个字符串进行sha1加密 (可逆加密解密函数)
        3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
    

    下面我们用Java代码来演示一下这个验证过程

    使用IDE(Eclipse或者IntelliJ IDEA)创建一个JavaWeb项目,新建servlet weChatAccounts,代码如下:

    package weChatServlet;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.activation.DataHandler;
    import javax.activation.FileDataSource;
    import javax.mail.*;
    import javax.mail.internet.*;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;
    import java.util.Calendar;
    import java.util.Date;
    import java.util.Properties;
    
    public class weChatAccounts extends HttpServlet {
        static Logger logger = LoggerFactory.getLogger(weChatAccounts.class);
    
        /*
        * 自定义token, 用作生成签名,从而验证安全性
        * */
        private final String TOKEN = "cherry";
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doGet(req,resp);
        }
        @Override
        public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            System.out.println("-----开始校验签名-----");
    
            /**
             * 接收微信服务器发送请求时传递过来的参数
             */
            String signature = req.getParameter("signature");
            String timestamp = req.getParameter("timestamp");
            String nonce = req.getParameter("nonce"); //随机数
            String echostr = req.getParameter("echostr");//随机字符串
    
            /**
             * 将token、timestamp、nonce三个参数进行字典序排序
             * 并拼接为一个字符串
             */
            String sortStr = sort(TOKEN,timestamp,nonce);
            /**
             * 字符串进行shal加密
             */
            String mySignature = shal(sortStr);
            /**
             * 校验微信服务器传递过来的签名 和  加密后的字符串是否一致, 若一致则签名通过
             */
            if(!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)){
                System.out.println("-----签名校验通过-----");
                resp.getWriter().write(echostr);
            }else {
                System.out.println("-----校验签名失败-----");
            }
        }
    
        /**
         * 参数排序
         * @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 "";
        }
    }
    
    

    在web.xml中配置 servlet:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    		  http://java.sun.com/xml/ns/javaee/web-app_3_1.xsd"
               version="3.1">
        <welcome-file-list>
            <welcome-file>index.jsp</welcome-file>
        </welcome-file-list>
    
        <servlet>
            <servlet-name>weChatServlet</servlet-name>
            <servlet-class>weChatServlet.weChatAccounts</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>weChatServlet</servlet-name>
            <url-pattern>/weChatServlet</url-pattern> <!--url-pattern必须与servlet-name一致-->
        </servlet-mapping>
    </web-app>
    
    

    然后在index.jsp中写hello world 测试:

    <%-- Created by IntelliJ IDEA. --%>
    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <html>
      <head>
        <title></title>
      </head>
      <body>
          <h3>微信公众号测试!</h3>
      </body>
    </html>
    

    启动项目结果如下:

    image.png

    然后启动natapp,进行内网透传 natapp -authtoken mytoken

    image.png

    根据动态生成的ip地址访问.,得到如下效果,则表示外网可以成功访问:

    image.png

    进入微信测试公众号管理界面,在接口配置信息中填入映射的外网地址和代码中声明的token,如下图所示:

    点击提交,会显示配置成功, 控制台就会打印信息, 显示签名校验通过.

    注意: URL是 外网的ip地址加上 web.xml中配置的servlet名称

    image.png

    image.png

    到此,我们的公众号应用已经能够和微信服务器正常通信了,也就是说我们的公众号已经接入到微信公众平台了。

    5.access_token管理

    我们的公众号和微信服务器对接成功之后,接下来要做的就是根据我们的业务需求调用微信公众号提供的接口来实现相应的逻辑了。在使用微信公众号接口中都需要一个access_token。

    1)access_token介绍

    access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。

    总结以上说明,access_token需要做到以下两点:

    1.因为access_token有2个小时的时效性,要有一个机制保证最长2个小时重新获取一次。

    2.因为接口调用上限每天2000次,所以不能调用太频繁。

    2)获取access_token步骤

    公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在“微信公众平台-开发-基本配置”页中获得(需要已经成为开发者,且帐号没有异常状态)。调用接口时,请登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中,点击查看设置方法,否则将无法调用成功。

    接口调用请求说明

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

    参数说明

    参数 是否必须 说明
    grant_type 获取access_token填写client_credential
    appid 第三方用户唯一凭证
    secret 第三方用户唯一凭证密钥,即appsecret

    返回说明

    正常情况下,微信会返回下述JSON数据包给公众号:

    {"access_token":"ACCESS_TOKEN","expires_in":7200}
    

    参数说明

    参数 说明
    access_token 获取到的凭证
    expires_in 凭证有效时间,单位:秒

    错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):

    {"errcode":40013,"errmsg":"invalid appid"}
    

    返回码说明

    返回码 说明
    -1 系统繁忙,此时请开发者稍候再试
    0 请求成功
    40001 AppSecret错误或者AppSecret不属于这个公众号,请开发者确认AppSecret的正确性
    40002 请确保grant_type字段值为client_credential
    40164 调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置

    3)代码实现获取access_token

    定义一个默认启动的servlet,在init方法中启动一个Thread,这个进程中定义一个无限循环的方法,用来获取access_token,当获取成功后,此进程休眠7000秒(7000秒=1.944444444444444小时),否则休眠3秒钟继续获取。流程图如下:

    image.png

    定义一个dto AccessToken:

    package AccessToken;
    
    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;
        }
    }
    
    
    package AccessToken;
    
    import AccessToken.AccessToken;
    
    public class AccessTokenInfo {
    
        public static AccessToken accessToken = null;
    }
    
    

    编写一个用于发起https请求的工具类NetWorkUtil,代码如下:

    getHttpsResponse方法是请求一个https地址,参数requestMethod为字符串“GET”或者“POST”,传null或者“”默认为get方式。

    package AccessToken;
    
    import javax.net.ssl.*;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.security.KeyManagementException;
    import java.security.NoSuchAlgorithmException;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    
    /**
     * created by xiuhong.chen
     * 2017/12/28
     * 发起HTTPS请求的工具类
     * getHttpsResponse方法是请求一个https地址,参数requestMethod为字符串“GET”或者“POST”,传null或者“”默认为get方式。
     */
    public class NetWorkUtil {
        /**
         * 发起HTTPS请求
         * @param reqUrl
         * @param requestMethod
         * @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 {
                    con.setRequestMethod("GET"); //使用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 {
            }
        };
    }
    
    

    定义一个默认启动的servlet,在init方法中启动一个新的线程去获取accessToken:

    此处需要将JSON数据解析为object, 需要用到fastjson.jar

    package AccessToken;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    
    
    public class AccessTokenServlet extends HttpServlet {
        static Logger logger = LoggerFactory.getLogger(AccessTokenServlet.class);
    
        @Override
        public void init() throws ServletException {
            System.out.println("-----启动AccessTokenServlet-----");
            super.init();
    
            final String appId = getInitParameter("appId");
            final String appSecret = getInitParameter("appSecret");
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            //获取accessToken
                            AccessTokenInfo.accessToken = getAccessToken(appId, appSecret);
                            //获取成功
                            if (AccessTokenInfo.accessToken != null) {
                                //获取到access_token 休眠7000秒,大约2个小时左右
                                Thread.sleep(7000 * 1000);
                            } else {
                                //获取失败
                                Thread.sleep(1000 * 3); //获取的access_token为空 休眠3秒
                            }
                        } catch (Exception e) {
                            System.out.println("发生异常:" + e.getMessage());
                            e.printStackTrace();
                            try {
                                Thread.sleep(1000 * 10); //发生异常休眠1秒
                            } catch (Exception e1) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }).start();
        }
    
        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 json = JSON.parseObject(result);
            AccessToken token = new AccessToken();
            token.setTokenName(json.getString("access_token"));
            token.setExpireSecond(json.getInteger("expires_in"));
            return token;
        }
    
    }
    
    

    然后在web.xml中配置AccessTokenServlet:

    	<servlet>
            <servlet-name>accessTokenServlet</servlet-name>
            <servlet-class>weChatServlet.AccessTokenServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>accessTokenServlet</servlet-name>
            <url-pattern>/accessTokenServlet</url-pattern> <!--url-pattern必须与servlet-name一致-->
        </servlet-mapping>
    

    index.jsp:

    <%-- Created by IntelliJ IDEA. --%>
    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
    <c:set var="basePath" value="${pageContext.request.contextPath }"></c:set>
    <%@ page import="AccessToken.AccessTokenInfo"%>
    <html>
      <head>
        <title></title>
      </head>
      <body>
    
          <h3>微信公众号测试!</h3>
          <form action="${pageContext.request.contextPath}/weChatServlet" method="get">
              <button οnclick="submit">测试微信公众号</button>
          </form>
    
          <hr/>
    		
          <%--获取access_token--%>
          <form action="${pageContext.request.contextPath}/accessTokenServlet" method="get">
              <button οnclick="submit">获取access_token</button>
          </form>
          <c:if test="AccessTokenInfo.accessToken != null">
              access_token为:<%=AccessTokenInfo.accessToken.getTokenName()%>
          </c:if>
    
      </body>
    </html>
    

    启动项目, 点击获取access_token按钮: 获取出错, 返回码40013,表示AppID无效错误

    -----启动AccessTokenServlet-----
    SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    SLF4J: Defaulting to no-operation (NOP) logger implementation
    SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
    {"errcode":40013,"errmsg":"invalid appid hint: [OwC0oa06551466]"}
    
    获取到的access_token={"errcode":40013,"errmsg":"invalid appid hint: [OwC0oa06551466]"}
    

    再次确认appID和appsecret没有错误之后,再次获取token:

    image.png

    至此, access_token获取成功 !

    6.总结一下项目启动之后, 通过微信公众号测试的全过程:

    1. 开启外网访问 : CMD进入natapp目录下, 运行命令natapp -authtoken yourauthtoken , 得到外网访问的域名

    2. Tomcat启动项目

    3. 进入微信公众号测试管理平台, 修改接口配置信息URL为: 新域名/weChatServlet , 待签名校验通过,就可以测试

    4. 进入测试公众号, 发送消息进行测试

    7.被动发送用户消息

    业务逻辑(一) — 发送文本消息

    经过上述的三步,我们开发前的准备工作已经完成了,接下来要做的就是接收微信服务器发送的消息并做出响应

    从微信公众平台接口消息指南中可以了解到,当用户向公众帐号发消息时,微信服务器会将消息通过POST方式提交给我们在接口配置信息中填写的URL,而我们就需要在URL所指向的请求处理类WxServlet的doPost方法中接收消息、处理消息和响应消息。

    可以参考微信API文档 – 被动回复用户消息 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543

    1)编写一个用于处理消息的工具类

    这个工具类主要是解析消息, 构建消息

    package weChatServlet;
    
    import org.dom4j.Document;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    
    import javax.servlet.http.HttpServletRequest;
    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;
    
    public class MessageUtil {
        /**
         * 解析微信发来的请求(XML)
         * @param request
         * @return map
         * @throws Exception
         */
        public static Map<String,String> parseXml(HttpServletRequest request) throws Exception {
            // 将解析结果存储在HashMap中
            Map<String,String> map = new HashMap();
            // 从request中取得输入流
            InputStream inputStream = request.getInputStream();
            System.out.println("获取输入流");
            // 读取输入流
            SAXReader reader = new SAXReader();
            Document document = reader.read(inputStream);
            // 得到xml根元素
            Element root = document.getRootElement();
            // 得到根元素的所有子节点
            List<Element> elementList = root.elements();
    
            // 遍历所有子节点
            for (Element e : elementList) {
                System.out.println(e.getName() + "|" + e.getText());
                map.put(e.getName(), e.getText());
            }
    
            // 释放资源
            inputStream.close();
            inputStream = null;
            return map;
        }
    
        /**
         * 根据消息类型 构造返回消息
         */
        public static String buildXml(Map<String,String> map) {
            String result;
            String msgType = map.get("MsgType").toString();
            System.out.println("MsgType:" + msgType);
            if(msgType.toUpperCase().equals("TEXT")){
                result = buildTextMessage(map, "Cherry的小小窝, 请问客官想要点啥?");
            }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);
        }
    
        private static String getUtcTime() {
            Date dt = new Date();// 如果不需要格式,可直接用dt,dt就是当前系统时间
            DateFormat df = new SimpleDateFormat("yyyyMMddhhmm");// 设置显示格式
            String nowTime = df.format(dt);
            long dd = (long) 0;
            try {
                dd = df.parse(nowTime).getTime();
            } catch (Exception e) {
    
            }
            return String.valueOf(dd);
        }
    
    }
    
    
    2)在WxServlet的doPost方法中处理请求

    WxServlet的doPost方法的代码如下:

    package weChatServlet;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;
    import java.util.Map;
    
    public class WeChatAccounts extends HttpServlet {
        static Logger logger = LoggerFactory.getLogger(WeChatAccounts.class);
    
        /*
        * 自定义token, 用作生成签名,从而验证安全性
        * */
        private final String TOKEN = "cherry";
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // TODO 接收、处理、响应由微信服务器转发的用户发送给公众帐号的消息
            // 将请求、响应的编码均设置为UTF-8(防止中文乱码)
            req.setCharacterEncoding("UTF-8");
            resp.setCharacterEncoding("UTF-8");
            System.out.println("请求进入");
            String result = "";
            try {
                Map<String,String> map = MessageUtil.parseXml(req);
    
                System.out.println("开始构造消息");
                result = MessageUtil.buildXml(map);
                System.out.println(result);
    
                if(result.equals("")){
                    result = "未正确响应";
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("发生异常:"+ e.getMessage());
            }
            resp.getWriter().println(result);
        }
        @Override
        public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            System.out.println("-----开始校验签名-----");
         ..............................
            
        }
    }
    
    

    将WxStudy部署到Tomcat服务器,启动服务器,记得使用ngrok将本地Tomcat服务器的8080端口映射到外网,保证接口配置信息的URL地址:http://xdp.ngrok.natapp.cn/WxServlet可以正常与微信服务器通信

    登录到我们的测试公众号的管理后台,然后用微信扫描一下测试号的二维码,如下图所示:

    image.png

    关注成功后,我们开发好的公众号应用会先给用户发一条提示用户操作的文本消息,微信用户根据提示操作输入"文本",我们的公众号应用接收到用户请求后就给用户回复了一条我们自己构建好的文本消息,如下图所示:

    image.png

    我们公众号获取到的输入流如下:

    第一次关注时获取的输入流, 消息类型为事件event, 订阅
    ToUserName|gh_fbcf752402d4
    FromUserName|oEG9V1PeFHnsQviezVY9D4IDcyAk
    CreateTime|1514461156
    MsgType|event
    Event|subscribe
    EventKey|
    
    第一次发送消息内容为"文本"获取的输入流, 消息类型是text
    ToUserName|gh_fbcf752402d4
    FromUserName|oEG9V1PeFHnsQviezVY9D4IDcyAk
    CreateTime|1514461351
    MsgType|text
    Content|文本
    MsgId|6504561974052876654
    

    我们公众号应用响应给微信用户的文本消息的XML数据如下:

    关注之后返回的消息是:
    MsgType:event
    <xml>
    	<ToUserName><![CDATA[oEG9V1PeFHnsQviezVY9D4IDcyAk]]></ToUserName>
        <FromUserName><![CDATA[gh_fbcf752402d4]]></FromUserName>					     			<CreateTime>1514417940000</CreateTime>
    	<MsgType><![CDATA[text]]></MsgType>
    	<Content><![CDATA[请回复如下关键词:
    	文本
    	图片
    	语音
    	视频
    	音乐
    	图文]]></Content>
    </xml>
    
    
    输入文本之后返回的消息是:
    <xml>
    	<ToUserName><![CDATA[oEG9V1PeFHnsQviezVY9D4IDcyAk]]></ToUserName>
    	<FromUserName><![CDATA[gh_fbcf752402d4]]></FromUserName>
    	<CreateTime>1514418120000</CreateTime>
    	<MsgType><![CDATA[text]]></MsgType>
    	<Content><![CDATA[Cherry的小小窝, 请问客官想要点啥?]]></Content>
    </xml>
    

    业务逻辑(二) — 上传素材获取Media_id

    图片,语音,视频的回复消息构造,这三种消息构造时的都需要一个mediaId,而这个mediaId是通过素材管理接口上传多媒体文件得到的,为了构造图片,语音,视频的这几种回复消息,我事先准备好了测试素材,

    如图片路径为: C:\Users\Chen Xiuhong\Pictures\timg (1).jpg

    然后通过微信公众号平台提供的素材管理接口将图片,语音,视频上传到微信服务器上,上传成功后,微信服务器会给我们返回一个mediaId,用于标识上传成功的多媒体素材,上传素材的工具类代码如下:

    package weChatServlet;
    
    import AccessToken.AccessToken;
    import AccessToken.NetWorkUtil;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONException;
    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 xiuhong.chen@hand-china.com
     * created on 2018/1/9
     * 上传media素材, 获取media_id
     */
    public class UploadMediaApiUtil {
        // 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;
        }
    
    }
    
    
    package weChatServlet;
    
    import com.alibaba.fastjson.JSONObject;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.File;
    import java.io.IOException;
    
    /**
     * 上传素材servlet
     */
    @WebServlet(name = "UploadMediaServlet")
    public class UploadMediaServlet extends HttpServlet {
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
        }
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            UploadMediaApiUtil uploadMediaApiUtil = new UploadMediaApiUtil();
            String appId = "wx0aa26453a7ec9df7";
            String appSecret = "2819f0c98199daef39cb6220b4d01b96";
            String accessToken = uploadMediaApiUtil.getAccessToken(appId,appSecret);
    
            String filePath = "C:\\Users\\Chen Xiuhong\\Pictures\\timg (1).jpg";
            File file = new File(filePath);
            String type = "IMAGE";
            JSONObject jsonObject = uploadMediaApiUtil.uploadMedia(file,accessToken,type);
            System.out.println(jsonObject.toString());
        }
    }
    
    

    运行的时候老是在PostMethod处报错, 错误信息显示缺包

    image.png

    百度之后发现Http协议使用封装jar包(commons-codec-1.3.jar、commons-httpclient-3.1.jar、commons-logging-1.1.jar), 所以还缺少一个包, 添加三个包之后就成功运行,可以得到media_id

    image.png

    可以看到,素材上传成功后,微信服务器就会返回一个media_id,用于标识上传后的文件.有了这个media_id后,我们就可以构建我们想要的图片,语音,视频回复消息了.

    业务逻辑(三) — 发送图片消息

    在第7部分中我们已经上传了一个图片素材, 并且也获得了media_id

    UCWXNCogK5ub6YFFQf7QcEpvDIYLf3Zh0L5W9i4aEp2ehfnTrASeV59x3LMD88SS
    

    接下来我们就使用media_id来构造图片消息:

    	/**
         *  构建图片消息
         * @param map
         * @param picUrl
         * @return
         */
        private static String buildImageMessage(Map<String, String> map, String picUrl) {
            String fromUserName = map.get("FromUserName");
            String toUserName = map.get("ToUserName");
            /*返回指定的图片(该图片是上传为素材的,获得其media_id)*/
            //String media_id = "UCWXNCogK5ub6YFFQf7QcEpvDIYLf3Zh0L5W9i4aEp2ehfnTrASeV59x3LMD88SS";
    
            /*返回用户发过来的图片*/
            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
            );
        }
    
    

    用户发送图片时,公众号返回给用户我之前上传的素材; 或者将用户发送的图片又返回一次

    image.png

    业务逻辑(四) — 发送语音消息

     	/**
         * 构造语音消息
         * @param map
         * @return
         */
        private static String buildVoiceMessage(Map<String, String> map) {
            String fromUserName = map.get("FromUserName");
            String toUserName = map.get("ToUserName");
            /*返回用户发过来的语音*/
            String media_id = map.get("MediaId");
            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
            );
        }
    
    

    业务逻辑(五) — 发送视频消息

    首先需要调用业务逻辑(二)上传一段小视频,获取media_id,上传的视频不大于10MB,支持MP4格式, 然后当用户发送视频格式时,公众号就回复视频格式

    	/**
         * 回复视频消息
         * @param map
         * @return
         */
        private static String buildVideoMessage(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 = "hTl1of-w78xO-0cPnF_Wax1QrTwhnFpG1WBkAWEYRr9Hfwxw8DYKPYFX-22hAwSs";
            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
            );
        }
    

    展示效果如下:

    image.png

    注意: media_id只能用上传的视频的id, 不能使用用户发送视频时的那个id, 至于原因待探究

    业务逻辑(六) — 发送音乐消息

    	/**
         * 回复音乐消息
         * @param map
         * @return
         */
        private static String buildMusicMessage(Map<String, String> map) {
            String fromUserName = map.get("FromUserName");
            String toUserName = map.get("ToUserName");
            String title = "亲爱的路人";
            String description = "多听音乐 心情棒棒 嘻嘻?";
            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
            );
        }
    

    image.png

    业务逻辑(七) — 发送图文消息

    /**
         * 返回图文消息
         * @param map
         * @return
         */
        private static String buildNewsMessage(Map<String, String> map) {
            String fromUserName = map.get("FromUserName");
            String toUserName = map.get("ToUserName");
            String title1 = "HAP审计的实现和使用";
            String description1 = "由于HAP框架用的是Spring+SpringMVC+Mybatis,其中Mybatis中的拦截器可以选择在被拦截的方法前后执行自己的逻辑。所以我们通过拦截器实现了审计功能,当用户对某个实体类进行增删改操作时,拦截器可以拦截,然后将操作的数据记录在审计表中,便于用户以后审计。";
            String picUrl1 ="http://upload-images.jianshu.io/upload_images/7855203-b9e9c9ded8a732a1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240";
            String textUrl1 = "http://blog.csdn.net/a1786223749/article/details/78330890";
    
            String title2 = "KendoUI之Grid的问题详解";
            String description2 = "kendoLov带出的值出现 null和undefined";
            String picUrl2 ="https://demos.telerik.com/kendo-ui/content/shared/images/theme-builder.png";
            String textUrl2 = "http://blog.csdn.net/a1786223749/article/details/78330908";
    
            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
            );
        }
    

    image.png

    网络不太好,所以图片没有加载出来, 在手机上测试是可以看到图片的.

    8.天气预报功能开发

    借助百度API查询天气, 所以首先我们要在百度开发平台注册信息

    image.png

    申请天气查询的API, 然后获取AK

    image.png

    但是查询天气的时候AK并不能使用, 显示APP服务被禁用

    image.png

    看来用百度的API这一步行不通, 只能使用Webservice网站上公开的接口

    调用WebService查询天气预报

    直接使用wsimport 通过该地址生成Java文件时,会报错。因为该wsdl里面包含 ref = “s:schema” 这样的引用。而jaxb是不支持的。所以需要手动将该wsdl下载下来做下修改,然后再生成java文件。

    wsimport -keep http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl

    D:\Eclipse Files\TEST_EMAIL\src\weatherService>wsimport -keep  http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl
    正在解析 WSDL...
    
    
    [WARNING] src-resolve.4.2: 解析组件 's:schema' 时出错。在该组件中检测到 's:schema' 位于名称空间 'http://www.w3.org/2001/XMLSchema' 中, 但无法从方案文档 'http://
    ws.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl#types?schema1' 引用此名称空间的组件。如果这是不正确的名称空间, 则很可能需要更改 's:schema' 的前缀。如果这是正确
    的名称空间, 则应将适当的 'import' 标记添加到 'http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl#types?schema1'。
      http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl#types?schema1的第 15 行
    
    [WARNING] src-resolve: 无法将名称 's:schema' 解析为 'element declaration' 组件。
      http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl#types?schema1的第 15 行
    
    [ERROR] undefined element declaration 's:schema'
      http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl的第 15 行
    
    [ERROR] undefined element declaration 's:schema'
      http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl的第 61 行
    
    [ERROR] undefined element declaration 's:schema'
      http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl的第 101 行
    
    Exception in thread "main" com.sun.tools.internal.ws.wscompile.AbortException
            at com.sun.tools.internal.ws.processor.modeler.wsdl.JAXBModelBuilder.bind(JAXBModelBuilder.java:129)
            at com.sun.tools.internal.ws.processor.modeler.wsdl.WSDLModeler.buildJAXBModel(WSDLModeler.java:2283)
            at com.sun.tools.internal.ws.processor.modeler.wsdl.WSDLModeler.internalBuildModel(WSDLModeler.java:183)
            at com.sun.tools.internal.ws.processor.modeler.wsdl.WSDLModeler.buildModel(WSDLModeler.java:126)
            at com.sun.tools.internal.ws.wscompile.WsimportTool.buildWsdlModel(WsimportTool.java:429)
            at com.sun.tools.internal.ws.wscompile.WsimportTool.run(WsimportTool.java:190)
            at com.sun.tools.internal.ws.wscompile.WsimportTool.run(WsimportTool.java:168)
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
            at java.lang.reflect.Method.invoke(Method.java:497)
            at com.sun.tools.internal.ws.Invoker.invoke(Invoker.java:159)
            at com.sun.tools.internal.ws.WsImport.main(WsImport.java:42)
    
    

    解决方法:

     1) 打开 http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl , 页面另存为xml文件;
     2)将所有的
    <s:element ref="s:schema" />
    <s:any />
    改成 <s:any minOccurs="2" maxOccurs="2"/>,一共有三处需要修改,建议查找<s:element ref="s:schema" />,修改时把<s:any />也要删掉
    3)运行命令
    wsimport -keep D:\WeatherWS.xml
    	
    需要注意的是,要修改WeatherWS.java文件中wsdl的url,因为我们用的是本地文件生成的,要修改成网站的
    url:http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl
    

    image.png

    image.png

    然后就可以调用生成的代码weatherWSSoap.getWeather(cityName,null);,来查询天气:

    public static String buildXml(Map<String,String> map) {
            String result = "";
            String msgType = map.get("MsgType").toString().toUpperCase();
            String content = map.get("Content");
            if("TEXT".equals(msgType)){
                /*查询天气*/
                if(content.contains("天气") && !"".equals(content)){
                    if(content.contains(":")){
                        String cityName = content.substring(content.lastIndexOf(":")+1,content.length());
                        WeatherInfo weather = new WeatherInfo();
                        String weaInfo = weather.getWeatherInfo(cityName);
                        result = buildTextMessage(map,weaInfo);
                    }else{
                        String notice = "查询天气的正确姿势: 天气:城市名\n请客官输入正确的格式哟~";
                        result = buildTextMessage(map,notice);
                    }
                }else{
                    result = buildTextMessage(map,"");
                }
            }
            return result;
        }
    
    package weChatServlet;
    
    import cn.com.webxml.ArrayOfString;
    import cn.com.webxml.WeatherWS;
    import cn.com.webxml.WeatherWSSoap;
    
    import java.util.List;
    
    /**
     * 调用weather 的webservice, 并处理json数据
     */
    public class WeatherInfo {
        public String getWeatherInfo(String cityName){
            /*实例化工厂WeatherWS  创建实例WeatherWSSoap  调用实例的方法getWeather()*/
            WeatherWS weatherWS = new WeatherWS();
            WeatherWSSoap weatherWSSoap = weatherWS.getWeatherWSSoap();
    
            /*响应信息*/
            StringBuffer sb = new StringBuffer();
    
            /*获取指定城市的天气预报*/
            ArrayOfString weatherInfo = weatherWSSoap.getWeather(cityName,null);
            List<String> listWeatherInfo = weatherInfo.getString();
            for(String str :  listWeatherInfo){
                if(!str.contains(".gif")){
                    sb.append(str);
                    sb.append("\n");
                    System.out.println(str);
                    System.out.println("------------------------");
                }
            }
            return sb.toString();
        }
    }
    
    

    结果如下: 查询天气的格式为: 天气:城市名

    image.png

    image.png

    9.翻译功能开发(有道智云)

    网络上有很多翻译API,大家可以根据自己的需求进行选择。这里我们选择应用比较广泛的,翻译功能还比较不错的有道翻译API,下面对这种API的相关信息进行分析。

    2017.12.31之前,我们可以使用有道翻译API

    关于有道翻译API使用 ,请参考链接: http://blog.csdn.net/nomasp/article/details/48995039

    有道翻译API key申请,请参考链接: http://fanyi.youdao.com/openapi?path=data-mode

    image.png

    有道翻译API已经禁用,所以我们要去有道智云寻求方法(http://ai.youdao.com/index.s), 首先需要注册,注册之后账户里默认有100元的额度,使用有道智云是有字数限制的,资费如下:

    image.png

    image.png

    然后我们需要创建应用, 绑定服务之后,就可以生成appKey和密钥 , 这两个数据在调用API中会用到

    image.png

    有道智云的API文档: http://ai.youdao.com/docs/doc-trans-api.s#p01

    下边就详细介绍如何调用有道智云API

    1) 接口调用参数:

    调用API需要向接口发送以下字段来访问服务。

    字段名 类型 含义 必填 备注
    q text 要翻译的文本 True 必须是UTF-8编码
    from text 源语言 True 语言列表 (可设置为auto)
    to text 目标语言 True 语言列表 (可设置为auto)
    appKey text 应用 ID True 可在 应用管理 查看
    salt text 随机数 True
    sign text 签名,通过md5(appKey+q+salt+密钥)生成 True appKey+q+salt+密钥的MD5值

    签名生成方法如下:

    1. 将请求参数中的 appKey,翻译文本 q (注意为UTF-8编码),随机数 salt密钥 (可在 应用管理 查看), 按照 appKey+q+salt+密钥 的顺序拼接得到字符串 str
    2. 对字符串 str 做md5,得到32位大写的 sign (参考Java生成MD5示例)

    注意:

    1. 请先将需要翻译的文本转换为 UTF-8 编码
    2. 在发送 HTTP 请求之前需要对各字段做 URL encode。
    3. 在生成签名拼接 appKey+q+salt+密钥 字符串时,q 不需要做 URL encode,在生成签名之后,发送 HTTP 请求之前才需要对要发送的待翻译文本字段 q 做 URL encode。

    我们代码中需要使用CloseableHttpClient, 所以需要引入两个jar包 httpclient-4.5.3.jar 和 httpcore-4.4.6.jar

    2) 输出结果

    返回的结果是json格式,包含字段与FROM和TO的值有关,具体说明如下:

    字段名 类型 含义 备注
    errorCode text 错误返回码 一定存在
    query text 源语言 查询正确时,一定存在
    translation text 翻译结果 查询正确时一定存在
    basic text 词义 基本词典,查词时才有
    web text 词义 网络释义,该结果不一定存在
    l text 源语言和目标语言 一定存在
    dict text 词典deeplink 查询语种为支持语言时,存在
    webdict text webdeeplink 查询语种为支持语言时,存在
    3)支持的语言表
    语言 代码
    中文 zh-CHS
    日文 ja
    英文 EN
    韩文 ko
    法文 fr
    俄文 ru
    葡萄牙文 pt
    西班牙文 es
    4) 错误代码列表
    错误码 含义
    101 缺少必填的参数,出现这个情况还可能是et的值和实际加密方式不对应
    102 不支持的语言类型
    103 翻译文本过长
    104 不支持的API类型
    105 不支持的签名类型
    106 不支持的响应类型
    107 不支持的传输加密类型
    108 appKey无效,注册账号, 登录后台创建应用和实例并完成绑定, 可获得应用ID和密钥等信息,其中应用ID就是appKey( 注意不是应用密钥)
    109 batchLog格式不正确
    110 无相关服务的有效实例
    111 开发者账号无效,可能是账号为欠费状态
    201 解密失败,可能为DES,BASE64,URLDecode的错误
    202 签名检验失败
    203 访问IP地址不在可访问IP列表
    301 辞典查询失败
    302 翻译查询失败
    303 服务端的其它异常
    401 账户已经欠费停
    5) DEMO

    result = EntityUtils.toString(httpEntity, “utf-8”); 返回的是JSON对象格式的字符串,如下:

    {
    	"web":[
    		{
    			"value":["Hello","How do you do","hi"],
    			"key":"你好"
    		},
    
    		{
                "value":["How are you","How Do You Do","Harvey, how are you Harvey"],
                "key":"你好吗"
    		},
    
    		{
                "value":["Teacher Kim Bong-du","My Teacher Mr Kim","Seonsaeng Kim Bong-du"],
                "key":"老师你好"
    		}
    	],
    
    	"query":"你好",
    
    	"translation":[
    		"How are you"
    	],
    
    	"errorCode":"0",
    
    	"dict":{"url":"yddict://m.youdao.com/dict?le=eng&q=%E4%BD%A0%E5%A5%BD"},
    
    	"webdict":{"url":"http://m.youdao.com/dict?le=eng&q=%E4%BD%A0%E5%A5%BD"},
    
    	"basic":{
    		"explains":["hello","hi"]
    	},
    
    	"l":"zh-CHS2EN"
    }
    

    所以需要将JSON字符串转换为实体类对象DTO

    /*处理JSON字符串为实体对象TranslateResponseDto*/
    TranslateResponseDto dto = new TranslateResponseDto();
    dto=JSON.parseObject(result, TranslateResponseDto.class);
    

    image.png

    YouDaoAPI.java

    package translate;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONArray;
    import com.alibaba.fastjson.JSONObject;
    import org.apache.http.HttpEntity;
    import org.apache.http.client.entity.UrlEncodedFormEntity;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.message.BasicNameValuePair;
    import org.apache.http.util.EntityUtils;
    
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    
    public class YouDaoAPI {
        public String translate(String q)throws Exception{
            //应用申请的key
            String appKey ="2d156317f9da5d91";
            //要翻译的文本 必须是UTF-8编码
            String query = q;
            //随机数
            String salt = String.valueOf(System.currentTimeMillis());
            //源语言
            String from = "auto";
            //目标语言
            String to = "auto";
            //签名,通过md5(appkey+q+salt+密钥)生成
            String sign = md5(appKey + query + salt+"PXwLK5mGbARMsbtjIfpZBM7sDwt40YAL");
    
            Map params = new HashMap();
            params.put("q", query);
            params.put("from", from);
            params.put("to", to);
            params.put("sign", sign);
            params.put("salt", salt);
            params.put("appKey", appKey);
            return requestForHttp("http://openapi.youdao.com/api", params);
        }
    
        public  String requestForHttp(String url,Map requestParams) throws Exception{
            String result = null; //翻译后的JSON字符串
            StringBuffer sb = new StringBuffer(); //处理后的返回结果
    
            CloseableHttpClient httpClient = HttpClients.createDefault();
            /**HttpPost*/
            HttpPost httpPost = new HttpPost(url);
    
            List params = new ArrayList();
            Iterator it = requestParams.entrySet().iterator();
            while (it.hasNext()) {
                Entry<String, String> en = (Entry)it.next();
                String key = en.getKey();
                String value = en.getValue();
                if (value != null) {
                    params.add(new BasicNameValuePair(key, value));
                }
            }
            httpPost.setEntity(new UrlEncodedFormEntity(params,"UTF-8"));
            /**HttpResponse*/
            CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
            try{
                HttpEntity httpEntity = httpResponse.getEntity();
                result = EntityUtils.toString(httpEntity, "utf-8");
                //EntityUtils.consume(httpEntity);
    
                /*处理JSON字符串为实体对象TranslateResponseDto*/
                TranslateResponseDto dto = new TranslateResponseDto();
                dto=JSON.parseObject(result, TranslateResponseDto.class);
    
                /*遍历实体类的每个字段,拼接为字符串返回给用户*/
                if(!"".equals(dto.getQuery()) && dto.getQuery() != null){
                    sb.append("原文:"+dto.getQuery() + "\n");
                }
                if(!"".equals(dto.getTranslation()) && dto.getTranslation() != null){
                    sb.append("翻译结果:");
                    List<String> translation = dto.getTranslation();
                    for(String s : translation){
                        sb.append(s + "\n");
                    }
                }
                if(!"".equals(dto.getBasic()) && dto.getBasic() != null){
                    Map<String, List<String>> basic = dto.getBasic();
                    sb.append("词义:");
                    for (Map.Entry<String, List<String>> entry : basic.entrySet()) {
                        sb.append(entry.getValue()+ "\n");
                    }
                }
                if(!"".equals(dto.getErrorCode()) && dto.getErrorCode() != null && !"0".equals(dto.getErrorCode())){
                    if("103".equals(dto.getErrorCode())){
                        sb.append("翻译出错:翻译文本过长\n");
                    }else{
                        sb.append("翻译出错:错误代码为"+dto.getErrorCode()+"\n");
                    }
                }
    
    
            }finally{
                try{
                    if(httpResponse!=null){
                        httpResponse.close();
                    }
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            return sb.toString();
        }
    
        /**
         * 生成32位MD5摘要
         * @param string
         * @return
         */
        public static String md5(String string) {
            if(string == null){
                return null;
            }
            char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                    'A', 'B', 'C', 'D', 'E', 'F'};
    
            try{
                byte[] btInput = string.getBytes("utf-8");
                /** 获得MD5摘要算法的 MessageDigest 对象 */
                MessageDigest mdInst = MessageDigest.getInstance("MD5");
                /** 使用指定的字节更新摘要 */
                mdInst.update(btInput);
                /** 获得密文 */
                byte[] md = mdInst.digest();
                /** 把密文转换成十六进制的字符串形式 */
                int j = md.length;
                char str[] = new char[j * 2];
                int k = 0;
                for (byte byte0 : md) {
                    str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                    str[k++] = hexDigits[byte0 & 0xf];
                }
                return new String(str);
            }catch(NoSuchAlgorithmException | UnsupportedEncodingException e){
                return null;
            }
        }
    
        /**
         * 根据api地址和参数生成请求URL
         * @param url
         * @param params
         * @return
         */
        public static String getUrlWithQueryString(String url, Map params) {
            if (params == null) {
                return url;
            }
    
            StringBuilder builder = new StringBuilder(url);
            if (url.contains("?")) {
                builder.append("&");
            } else {
                builder.append("?");
            }
    
            int i = 0;
            for (String key : (List<String>)params.keySet()) {
                String value = (String) params.get(key);
                if (value == null) { // 过滤空的key
                    continue;
                }
    
                if (i != 0) {
                    builder.append('&');
                }
    
                builder.append(key);
                builder.append('=');
                builder.append(encode(value));
    
                i++;
            }
    
            return builder.toString();
        }
    
        /**
         * 进行URL编码
         * @param input
         * @return
         */
        public static String encode(String input) {
            if (input == null) {
                return "";
            }
    
            try {
                return URLEncoder.encode(input, "utf-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
    
            return input;
        }
    }
    
    
    
    if(content.contains("翻译") && !"".equals(content)){
                    if(content.contains(":")){
                        String word = content.substring(content.lastIndexOf(":")+1,content.length()).trim();
                        YouDaoAPI translateInfo = new YouDaoAPI();
                        String weaInfo = translateInfo.translate(word);
                        result = buildTextMessage(map,weaInfo);
                    }else{
                        String notice = "翻译的正确姿势: 翻译:翻译文本\n请客官输入正确的格式哟~";
                        result = buildTextMessage(map,notice);
                    }
    

    image.png

    image.png

    10.发送emoji表情

    关于普通表情和emoji表情的处理可以参考:http://blog.csdn.net/frankcheng5143/article/details/64129433

    一般的表情是/:: 或者 [heart] 或者 空白的(其实是有特殊编码的), 所以此处我们要对emoji转换为unicode编码, 代码如下:

    package weChatServlet;
    
    import java.util.Formatter;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * emoji表情的工具类
     * 工具类中是将emoji转换为unicode编码,可以将其替换为图片,或者用空格过滤掉。
     */
    public class EmojiUtil {
    
        /**
         * 显示不可见字符的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;
        }
    }
    
    

    在messageUtil中调用emoji工具类

    String content = map.get("Content");
    
    EmojiUtil emojiUtil = new EmojiUtil();
    String unicodeEmoji = emojiUtil.filterEmoji(content); //unicode编码的Emoji
    
    if(content.contains("/:")  || content.contains("\\:")  || content.contains("[") && content.contains("]") || unicodeEmoji.contains("\\")){
        result = buildTextMessage(map,"客官发送的内容很特别哟~/:heart    " + content);
    }
    
    

    结果如下:

    image.png

    展开全文
  • 1.UnionId和OpenId 微信登录最重要的两个返回信息...开放平台下面可以申请多个应用或绑定微信公众号(必须为服务号)。如下图所示 APP登录就得申请移动应用(做了微信支付的都知道怎么回事了哈);电脑端登录就...
  • 微信的平台分为公众平台和开放平台。 公众平台就是面向公众的平台。 分为3类: 订阅号 : 发布信息用的,面向个人或企业俗称自媒体(无微信支付), 。 服务号 : 面向企业的公众平台,比如招行等企业的应用(和钱有关)。 ...
  • 微信公众号开发(一)服务器及接口的配置 关于微信公众号中的订阅号和服务的区别这里不多加讨论,网上有很多资源可以搜到,这里直接进入正题,如果是个人开发者,这里建议使用测试号进行开发学习,测试号的权限要比...
  • 微信平台提供的公众号丶服务号丶订阅号、 企业号,它们之间存在着区别和联系,以下为功能对比 公众号公众号是开发者或商家在微信公众平台上申请的应用账号,该帐号与QQ账号互通,通过公众号,商家可在微信平台上...
  • 微信公众号网页授权登录: 前端时间做了一个微信公众号的项目,就是微信公众号的菜单点击我的个人中心,就向用户授权登录 获取用户的信息,进行业务逻辑的操作,微信公众号官方文档,这是我写的文章,里面有很多...
  • 偶然接触到了公众号开发。说需要调起微信扫一扫。便查看了公众号开发文档     看完文档后,发现js是相对简单的。唯一的难点是在java后台编写的秘钥生成代码。 也就是说。首先需要获取access_token,然后...
  • 微信公众号主要面向名人、政府、媒体、企业等机构推出的合作推广业务。在这里可以通过微信渠道将品牌推广给上亿的微信用户,减少宣传成本,提高品牌知名度,打造更具影响力的品牌形象。 3. 公众平台 微信公众平...
  • 一、前言 这次的项目主要是关于微信公众号的一个开发,本人这次分配的模块是后台微信公众号的支付和退款,第一次接触微信公众的项目刚开始一脸懵逼,开发过程中遇到各种坑,所以想自己写一篇详细的关于微信公众号的...
  • 如果公司需要开发小程序,首先要在公司微信公众号里面申请小程序,快速开通并认证之后才能使用。微信最新推出,只要认证过微信公众号,那么接着认证微信小程序就可以复用微信公众号资质注册小程序流,快速注册认证小...
  • 微信小程序与微信公众号同一用户登录问题 最近在做微信小程序与微信公众号登录合并的接口。整理相关资料以及个人认识的心得写了这篇文章与大家一起分享。 首先,简单说下我遇到的问题是我们的程序调用微信...
  • java开发微信公众号的环境搭建 前言:这段时间接触了下微信公众号的开发,回顾下学习的过程,做了学习的总结。微信公众号的开发有两种模式,第一种微信公众号提供的编辑模式,使用起来还是挺方便的。可以进行信息...
1 2 3 4 5 ... 20
收藏数 261,799
精华内容 104,719
关键字:

微信公众号