热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

由获取微信access

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

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

背景

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

1、为了保密appsecrect,第三方需要一个access_token获取和刷新的中控服务器。而其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则会造成access_token覆盖而影响业务;
2、目前access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器对外输出的依然是老access_token,此时公众平台后台会保证在刷新短时间内,新老access_token都可用,这保证了第三方业务的平滑过渡;
3、access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的情况下,可以触发access_token的刷新流程。

简单起见,使用一个随servlet容器一起启动的servlet来实现获取access_token的功能,具体为:因为该servlet随着web容器而启动,在该servlet的init方法中触发一个线程来获得access_token,该线程是一个无线循环的线程,每隔2个小时刷新一次access_token。相关代码如下:
1)servlet代码

public class InitServlet extends HttpServlet 
{
	private static final long serialVersiOnUID= 1L;

	public void init(ServletConfig config) throws ServletException 
	{
		new Thread(new AccessTokenThread()).start();  
	}

}

2)线程代码

public class AccessTokenThread implements Runnable 
{
	public static AccessToken accessToken;
	
	@Override
	public void run() 
	{
		while(true) 
		{
			try{
				AccessToken token = AccessTokenUtil.freshAccessToken();	// 从微信服务器刷新access_token
				if(token != null){
					accessToken = token;
				}else{
					System.out.println("get access_token failed------------------------------");
				}
			}catch(IOException e){
				e.printStackTrace();
			}
			
			try{
				if(null != accessToken){
					Thread.sleep((accessToken.getExpire_in() - 200) * 1000);	// 休眠7000秒
				}else{
					Thread.sleep(60 * 1000);	// 如果access_token为null,60秒后再获取
				}
			}catch(InterruptedException e){
				try{
					Thread.sleep(60 * 1000);
				}catch(InterruptedException e1){
					e1.printStackTrace();
				}
			}
		}
	}
}

3)AccessToken代码

public class AccessToken 
{
	private String access_token;
	private long expire_in;		// access_token有效时间,单位为妙
	
	public String getAccess_token() {
		return access_token;
	}
	public void setAccess_token(String access_token) {
		this.access_token = access_token;
	}
	public long getExpire_in() {
		return expire_in;
	}
	public void setExpire_in(long expire_in) {
		this.expire_in = expire_in;
	}
}

4)servlet在web.xml中的配置

  
    initServlet
    class>com.sinaapp.wx.servlet.InitServletclass>
    0
  

因为initServlet设置了load-on-startup=0,所以保证了在所有其它servlet之前启动。

其它servlet要使用access_token的只需要调用 AccessTokenThread.accessToken即可。

引出多线程并发问题

1)上面的实现似乎没有什么问题,但是仔细一想,AccessTokenThread类中的accessToken,它存在并发访问的问题,它仅仅由AccessTokenThread每隔2小时更新一次,但是会有很多线程来读取它,它是一个典型的读多写少的场景,而且只有一个线程写。既然存在并发的读写,那么上面的代码肯定是存在问题的。

一般想到的最简单的方法是使用synchronized来处理:

public class AccessTokenThread implements Runnable 
{
	private static AccessToken accessToken;
	
	@Override
	public void run() 
	{
		while(true) 
		{
			try{
				AccessToken token = AccessTokenUtil.freshAccessToken();	// 从微信服务器刷新access_token
				if(token != null){
					AccessTokenThread.setAccessToken(token);
				}else{
					System.out.println("get access_token failed");
				}
			}catch(IOException e){
				e.printStackTrace();
			}
			
			try{
				if(null != accessToken){
					Thread.sleep((accessToken.getExpire_in() - 200) * 1000);	// 休眠7000秒
				}else{
					Thread.sleep(60 * 1000);	// 如果access_token为null,60秒后再获取
				}
			}catch(InterruptedException e){
				try{
					Thread.sleep(60 * 1000);
				}catch(InterruptedException e1){
					e1.printStackTrace();
				}
			}
		}
	}

	public synchronized static AccessToken getAccessToken() {
		return accessToken;
	}

	private synchronized static void setAccessToken(AccessToken accessToken) {
		AccessTokenThread2.accessToken = accessToken;
	}
}

accessToken变成了private,setAccessToken也变成了private,增加了同步synchronized访问accessToken的方法。

那么到这里是不是就完美了呢?就没有问题了呢?仔细想想,还是有问题,问题出在AccessToken类的定义上,它提供了public的set方法,那么所有的线程都在使用AccessTokenThread.getAccessToken()获得了所有线程共享的accessToken之后,任何线程都可以修改它的属性!!!!而这肯定是不对的,不应该的。

2)解决方法一

我们让AccessTokenThread.getAccessToken()方法返回一个accessToken对象的copy,副本,这样其它的线程就无法修改AccessTokenThread类中的accessToken了。如下修改AccessTokenThread.getAccessToken()方法即可:

	public synchronized static AccessToken getAccessToken() {
		AccessToken at = new AccessToken();
		at.setAccess_token(accessToken.getAccess_token());		
		at.setExpire_in(accessToken.getExpire_in());
		return at;
	}

也可以在AccessToken类中实现clone方法,原理都是一样的。当然setAccessToken也变成了private。

3)解决方法二

既然我们不应该让AccessToken的对象被修改,那么我们为什么不将accessToken定义成一个“不可变对象”?相关修改如下:

public class AccessToken 
{
	private final String access_token;
	private final long expire_in;		// access_token有效时间,单位为妙
	
	public AccessToken(String access_token, long expire_in)
	{
		this.access_token = access_token;
		this.expire_in = expire_in;
	}
	
	public String getAccess_token() {
		return access_token;
	}
	
	public long getExpire_in() {
		return expire_in;
	}
}

如上所示,AccessToken所有的属性都定义成了final类型了,只提供构造函数和get方法。这样的话,其他的线程在获得了AccessToken的对象之后,就无法修改了。改修改要求AccessTokenUtil.freshAccessToken()中返回的AccessToken的对象只能通过有参的构造函数来创建。同时AccessTokenThread的setAccessToken也要修改成private,getAccessToken无须返回一个副本了。

注意不可变对象必须满足下面的三个条件:

a) 对象创建之后其状态就不能修改;

b) 对象的所有域都是final类型;

c) 对象是正确创建的(即在对象的构造函数中,this引用没有发生逸出);

4)解决方法三

还有没有其他更加好,更加完美,更加高效的方法呢?我们分析一下,在解决方法二中,AccessTokenUtil.freshAccessToken()返回的是一个不可变对象,然后调用private的AccessTokenThread.setAccessToken(AccessToken accessToken)方法来进行赋值。这个方法上的synchronized同步起到了什么作用呢?因为对象时不可变的,而且只有一个线程可以调用setAccessToken方法,那么这里的synchronized没有起到"互斥"的作用(因为只有一个线程修改),而仅仅是起到了保证“可见性”的作用,让修改对其它的线程可见,也就是让其他线程访问到的都是最新的accessToken对象。而保证“可见性”是可以使用volatile来进行的,所以这里的synchronized应该是没有必要的,我们使用volatile来替代它。相关修改代码如下:

public class AccessTokenThread implements Runnable 
{
	private static volatile AccessToken accessToken;
	
	@Override
	public void run() 
	{
		while(true) 
		{
			try{
				AccessToken token = AccessTokenUtil.freshAccessToken();	// 从微信服务器刷新access_token
				if(token != null){
					AccessTokenThread2.setAccessToken(token);
				}else{
					System.out.println("get access_token failed");
				}
			}catch(IOException e){
				e.printStackTrace();
			}
			
			try{
				if(null != accessToken){
					Thread.sleep((accessToken.getExpire_in() - 200) * 1000);	// 休眠7000秒
				}else{
					Thread.sleep(60 * 1000);	// 如果access_token为null,60秒后再获取
				}
			}catch(InterruptedException e){
				try{
					Thread.sleep(60 * 1000);
				}catch(InterruptedException e1){
					e1.printStackTrace();
				}
			}
		}
	}

	private static void setAccessToken(AccessToken accessToken) {
		AccessTokenThread2.accessToken = accessToken;
	}
public static AccessToken getAccessToken() {
return accessToken;
} }

也可以这样改:

public class AccessTokenThread implements Runnable 
{
	private static volatile AccessToken accessToken;
	
	@Override
	public void run() 
	{
		while(true) 
		{
			try{
				AccessToken token = AccessTokenUtil.freshAccessToken();	// 从微信服务器刷新access_token
				if(token != null){
					accessToken = token;
				}else{
					System.out.println("get access_token failed");
				}
			}catch(IOException e){
				e.printStackTrace();
			}
			
			try{
				if(null != accessToken){
					Thread.sleep((accessToken.getExpire_in() - 200) * 1000);	// 休眠7000秒
				}else{
					Thread.sleep(60 * 1000);	// 如果access_token为null,60秒后再获取
				}
			}catch(InterruptedException e){
				try{
					Thread.sleep(60 * 1000);
				}catch(InterruptedException e1){
					e1.printStackTrace();
				}
			}
		}
	}

	public static AccessToken getAccessToken() {
		return accessToken;
	}
}

还可以这样改:

public class AccessTokenThread implements Runnable 
{
    public static volatile AccessToken accessToken;
    
    @Override
    public void run() 
    {
        while(true) 
        {
            try{
                AccessToken token = AccessTokenUtil.freshAccessToken();    // 从微信服务器刷新access_token
                if(token != null){
                    accessToken = token;
                }else{
                    System.out.println("get access_token failed");
                }
            }catch(IOException e){
                e.printStackTrace();
            }
            
            try{
                if(null != accessToken){
                    Thread.sleep((accessToken.getExpire_in() - 200) * 1000);    // 休眠7000秒
                }else{
                    Thread.sleep(60 * 1000);    // 如果access_token为null,60秒后再获取
                }
            }catch(InterruptedException e){
                try{
                    Thread.sleep(60 * 1000);
                }catch(InterruptedException e1){
                    e1.printStackTrace();
                }
            }
        }
    }
}

accesToken变成了public,可以直接是一个AccessTokenThread.accessToken来访问。

其实这个问题的关键是:在多线程并发访问的环境中如何正确的发布一个共享对象。

推荐阅读
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 在Kubernetes上部署JupyterHub的步骤和实验依赖
    本文介绍了在Kubernetes上部署JupyterHub的步骤和实验所需的依赖,包括安装Docker和K8s,使用kubeadm进行安装,以及更新下载的镜像等。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 【Windows】实现微信双开或多开的方法及步骤详解
    本文介绍了在Windows系统下实现微信双开或多开的方法,通过安装微信电脑版、复制微信程序启动路径、修改文本文件为bat文件等步骤,实现同时登录两个或多个微信的效果。相比于使用虚拟机的方法,本方法更简单易行,适用于任何电脑,并且不会消耗过多系统资源。详细步骤和原理解释请参考本文内容。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
author-avatar
靈幻雪月
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有