session存储 微信开发_为什么退出微信程序微信公众号中存的session也没有了 - CSDN
  • 微信页面不能使用session,不知道如何才能在页面中使用session,请大神不吝赐教!!!!
  • session存储在服务器端的,那么区别每个用户的session就需要使用客户端的cookie,微信服务器是不发送cookie到开发者服务器,所以基于cookie的session无法使用。但是只要为每个用户设置一个唯一的session_id,也可以...

        session是存储在服务器端的,那么区别每个用户的session就需要使用客户端的cookie,微信服务器是不发送cookie到开发者服务器,所以基于cookie的session无法使用。

        但是只要为每个用户设置一个唯一的session_id,也可以达到同样的效果。

        每个人微信号是唯一的,所以我们可以使用微信号作为用户的session_id,也可以将其md5加密后使用。

        如下将ToUserName设置为session_id

      $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
      if(!empty($postStr)){
          $msg = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
          $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];  if(!empty($postStr)){        
          $msg = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
          //设置session_id
          session_id($msg['ToUserName']);
          session_start();
      }






    展开全文
  • 微信公众平台开发教程(八)Session处理  在微信窗口,输入的信息有限,我们需要将一些信息分多次请求。 比如:在进行用户绑定时,我们需要输入用户的相关信息,比如:用户名、密码,或者姓名、电话号码,服务端...

    微信公众平台开发教程(八)Session处理 

    在微信窗口,输入的信息有限,我们需要将一些信息分多次请求。

    比如:在进行用户绑定时,我们需要输入用户的相关信息,比如:用户名、密码,或者姓名、电话号码,服务端验证通过,即可将系统用户与微信用户绑定。

    然后,此微信账户就有一定的功能权限了,可以查积分,消费记录等。服务号:招商银行信用卡,就有很多功能。

    微信客户端无法缓存信息,而且输入信息有限,需要进行多次请求,在服务端保存当前会话状态。这就需要Session。

    本文以用户认证,绑定账号为例,来说明具体处理。 

    一、创建通用的Session处理机制。

    为了更好的说明原理,便于扩展,我们来自己设计Session。当然,这里也可以使用System.Web.SessionState.HttpSessionState,这是Web常用的Session机制。

    1、自定义Session

    用于存储会话片段以及相关数据。

    复制代码
        class Session
        {
            /// <summary>
            /// 缓存hashtable
            /// </summary>
            private static Hashtable mDic = new Hashtable();
            /// <summary>
            /// 添加
            /// </summary>
            /// <param name="key">key</param>
            /// <param name="value">value</param>
            public static void Add(string key, object value)
            {
                mDic[key] = value;
            }
            /// <summary>
            /// 移除
            /// </summary>
            /// <param name="key">key</param>
            public static void Remove(string key)
            {
                if (Contains(key))
                {
                    mDic.Remove(key);
                }
            }
            /// <summary>
            /// 设置值
            /// </summary>
            /// <param name="key"></param>
            /// <param name="value"></param>
            public static void Set(string key, object value)
            {
                mDic[key] = value;
            }
            /// <summary>
            /// 获取值
            /// </summary>
            /// <param name="key"></param>
            /// <returns></returns>
            public static object Get(string key)
            {
                return mDic[key];
            }
            /// <summary>
            /// 是否含有
            /// </summary>
            /// <param name="key">key</param>
            /// <returns>bool</returns>
            public static bool Contains(string key)
            {
                return mDic.ContainsKey(key);
            }
            /// <summary>
            /// 清空所有项
            /// </summary>
            public static void Clear()
            {
                mDic.Clear();
            }
        }
    复制代码

    2、操作类型

    记录具体的操作类型,标识当前会话的具体操作

    复制代码
        /// <summary>
        /// 操作类型
        /// </summary>
        enum Operation
        {
            /// <summary>
            /// 认证
            /// </summary>
            Auth,
            /// <summary>
            /// 添加用户
            /// </summary>
            CreateUser
        }
    复制代码

    3、操作过程枚举

    用于标识当前操作,处于哪一个阶段,不同阶段做不同的处理。

    复制代码
        /// <summary>
        /// 操作过程
        /// </summary>
        enum OperationStage
        {
            /// <summary>
            /// 默认
            /// </summary>
            Default,
            /// <summary>
            /// 第一步
            /// </summary>
            First,
            /// <summary>
            /// 第二步
            /// </summary>
            Second,
            /// <summary>
            /// 第三步
            /// </summary>
            Third
        }
    复制代码

    4、Session缓存项

    缓存记录的项,这里面记录了操作类型、操作步骤以及会话对象。为了便于进行Session管理,还增加了最后访问时间,是否自动清除标识。

    复制代码
        class SessionItem
        {
            /// <summary>
            /// 操作类型
            /// </summary>
            public Operation Oper { get; set; }
            /// <summary>
            /// 当前步骤
            /// </summary>
            public OperationStage Stage { get; set; }
            /// <summary>
            /// 数据对象
            /// </summary>
            public object Data { get; set; }
            /// <summary>
            /// 是否自动删除
            /// </summary>
            public bool AutoRemove
            {
                get;
                set;
            }
            /// <summary>
            /// 最后更新时间
            /// </summary>
            public DateTime UpdateTime { get; set; }
        }
    复制代码

     

    二、就要在消息处理中,加入Session处理。

    1、增加缓存项数据对象

    这个对象,记录用户在会话过程中,录入的相关信息。也是作为业务处理数据提供对象。

    复制代码
        class AuthSessionItem
        {
            /// <summary>
            /// 用户名
            /// </summary>
            public string FromUserName { get; set; }
            /// <summary>
            /// 账号
            /// </summary>
            public string Code { get; set; }
            /// <summary>
            /// 唯一标识
            /// </summary>
            public string ID { get; set; }
        }
    复制代码

     

    2、认证处理过程

    1)开始进入认证,根据认证关键字进行标识,启动会话,并缓存相关数据

    2)提示录入个人账号信息

    3)微信用户录入个人账号,服务端记录账号信息,并提示录入员工卡号

    4)微信用户录入卡号信息,服务端记录卡号信息,并调用具体的认证逻辑

    5)用户认证通过,绑定微信OpenId,提示成功绑定信息,并清除会话。

    在认证过程中,需要对用户录入信息进行合法性验证,而且在会话过程中,支持用户退出当前操作。

    复制代码
            /// <summary>
            /// 认证用户信息
            /// </summary>
            /// <param name="tm"></param>
            /// <returns></returns>
            private bool Auth(TextMessage tm, ref string response)
            {
                SessionItem sessionItem = null;
                if (string.Equals(tm.Content, "Auth", StringComparison.OrdinalIgnoreCase))
                {
                    //检查是否已经认证,业务组件验证
                    if (UserManager.IsAuth(tm.FromUserName))
                    {
                        //如果已经认证,提示
                        tm.Content = "您已经认证过了,无需再次认证!";                    
                    }
                    else
                    {
                        AuthSessionItem authSessionItem = new AuthSessionItem();
                        authSessionItem.FromUserName = tm.FromUserName;
    
                        sessionItem.Oper = Operation.Auth;
                        sessionItem.Stage = OperationStage.First;
                        sessionItem.Data = authSessionItem;
                        Session.Set(tm.FromUserName, sessionItem);
    
                        //输入账号,并将数据和步骤,写入缓存
                        tm.Content = "请输入您的个人账号";
                    }
    
                    response = ResponseText(tm);
                    return false;
                }
    
                //从Session获取用户信息
                sessionItem = Session.Get(tm.FromUserName) as SessionItem;
                //如果会话存在,且当前操作为用户认证
                if (sessionItem != null && sessionItem.Oper == Operation.Auth)
                {
                    if (sessionItem.Stage == OperationStage.First)
                    {
                        tm.Content = tm.Content.Trim();
                        if (string.IsNullOrEmpty(tm.Content) || tm.Content.Length > 20)
                        {
                            tm.Content = "输入的个人账号不合法,请重新输入。";
                            response = ResponseText(tm);
                            return false;
                        }
                        AuthSessionItem authSessionItem = sessionItem.Data as AuthSessionItem;
                        if (authSessionItem != null)
                        {
                            authSessionItem.Code = tm.Content;
                        }
    
                        //更新缓存
                        sessionItem.Stage = OperationStage.Second;
                        Session.Set(tm.FromUserName, sessionItem);
                        tm.Content = "请输入您的员工卡号!\n退出认证请输入Exit。";
                        response = ResponseText(tm);  
                    }
                    else if (sessionItem.Stage == OperationStage.Second)
                    {
                        string cardNum = null;
                        if (!Common.TryConvertToCardNum(tm.Content, out cardNum))
                        {                       
                            tm.Content = "员工卡号不合法,请重新输入。\n退出认证请输入Exit。";
                            response = ResponseText(tm);
                            return false;
                        }
                        AuthSessionItem authSessionItem = sessionItem.Data as AuthSessionItem;
                        if (authSessionItem != null)
                        {
                            authSessionItem.ID = cardNum;
                        }
                        //认证
                        string message;
                        if (UserManager.Authenticate(authSessionItem, out message))
                        {
                            tm.Content = "祝贺您,已经认证成功,可以使用通讯录的查询功能呢。";
                            //清理缓存
                            Session.Remove(tm.FromUserName);
                            response = ResponseText(tm);
                            return true;
                        }
                        else if (!string.IsNullOrEmpty(message))
                        {
                            tm.Content = message;
                        }
                        else
                        {
                            tm.Content = "您输入的信息有误。\n重新认证请输入:Auth!";
                        }
                        //过程结束:清理Session
                        Session.Remove(tm.FromUserName);
                        response = ResponseText(tm);
                        return false;
                    }
                }
    
                return false;
            }
    复制代码

    3、退出会话,清理Session

    在认证过程中,用户可以通过命令,强制退出当前操作,在退出当前操作时,需要清理会话信息。

    复制代码
            /// <summary>
            /// 退出,并清理Session
            /// </summary>
            /// <param name="tm"></param>
            /// <param name="response"></param>
            /// <returns></returns>
            private bool Exit(TextMessage tm, ref string response)
            {
                //退出
                if (string.Equals(tm.Content, "Exit", StringComparison.OrdinalIgnoreCase))
                {
                    //清除Session
                    Session.Remove(tm.FromUserName);
                    tm.Content = "您已退出当前操作,请执行其他操作。";
                    response = ResponseText(tm);
                    return true;
                }
    
                return false;
            }
    复制代码

     三、用户认证通过,绑定微信账户

    用户认证通过,并绑定微信OpenId,通过OpenId即可查询通讯录、查询个人积分以及消费记录等操作了。用户认证是一个身份认证过程,也是一个用户绑定过程。用户身份认证通过,即可通过微信账号查询具体信息了。这时候业务层可以根据微信分配的OpenId直接查询用户相关信息。

    四、后记

    通过这种方法,公众账号,可以通过小小的文本输入框,实现更多、更复杂的业务应用。当然,还是通过提供网页来进行信息录入,更直观便捷。 

    展开全文
  • 将openId存入session,再次取得时候拿不到poinId... String openId=(String)map.get("FromUserName"); logger.info("解析出来的openId"+openId); session.setAttribute("openId", openId); String ...
  • 微信开发与代码的编写(一) 微信开发环境的搭建 目前移动开发处于比较火的的趋势,很多的开发者都跃跃欲试,目前移动App开发领域主要分为以下几种类型    我在平时的工作中接触得比较多的就是基于Android的Native...

    微信开发与代码的编写(一)

    微信开发环境的搭建

    目前移动开发处于比较火的的趋势,很多的开发者都跃跃欲试,目前移动App开发领域主要分为以下几种类型

      

      我在平时的工作中接触得比较多的就是基于Android的Native App开发和基于微信公众号的Light App开发,今天就来带领大家快速进入微信公众号的开发领域.

    微信开发环境搭建

      工欲善其事,必先利其器。要做微信公众号开发,那么要先准备好两样必不可少的东西:

      1、要有一个用来测试的公众号。

      2、用来调式代码的开发环境。

     

    注册测试公众号

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

      我们所说的微信公众号开发指的是订阅号和服务号。

      关于订阅号和服务器的区别,官方是这样解释的

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

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

      个人订阅号有一些接口是没有权限的,也就是说个人订阅号无法调用一些高级的权限接口,下图就是一个我的个人订阅号所具备权限列表,如下图所示:

      

      而一些高级接口,如生成二维码、网页授权、自定义菜单、微信支付这样的接口权限个人订阅号是没有调用权限的,如上图红色框起来的那些接口,个人订阅号都无法调用。

      幸运的是,微信公众平台提供了测试公众账号,测试公众号的注册地址为:http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login,只需要到这个页面,点击登录,并用自己的微信客户端扫码,并授权登录,就可以获得属于自己的测试公众号。测试公众号具备几乎所有的接口,所以平时学习微信公众号开发时,就可以去注册一个测试公众号,然后使用这个测试公众号做开发就可以了。不废话了,还是先注册一个测试公众号吧

      访问http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login,注册一个测试公众号。

      注册测试公众号的步骤如下图所示:

      

        

       用微信扫描上述的二维码进行登录,登录成功后,就可以看到腾讯分配给我们的测试公众号的信息了,如下图所示:

      

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

      

      可以看到,测试公众号拥有大部分的接口调用权限,因此用测试公众号来学习微信开发是完全可以的。

     

    搭建微信本地调试环境

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

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

      

      从http://natapp.cn/网站上下载ngrok客户端,如下图所示:

      

       下载完成后,得到一个压缩包,解压压缩包后,得到一个文件夹,里面有如下图所示的几个文件:

      

      打开CMD命令行窗口,进入到ngrok_windows目录下,然后输入如下命令:

      ngrok -config ngrok.cfg -subdomain xdp 8080,如下图所示:

      

      xdp是我自己自定义的一个域名, 8080为本地服务器的运行端口,执行完上述命令后,我们就可以本地的127.0.0.1:8080服务器映射到外网了,如下图所示:

      

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

      

      

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

     

    微信公众平台的基本原理

      在开始做之前,先简单介绍了微信公众平台的基本原理。

      微信服务器就相当于一个转发服务器,终端(手机、Pad等)发起请求至微信服务器,微信服务器然后将请求转发给我们的应用服务器。应用服务器处理完毕后,将响应数据回发给微信服务器,微信服务器再将具体响应信息回复到微信App终端。

      通信协议为:HTTP

      数据传输格式为:XML

      具体的流程如下图所示:

      

      来一张更加直观的图吧:

      

      我们需要做的事情,就是对微信服务器转发的HTTP请求做出响应。具体的请求内容,我们按照特定的XML格式去解析,处理完毕后,也要按照特定的XML格式返回。

     

    微信公众号接入

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

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

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

      第1步中服务器配置包含服务器地址(URL)、Token和EncodingAESKey。

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

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

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

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

      

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

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

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

      使用IDE(Eclipse或者IntelliJ IDEA)创建一个JavaWeb项目,这里我使用的是IntelliJ IDEA,项目目录结构如下图所示:

      

      编写一个servlevt,在其中的doGet方法中定义校验方法,具体代码如下:

    package me.gacl.wx.web.servlet;
      
      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.IOException;
      import java.security.MessageDigest;
      import java.security.NoSuchAlgorithmException;
      import java.util.Arrays;
      
      /**
       * Created by xdp on 2016/1/25.
       * 使用@WebServlet注解配置WxServlet,urlPatterns属性指明了WxServlet的访问路径
       */
      @WebServlet(urlPatterns="/WxServlet")
      public class WxServlet extends HttpServlet {
      
          /**
           * Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
           * 比如这里我将Token设置为gacl
           */
         private final String TOKEN = "gacl";
      
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      
          }
      
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              System.out.println("开始校验签名");
              /**
               * 接收微信服务器发送请求时传递过来的4个参数
               */
              String signature = request.getParameter("signature");//微信加密签名signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
              String timestamp = request.getParameter("timestamp");//时间戳
              String nonce = request.getParameter("nonce");//随机数
              String echostr = request.getParameter("echostr");//随机字符串
              //排序
              String sortString = sort(TOKEN, timestamp, nonce);
              //加密
              String mySignature = sha1(sortString);
              //校验签名
             if (mySignature != null && mySignature != "" && mySignature.equals(signature)) {
                  System.out.println("签名校验通过。");
                  //如果检验成功输出echostr,微信服务器接收到此输出,才会确认检验完成。
                  //response.getWriter().println(echostr);
                  response.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();
          }
      
          /**
           * 将字符串进行sha1加密
           *
           * @param str 需要加密的字符串
           * @return 加密后的内容
           */
         public String sha1(String str) {
              try {
                  MessageDigest digest = MessageDigest.getInstance("SHA-1");
                  digest.update(str.getBytes());
                  byte messageDigest[] = digest.digest();
                  // Create Hex String
                  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 "";
         }
     }

      我这里用的Servlet3.0,使用Servlet3.0的好处就是可以直接使用@WebServlet注解映射Servlet的访问路径,不再需要在web.xml文件中进行配置.

      将WxStudy项目部署到Tomcat服务器中运行,直接启动项目,然后用ngrok将本地8080端口映射到外网(如何使用ngrok请参考博客《微信开发学习总结(一)——微信开发环境搭建》)。如下图所示:

      

      测试是否可以通过http://xdp.ngrok.natapp.cn地址正常访问,测试结果如下:

      

      可以看到,我们的项目已经可以被外网正常访问到了。

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

    点击提交按钮,页面会提示配置成功,

      

      IDE的控制台中输出了校验通过的信息,如下图所示:

      

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

     

    access_token管理

    access_token介绍

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

      关于access_token,在微信公众平台开发者文档上的获取接口调用凭据有比较详细的介绍:access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token,开发者需要妥善保存access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。并且每天调用获取access_token接口的上限是2000次。

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

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

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

    微信公众平台提供的获取access_token的接口

      关于access_token的获取方式,在微信公众平台开发者文档上有说明,公众号可以调用一个叫"获取access token"的接口来获取access_token。

      获取access token接口调用请求说明

        http请求方式: GET

        请求的URL地址:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
           

      我们可以看到,调用过程中需要传递appID和AppSecret,appID和AppSecret是在申请公众号的时候自动分配给公众号的,相当于公众号的身份标示,使用微信公众号的注册帐号登录到腾讯提供的微信公众号管理后台就可以看到自己申请的公众号的AppID和AppSecret,如下图所示:

                 

      这是我申请公众号测试帐号时分配到的AppID和AppSecret。

    获取access_token方案以及具体实现

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

      

      下面正式开始在工程中实现以上思路,因为返回的数据都是json格式,这里会用到阿里的fastjson库,为构造请求和处理请求后的数据序列化和反序列化提供支持。

      1.定义一个AccessToken实体类

     package me.gacl.wx.entry;
     
     /**
       * AccessToken的数据模型
      * Created by xdp on 2016/1/25.
      */
     public class AccessToken {
     
         //获取到的凭证
         private String accessToken;
        //凭证有效时间,单位:秒
         private int expiresin;
     
         public String getAccessToken() {
             return accessToken;
         }
     
         public void setAccessToken(String accessToken) {
            this.accessToken = accessToken;
         }
     
         public int getExpiresin() {
             return expiresin;
         }
     
         public void setExpiresin(int expiresin) {
             this.expiresin = expiresin;
         }
     }

         2.定义一个AccessTokenInfo类,用于存放获取到的AccessToken,代码如下:

     package me.gacl.wx.Common;
     
      import me.gacl.wx.entry.AccessToken;
     
     /**
      * Created by xdp on 2016/1/25.
      */
     public class AccessTokenInfo {
     
         //注意是静态的
         public static AccessToken accessToken = null;
     }

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

     package me.gacl.wx.util;
     
     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;
     
     /**
     * 访问网络用到的工具类
      */
     public class NetWorkHelper {
     
         /**
          * 发起Https请求
          * @param reqUrl 请求的URL地址
          * @param requestMethod
          * @return 响应后的字符串
          */
         public String getHttpsResponse(String reqUrl, String requestMethod) {
             URL url;
             InputStream is;
             String resultData = "";
             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) {
                     resultData += inputLine + "\n";
                 }
                 System.out.println(resultData);
     
             } catch (Exception e) {
                 e.printStackTrace();
             }
             return resultData;
         }
     
         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 {
     
             }
         };
     }

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

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

     package me.gacl.wx.web.servlet;
     
     import com.alibaba.fastjson.JSON;
     import com.alibaba.fastjson.JSONObject;
     import me.gacl.wx.Common.AccessTokenInfo;
     import me.gacl.wx.entry.AccessToken;
     import me.gacl.wx.util.NetWorkHelper;
     
     import javax.servlet.ServletException;
     import javax.servlet.annotation.WebInitParam;
     import javax.servlet.annotation.WebServlet;
     import javax.servlet.http.HttpServlet;
     
     /**
      * 用于获取accessToken的Servlet
      * Created by xdp on 2016/1/25.
      */
     @WebServlet(
             name = "AccessTokenServlet",
             urlPatterns = {"/AccessTokenServlet"},
             loadOnStartup = 1,
             initParams = {
                     @WebInitParam(name = "appId", value = "wxbe4d433e857e8bb1"),
                     @WebInitParam(name = "appSecret", value = "ccbc82d560876711027b3d43a6f2ebda")
             })
     public class AccessTokenServlet extends HttpServlet {
     
         @Override
         public void init() throws ServletException {
             System.out.println("启动WebServlet");
             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);
                                 //Thread.sleep(10 * 1000);//10秒钟获取一次
                             } 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) {
     
                             }
                         }
                     }
     
                 }
             }).start();
         }
     
         /**
          * 获取access_token
          *
          * @return AccessToken
          */
         private AccessToken getAccessToken(String appId, String appSecret) {
             NetWorkHelper netHelper = new NetWorkHelper();
             /**
              * 接口地址为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.setAccessToken(json.getString("access_token"));
             token.setExpiresin(json.getInteger("expires_in"));
             return token;
         }
     }

            AccessTokenServlet采用注解的方式进行配置
      至此代码实现完毕,将项目部署,看到控制台输出如下:

      

      为了方便看效果,可以把休眠时间设置短一点,比如10秒获取一次,然后将access_token输出。

      下面做一个测试jsp页面,并把休眠时间设置为10秒,这样过10秒刷新页面,就可以看到变化

     <%-- Created by IntelliJ IDEA. --%>
      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <%@ page import="me.gacl.wx.Common.AccessTokenInfo"%>
      <html>
       <head>
         <title></title>
       </head>
       <body>
         微信学习
         <hr/>
         access_token为:<%=AccessTokenInfo.accessToken.getAccessToken()%>
       </body>
     </html>

      

      10秒钟后刷新页面,access_token变了,如下图所示:

      

     

    接收微信服务器发送的消息并做出响应

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

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

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

      编写处理消息的工具栏,工具类代码如下:

     package me.gacl.wx.util;
      
      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;
      
      /**
       * 消息处理工具类
       * Created by xdp on 2016/1/26.
       */
      public class MessageHandlerUtil {
      
          /**
           * 解析微信发来的请求(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, "孤傲苍狼在学习和总结微信开发了,构建一条文本消息:Hello World!");
              }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数据格式
               * <xml>
                   <ToUserName><![CDATA[toUser]]></ToUserName>
                  <FromUserName><![CDATA[fromUser]]></FromUserName>
                   <CreateTime>1348831860</CreateTime>
                   <MsgType><![CDATA[text]]></MsgType>
                  <Content><![CDATA[this is a test]]></Content>
                  <MsgId>1234567890123456</MsgId>
              </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);
         }
     }

     为了方便解析微信服务器发送给我们的xml格式的数据,这里我们借助于开源框架dom4j去解析xml(这里使用的是dom4j-2.0.0-RC1.jar)

      

     

    在WxServlet的doPost方法中处理请求

      WxServlet的doPost方法的代码如下:

      /**
          * 处理微信服务器发来的消息
          */
         protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
             // TODO 接收、处理、响应由微信服务器转发的用户发送给公众帐号的消息
             // 将请求、响应的编码均设置为UTF-8(防止中文乱码)
             request.setCharacterEncoding("UTF-8");
             response.setCharacterEncoding("UTF-8");
             System.out.println("请求进入");
             String result = "";
             try {
                 Map<String,String> map = MessageHandlerUtil.parseXml(request);
                 System.out.println("开始构造消息");
                 result = MessageHandlerUtil.buildXml(map);
                 System.out.println(result);
                 if(result.equals("")){
                     result = "未正确响应";
                 }
             } catch (Exception e) {
                 e.printStackTrace();
                 System.out.println("发生异常:"+ e.getMessage());
             }
             response.getWriter().println(result);
         }
    

      到此,我们的WxServlet已经可以正常处理用户的请求并做出响应了.接下来我们测试一下我们开发好的公众号应用是否可以正常和微信用户交互

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

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

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

     

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

     <xml>
       <ToUserName><![CDATA[ojADgs0eDaqh7XkTM9GvDmdYPoDw]]></ToUserName>
       <FromUserName><![CDATA[gh_43df3882c452]]></FromUserName>
       <CreateTime>1453755900000</CreateTime>
       <MsgType><![CDATA[text]]></MsgType>
       <Content><![CDATA[孤傲苍狼在学习和总结微信开发了,构建一条文本消息:Hello World!]]></Content>
    </xml>

      测试公众号的管理后台也可以看到关注测试号的用户列表,如下图所示:

      通过这个简单的入门程序,我们揭开了微信开发的神秘面纱了.

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 微信小程序Session问题

    2018-03-01 15:40:47
    微信小程序——session 今天做微信小程序的时候,遇到一个问题,就是微信虽然提供了类似于ajax的数据通讯工具wx.request,但是因为不是浏览器,因此这个请求是不会带有sessionid的,那么今天就写一篇来记录如果解决...

    微信小程序——session

    今天做微信小程序的时候,遇到一个问题,就是微信虽然提供了类似于ajax的数据通讯工具wx.request,但是因为不是浏览器,因此这个请求是不会带有sessionid的,那么今天就写一篇来记录如果解决微信小程序session的问题。

    sessionid有什么用

    因为我只写java web多,所以就以java web为主。那么写过java web的都知道,request是一次请求,session是一个会话周期,但是后台是如何识别请求是属于哪个session的呢?这就要降到cookie和session的关系。

    我们都知道,session是服务器维护的会话,cookie是客户端(浏览器)持有的保存一些数据的,那么session实际上是通过cookie识别的。(因此当用户浏览器禁用cookie,也就代表服务器端session也被禁用了,无法得知访问是属于哪个session,因此就不存在session了。但是也不代表完全无法得到)那么当浏览器第一次访问网站,网站会在response的header中添加一个叫做Set-Cookie的字段,这个字段的含义就是让浏览器将这里面的每条数据都放到你的cookie里面,这对于用户开发者都是不可见的,浏览器会自动放入cookie,而每次请求都会带着set-cookie里面的数据在cookie里,因此服务端只需要看一下cookie里面的sessionid就知道是来源了。在jsp中是jessionid这个值。

    针对wx.request添加header

    那么知道了原理,我们就可以手动模仿浏览器自动添加cookie这一步,那么app.js里面有一个onShow/onLaunch,在这步里面,我们可以使用一个wx.request去访问我们的后台,那么后台的服务器这时候是可以拿到第一个sessionid的,那么服务端将这个sessionid回复给我们,我们就可以记录下这个header。
    首先在我们的app.js全局数据中添加header

      globalData: {
        userInfo: null,
        header: {
          'Cookie': ''
        }
      }

    这里面可以放我们的cookie,然后请求服务端返回当前会话的sessionid
    客户端代码:

    wx.request({
              url: app.globalData.requestUrl,
              data: {
                code: res.code
              },
              success: function (res) {
                let ret = res.data;
                if (ret.status == 200) {
                // 添加到全局数据的header中
                  app.globalData.header.Cookie = 'JSESSIONID=' + ret.data.sessionid;
                }
              }
            })

    服务端代码:
    ret.put("sessionid", request.getSession().getId());
    很简单就能直接获得我们的id,这样子,我们就完成了header的获取

    那么每次我们的wx.request,就要带上这个header
    代码:

        wx.request({
          url: app.globalData.requestUrl,
          // header中添加我们预先放入全局数据中的header,完成session的手动添加,那么就完成了会话的设置
          header: app.globalData.header,
          method: 'GET',
          dataType: 'json',
          success: function(res){
            console.log(res.data.data.url)
            that.setData({
              captcha: res.data.data
            })
          }
        })

    图片显示问题

    因为有些时候可能需要验证码,而验证码是放到session中的,那么如果直接选择访问服务器返回的验证码地址,可能取出来的就是null了。

    比如我们的服务器返回一个url,而这个url实际上是取session中的验证码来生成图片,那么可能就存在问题。

    因此,这就是当浏览器禁用了cookie的时候,我们可以通过URL获得sessionid

    对于java web来说,url;jessionid=就可以达到和设置header一样的效果,就能解决非request的session问题了

    总结

    两种办法:
    1、针对wx.request,采用全局数据保存sessionid的方式,手动每次请求添加进请求体中
    2、针对图片资源,采用url传session方式,将sessionid放入url,可以起到一样的效果

    展开全文
  • 上一篇《微信开发学习总结(一)——微信开发环境搭建》我们已经完成了微信开发的准备工作,准备工作完成之后,就要开始步入正题了。 一、微信公众平台的基本原理  在开始做之前,先简单介绍了微信公众平台的基本...
  • 作为一个开发JavaWeb应用的程序猿,都喜欢将用户登录后的用户信息(比如说用户id,用户名称)放入session中保存,之后在业务逻辑的开发中需要用到用户信息的时候就可以轻松又方便的从session中取到值。最近在开发微信...
  • 微信开发遇到的坑 在tp5使用session失效 问题描述:在不同控制器定义个session ,在另一个控制器却访问不到这个session 原因:tp5中的session只支持浏览器的访问,又或者是微信小程序是经过微信的中继服务器链接...
  • 微信开发之架构设计

    2014-10-30 11:45:32
     本文将讲解微信开发的前期准备,包括微信开发上的一些坑、架构上的设计、接口上需要注意的地方,全部来自自己的开发经验,如有不对,请指正。   微信开发的坑   1、微信授权  微信...
  • 由于种种原因,好吧!主要也是我放假一回家就会跟在学校是两个人,在家太懒,不想学习;在校还好,会想学习。...做的过程问题不少,今天碰到在微信公众号服务器上设置 session 结果在本页面能取到值,在其它
  • 微信进入公众号后,可以后台调取其接口获取opendid,但在规范中通常要求用户有交互才能获取,而且用户进入公众号这步是无法获取到这个交互,只有进公众号之后的点击操作才行。所以思路就是在用户点击菜单后,获取其...
  • 所有微信开发的相关内容,都需要参考官方文档。 [微信公众平台|开发文档] http://mp.weixin.qq.com/wiki/home/。 一、通过网页授权,可以获取用户微信的基本信息。 二、总共有5个步骤: 1 :用户同意授权,...
  • 对于已经熟悉了session原理的同学来说,我们都清楚:在浏览器端我们会存储一个sessionId,用它来作为凭证,在服务器端得到有关本次浏览器与服务器会话的所有信息,这些信息是储存在服务器端的存储空间中的,它完全...
  • .net C#微信公众号开发

    2020-06-17 16:32:09
    .net C#微信公众号开发 #一、开发准备工作 打开微信公众平台,主页左侧找到 “开发”栏目,选择基本配置,获取AppId,appsecret。 开发者密码需要管理员授权查看。 二、服务器配置 1、主页左侧找到 “开发”...
  • 微信小程序开发中,由wx.request()发起的每次请求对于服务端来说都是不同的一次会话,微信小程序不会把session信息带回服务端,即对应服务端不同的session,由于项目中使用session保存用户信息所以导致后续请求...
  • 做企业微信开发时,进行前后端分离,前端使用vue开发(移动端组件库使用vant),后端使用原来PC项目(spring+springmvc+JdbcTemplate)开发。前端和后端分别部署在不同的域名服务器上。 在后端进行oauth2授权登录后,将...
1 2 3 4 5 ... 20
收藏数 8,337
精华内容 3,334
关键字:

session存储 微信开发