google foobar page

https://www.google.com/foobar/?eid=sfeTWdGPBIac8QXcxpXgAQ&usg=AG3vBD082_C21k4vEcaG4KspC-1eGqU7KA

Minion Labor Shifts
===================

Commander Lambda's minions are upset! They're given the worst jobs on the whole space station, and some of them are starting to complain that even those worst jobs are being allocated unfairly. If you can fix this problem, it'll prove your chops to Commander Lambda so you can get promoted!

Minions' tasks are assigned by putting their ID numbers into a list, one time for each day they'll work that task. As shifts are planned well in advance, the lists for each task will contain up to 99 integers. When a minion is scheduled for the same task too many times, they'll complain about it until they're taken off the task completely. Some tasks are worse than others, so the number of scheduled assignments before a minion will refuse to do a task varies depending on the task.  You figure you can speed things up by automating the removal of the minions who have been assigned a task too many times before they even get a chance to start complaining.

Write a function called answer(data, n) that takes in a list of less than 100 integers and a number n, and returns that same list but with all of the numbers that occur more than n times removed entirely. The returned list should retain the same ordering as the original list - you don't want to mix up those carefully-planned shift rotations! For instance, if data was [5, 10, 15, 10, 7] and n was 1, answer(data, n) would return the list [5, 15, 7] because 10 occurs twice, and thus was removed from the list entirely.

Languages
=========

To provide a Python solution, edit solution.py
To provide a Java solution, edit solution.java

Test cases
==========

Inputs:
	(int list) data = [1, 2, 3]
	(int) n = 0
Output:
	(int list) []

Inputs:
	(int list) data = [1, 2, 2, 3, 3, 3, 4, 5, 5]
	(int) n = 1
Output:
	(int list) [1, 4]

Inputs:
	(int list) data = [1, 2, 3]
	(int) n = 6
Output:
	(int list) [1, 2, 3]

Use verify [file] to test your solution and see how it does. When you are finished editing your code, use submit [file] to submit your answer. If your solution passes the test cases, it will be removed from your home folder.

2017-08-16

@Autowired vs @Resource vs @Inject 的区别

为了实现依赖注入 DI 而引入,Java 提供 javax.annotation.Resource , javax.inject.Inject 注解,Spring 框架提供了 org.springframework.beans.factory.annotation.Autowired 。依赖注入(Denpendency Injection,DI), 控制反转(Inversion of Control, IoC),主要的目的是去除代码耦合。具体可参考其他资料。

使用

Spring 注入的方式有多种,可以写在 field 上,可以写在 setter 方法上,可以写在 constructor 上。

// field
@Autowired
private UserDao userDao;

// constructor
@Autowired
public UserService(UserDao userDao) {
	this.userDao = userDao;
}


@Resource
private UserDao userDao;

配置

<context:annotation-config/>

or

<context:component-scan base-package="需要自动扫描的包" />

具体解释

Annotation Package Source
@Autowired org.springframework.beans.factory.annotation.Autowire Spring
@Resource javax.annotation.Resource Java
@Inject javax.inject.Inject Java 需额外依赖

@Autowired: Spring 特有的注解,@Autowired 通过类型来注入,比如通过类的类型,或者类的接口来注解 field 或者 constructor。为了防止在项目中实现同一个接口,或者一系列子类,可以使用 @Qualifier 注解来避免歧义。默认情况下 bean 的名字就是 qualifier 的值。 尽管你可以按照约定通过名字来使用 @Autowired 注解,@Autowired 根本上还是类型驱动的注入,并且附带可选的语义上的 qualifiers.

@Inject: 该注解基于 JSR-330, @Inject 注解是 Spring @Autowired 注解的代替品。所以使用 Spring 独有的 @Autowired 注解时,可以考虑选择使用 @Inject. @Autowired 和 @Inject 的不同之处在于是否有 required 属性,@Inject 没有 required 属性,因此在找不到合适的依赖对象时 inject 会失败,而 @Autowired 可以使用 required=false 来允许 null 注入。

使用 @Inject 需要添加如下依赖:

<dependency>
	<groupId>javax.inject</groupId>
	<artifactId>javax.inject</artifactId>
	<version>1</version>
</dependency>

Advantage of @Inject annotation is that rather than inject a reference directly, you could ask @Inject to inject a Provider. The Provider interface enables, among other things, lazy injection of bean references and injection of multiple instances of a bean. In case we have few implementation of an interface or a subclass we can narrow down the selection using the @Named annotation to avoid ambiguity. @Named annotation works much like Spring’s @Qualifier

@Resource: JDK 1.6 支持注解,JSR-250 引入。@Resource 和 @Autowired @Inject 类似,最主要的区别在于寻找存在的 Bean 注入的路径不同。@Resource 寻找的优先顺序为

  • 1) 优先通过名字 (by name)
  • 2)其次是类型 (by type)
  • 3)再次是 qualifier(by qualifier)

@Autowired and @Inject 寻找的顺序为

  1. 通过类型寻找
  2. 通过 qualifier
  3. 最后通过名字寻找

@Resource 如果没有指定 name 属性,当注解标注在 field 上,默认取字段名称作为 bean 名称寻找依赖对象;当标注在属性 setter 方法上,默认取属性名作为 bean 名称寻找依赖。如果没有指定 name 属性,并且按照默认名称找不到依赖对象时,回退到类型装配。

扩展

https://github.com/google/guice/

Google 提供的轻量级依赖注入框架,支持 Java 6 及以上

http://square.github.io/dagger/

为 Android 和 Java 设计的 DI

reference


2017-08-15 spring , java , web , design-pattern , spring-mvc , spring-boot

Spring Interceptor vs Filter 拦截器和过滤器区别

Spring的Interceptor(拦截器)与Servlet的Filter有相似之处,都能实现权限检查、日志记录等。不同的是:

Filter Interceptor Summary
Filter 接口定义在 javax.servlet 包中 接口 HandlerInterceptor 定义在org.springframework.web.servlet 包中  
Filter 定义在 web.xml 中    
Filter在只在 Servlet 前后起作用。Filters 通常将 请求和响应(request/response) 当做黑盒子,Filter 通常不考虑servlet 的实现。 拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。允许用户介入(hook into)请求的生命周期,在请求过程中获取信息,Interceptor 通常和请求更加耦合。 在Spring构架的程序中,要优先使用拦截器。几乎所有 Filter 能够做的事情, interceptor 都能够轻松的实现1
Filter 是 Servlet 规范规定的。 而拦截器既可以用于Web程序,也可以用于Application、Swing程序中。 使用范围不同
Filter 是在 Servlet 规范中定义的,是 Servlet 容器支持的。 而拦截器是在 Spring容器内的,是Spring框架支持的。 规范不同
Filter 不能够使用 Spring 容器资源 拦截器是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如 Service对象、数据源、事务管理等,通过IoC注入到拦截器即可 Spring 中使用 interceptor 更容易
Filter 是被 Server(like Tomcat) 调用 Interceptor 是被 Spring 调用 因此 Filter 总是优先于 Interceptor 执行

interceptor 使用

interceptor 的执行顺序大致为:

  1. 请求到达 DispatcherServlet
  2. DispatcherServlet 发送至 Interceptor ,执行 preHandle
  3. 请求达到 Controller
  4. 请求结束后,postHandle 执行

Spring 中主要通过 HandlerInterceptor 接口来实现请求的拦截,实现 HandlerInterceptor 接口需要实现下面三个方法:

  • preHandle() – 在handler执行之前,返回 boolean 值,true 表示继续执行,false 为停止执行并返回。
  • postHandle() – 在handler执行之后, 可以在返回之前对返回的结果进行修改
  • afterCompletion() – 在请求完全结束后调用,可以用来统计请求耗时等等

统计请求耗时

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter{

	private static final Logger logger = Logger.getLogger(ExecuteTimeInterceptor.class);

	//before the actual handler will be executed
	public boolean preHandle(HttpServletRequest request,
		HttpServletResponse response, Object handler)
		throws Exception {

		long startTime = System.currentTimeMillis();
		request.setAttribute("startTime", startTime);

		return true;
	}

	//after the handler is executed
	public void postHandle(
		HttpServletRequest request, HttpServletResponse response,
		Object handler, ModelAndView modelAndView)
		throws Exception {

		long startTime = (Long)request.getAttribute("startTime");

		long endTime = System.currentTimeMillis();

		long executeTime = endTime - startTime;

		//modified the exisitng modelAndView
		modelAndView.addObject("executeTime",executeTime);

		//log it
		if(logger.isDebugEnabled()){
		   logger.debug("[" + handler + "] executeTime : " + executeTime + "ms");
		}
	}
}

例子来源 mkyong

使用mvc:interceptors标签来声明需要加入到SpringMVC拦截器链中的拦截器

<mvc:interceptors>  
<!-- 使用bean定义一个Interceptor,直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求 -->  
<bean class="com.company.app.web.interceptor.AllInterceptor"/>  
	<mvc:interceptor>  
		 <mvc:mapping path="/**"/>  
		 <mvc:exclude-mapping path="/parent/**"/>  
		 <bean class="com.company.authorization.interceptor.SecurityInterceptor" />  
	</mvc:interceptor>  
	<mvc:interceptor>  
		 <mvc:mapping path="/parent/**"/>  
		 <bean class="com.company.authorization.interceptor.SecuritySystemInterceptor" />  
	</mvc:interceptor>  
</mvc:interceptors>  

可以利用mvc:interceptors标签声明一系列的拦截器,然后它们就可以形成一个拦截器链,拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。

在mvc:interceptors标签下声明interceptor主要有两种方式:

  • 直接定义一个Interceptor实现类的bean对象。使用这种方式声明的Interceptor拦截器将会对所有的请求进行拦截。
  • 使用mvc:interceptor标签进行声明。使用这种方式进行声明的Interceptor可以通过mvc:mapping子标签来定义需要进行拦截的请求路径。

经过上述两步之后,定义的拦截器就会发生作用对特定的请求进行拦截了。

Filter 使用

Servlet 的 Filter 接口需要实现如下方法:

  • void init(FilterConfig paramFilterConfig) – 当容器初始化 Filter 时调用,该方法在 Filter 的生命周期只会被调用一次,一般在该方法中初始化一些资源,FilterConfig 是容器提供给 Filter 的初始化参数,在该方法中可以抛出 ServletException 。init 方法必须执行成功,否则 Filter 可能不起作用,出现以下两种情况时,web 容器中 Filter 可能无效: 1)抛出 ServletException 2)超过 web 容器定义的执行时间。
  • doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain) – Web 容器每一次请求都会调用该方法。该方法将容器的请求和响应作为参数传递进来, FilterChain 用来调用下一个 Filter。
  • void destroy() – 当容器销毁 Filter 实例时调用该方法,可以在方法中销毁资源,该方法在 Filter 的生命周期只会被调用一次。

    FrequencyLimitFilter com.company.filter.FrequencyLimitFilter FrequencyLimitFilter /login/*

Filter 和 Interceptor 的一些用途

  • Authentication Filters
  • Logging and Auditing Filters
  • Image conversion Filters
  • Data compression Filters
  • Encryption Filters
  • Tokenizing Filters
  • Filters that trigger resource access events
  • XSL/T filters
  • Mime-type chain Filter

Request Filters 可以:

  • 执行安全检查 perform security checks
  • 格式化请求头和主体 reformat request headers or bodies
  • 审查或者记录日志 audit or log requests
  • 根据请求内容授权或者限制用户访问 Authentication-Blocking requests based on user identity.
  • 根据请求频率限制用户访问

Response Filters 可以:

  • 压缩响应内容,比如让下载的内容更小 Compress the response stream
  • 追加或者修改响应 append or alter the response stream
  • 创建或者整体修改响应 create a different response altogether
  • 根据地方不同修改响应内容 Localization-Targeting the request and response to a particular locale.

reference

  1. https://stackoverflow.com/a/8006315/1820217 


2017-08-14 Spring , Java , Web

Spring @Component vs @Service vs @Controller vs @Repository

@Component, @Service, @Controller@Repository 四个注解在 Spring 中等同于在XML中定义 <bean> 标签,他们注解的对象都是 Spring 的 Bean。@Service@Controller@Repository 本质上就是 @Component@Controller@Service@Repository 他们在功能上几乎相同,主要的功能是用来给应用分层。

  • @Controller: 处理对应的请求,对应表现层(控制层),使用 @RequestMapping 注解来定义请求 Path,在该层中做请求分发,转发,调用Service方法等
  • @Service: 所有业务逻辑放在 Service 中,对应业务层,包括数值计算,业务逻辑,在该层中直接调用持久层的方法
  • @Repository: 持久层,访问数据,保存数据,DAO(Data Access Objects),比如所有数据库相关的操作
  • @Component: 通用的Spring 组件,generic stereotype for any Spring-managed component,当组件不好归类时可以使用该注解

在配置文件中定义

<context:component-scan base-package="com.mycompany.mypackage" />

Spring 就会自动扫描package下所有类,将带有@Component@Repository@Service@Controller 标签的类自动注册到Spring容器。 简而言之,注解的方式省去了过去需要在 XML 中定义 <bean class="..."> 的繁重工作。component-scan 包含了annotation-config标签的作用。 @Repository@Service@Controller 这三个注解除了作用于不同的软件层面外,其他使用方式和 @Repository 几乎一致。

除了上面的四个注解外,用户同样也可以创建自定义的注解,然后在注解上标注 @Component,那么,该自定义注解便具有了与所 @Component 相同的功能。

当一个 Bean 被自动检测到时,会根据那个扫描器的 BeanNameGenerator 策略生成它的 bean 名称。默认情况下,对于包含 name 属性的 @Component@Repository@Service@Controller,会把 name 取值作为 Bean 的名字。如果这个注解不包含 name 值或是其他被自定义过滤器发现的组件,默认 Bean 名称会是小写开头的非限定类名。如果你不想使用默认 bean 命名策略,可以提供一个自定义的命名策略。首先实现 BeanNameGenerator 接口,确认包含了一个默认的无参数构造方法。然后在配置扫描器时提供一个全限定类名,如下所示:

<beans ...> 
<context:component-scan 
	base-package="a.b" name-generator="a.SimpleNameGenerator"/> 
</beans> 

与通过 XML 配置的 Spring Bean 一样,通过上述注解标识的 Bean,其默认作用域是”singleton”,为了配合这四个注解,在标注 Bean 的同时能够指定 Bean 的作用域,Spring 2.5 引入了 @Scope 注解。 可以在定义Component 的时候指定 @Scope("prototype”) 来改变。

@Component
@Scope("prototype")
public class UserService {
	private int counter;

}

通过名字获取Bean

在一些特殊情况下当我们无法使用注解直接使用 Spring Bean 时,比如在 Filter 中,有一些教程提示我们可以直接使用 ApplicationContext.getBean() 来后去 Bean,但这样的方式不优雅 ,我们可以考虑实现 org.springframework.context.ApplicationContextAware 接口,来动态的根据名字来获取 Bean。文档上

Interface to be implemented by any object that wishes to be notified of the ApplicationContext that it runs in. Implementing this interface makes sense for example when an object requires access to a set of collaborating beans. Note that configuration via bean references is preferable to implementing this interface just for bean lookup purposes.

具体实现如下:

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
 
@Component
public class ContextProvider implements ApplicationContextAware {
 
	private static ApplicationContext CONTEXT;
 
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		CONTEXT = applicationContext;
	}
 
	/**
	 * Get a Spring bean by type.
	 **/
	public static <T> T getBean(Class<T> beanClass) {
		return CONTEXT.getBean(beanClass);
	}
 
	/**
	 * Get a Spring bean by name.
	 **/
	public static Object getBean(String beanName) {
		return CONTEXT.getBean(beanName);
	}
}

上面的类实现,定义了一个简单的 @Component 实现了 ApplicationContextAware 接口,当 Spring Context 被创建时会被通知到。当被通知后 Spring context 会被放到唯一的静态变量 CONTEXT 中,因此静态方法能够通过 getBean 方法找到相应的 Bean 实例。

getBean 的默认名称是类名(头字母小写),如果想简单自定义Bean名字,可以@Service("serviceNewName") 这样来指定。

使用 @PostConstruct 和 @PreDestroy 指定生命周期回调方法

可以使用以下方式指定初始化方法和销毁方法:

@PostConstruct
public void init() { 

} 

@PreDestroy
public void destory() { 

}

Spring Bean 是受 Spring IoC 容器管理,由容器进行初始化和销毁的(prototype 类型由容器初始化之后便不受容器管理),通常我们不需要关注容器对 Bean 的初始化和销毁操作,由 Spring 经过构造函数或者工厂方法创建的 Bean 就是已经初始化完成并立即可用的。然而在某些情况下,可能需要我们手工做一些额外的初始化或者销毁操作,这通常是针对一些资源的获取和释放操作。

Spring 2.5 在保留以上两种方式的基础上,提供了对 JSR-250 的支持。JSR-250 规范定义了两个用于指定声明周期方法的注解:@PostConstruct 和 @PreDestroy。这两个注解使用非常简单,只需分别将他们标注于初始化之后执行的回调方法或者销毁之前执行的回调方法上。由于使用了注解,因此需要配置相应的 Bean 后处理器,亦即在 XML 中增加如下一行:

比较上述三种指定生命周期回调方法的方式,第一种是不建议使用的,不但其用法不如后两种方式灵活,而且无形中增加了代码与框架的耦合度。后面两种方式开发者可以根据使用习惯选择其中一种,但是最好不要混合使用,以免增加维护的难度。

reference


2017-08-13 Spring , Bean , Java , DI , IoC

使用 itsdangerous 签名校验

一般在开发网站时使用 session 或者 cookie 来处理用户登陆等等权限问题,而在移动应用中要验证用户身份采用登录时给用户生成一个 token(令牌)的方式。每次用户发出需要身份认证的请求时,就需要验证一次 token 是否有效,无效的情况包括 token 无法被解析等。在向不可信环境发送数据时,确保数据经过签名,使用只有自己知道的密钥来签名数据,加密后发送,在取回数据时,确保没有人篡改过。

Python 有个 itsdangerous 包含了很多安全校验 token 验证相关的方案。 itsdangerous 就是这样一个签名校验的工具,内部使用 HMAC 和 SHA1 来签名。基于 Django 签名模块,支持 JSON Web 签名 (JWS), 这个库采用 BSD 协议。

给定字符串签名

发送方和接收方拥有相同的密钥 secret-key ,发送方使用密钥对发送内容进行签名,接收方使用相同的密钥对接收到的内容进行验证,看是否是发送方发送的内容。

>>> from itsdangerous import Signer
>>> s = Signer('secret-key')
>>> s.sign('my string')
'my string.wh6tMHxLgJqB6oY1uT73iMlyrOA'

签名过的字符串使用 . 分割。验证使用 unsign

>>> s.unsign('my string.wh6tMHxLgJqB6oY1uT73iMlyrOA')

带时间戳的签名

签名有一定的时效性,发送方发送时,带上时间信息,接收方判断多长时间内是否失效

>>> from itsdangerous import TimestampSigner
>>> s = TimestampSigner('secret-key')
>>> string = s.sign('foo')
foo.DlGDsw.dpJ37ffyfNAVufH21lH_yoelnKA
>>> s.unsign(string, max_age=5)

如果验证时间不对会抛出异常

序列化

>>> from itsdangerous import Serializer
>>> s = Serializer('secret-key')
>>> s.dumps([1, 2, 3, 4])
>>> s.loads('[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo')

带时间戳的序列化

>>> from itsdangerous import TimedSerializer
>>> s=TimedSerializer('secret-key')
>>> s.dumps([1,2,3,4])
>>> s.loads('[1, 2, 3, 4].DlGEjg.1yG-U7iBk92FBYAZLezoBv2mfJs')

URL 安全序列化

如果加密过的字符串需要在 URL 中传输,可以使用这种方式。常见的就是在邮件验证 token 中。

>>> from itsdangerous import URLSafeSerializer
>>> s = URLSafeSerializer('secret-key')
>>> s.dumps([1, 2, 3, 4])
'WzEsMiwzLDRd.wSPHqC0gR7VUqivlSukJ0IeTDgo'
>>> s.loads('WzEsMiwzLDRd.wSPHqC0gR7VUqivlSukJ0IeTDgo')
[1, 2, 3, 4]

JSON Web Signatures

>>> from itsdangerous import JSONWebSignatureSerializer
>>> s = JSONWebSignatureSerializer('secret-key')
>>> s.dumps({'x': 42})
'eyJhbGciOiJIUzI1NiJ9.eyJ4Ijo0Mn0.ZdTn1YyGz9Yx5B5wNpWRL221G1WpVE5fPCPKNuc6UAo'

带时间戳的 JSON Web 签名

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
s = Serializer('secret-key', expires_in=60)
s.dumps({'id': user.id}) # user 为 model 中封装过的对象

在 Flask 中应用

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from itsdangerous import SignatureExpired, BadSignature
from config import config

def gen_token(user, expiration=1440*31*60):  # 单位为秒,设定 31 天过期
    s = Serializer(config.SECRET_KEY, expires_in=expiration)
    return s.dumps({'id': user.id})  # user 为 model 中封装过的对象

装饰器

from functools import wraps
def token_required(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        token = request.form['token']
        s = Serializer(config.SECRET_KEY)
        try:
            data = s.loads(token)
        except SignatureExpired:
            return jsonify({'status': 'fail', 'data': {'msg': 'expired token'}})
        except BadSignature:
            return jsonify({'status': 'fail', 'data': {'msg': 'useless token'}})
        kwargs['user_id'] = data['id']
        return func(*args, **kwargs)
    return wrapper

盐值

不同的盐值,生成的签名或者序列化的数值不一样,这里的盐 (SALT)不同于加密算法中的盐值,这里的盐值是用来避免彩虹表破解

Hash

哈希(Hash)算法就是单向散列算法,它把某个较大的集合 P 映射到另一个较小的集合 Q 中,假如这个算法叫 H,那么就有 Q = H(P)。对于 P 中任何一个值 p 都有唯一确定的 q 与之对应,但是一个 q 可以对应多个 p。作为一个有用的 Hash 算法,H 还应该满足:H(p) 速度比较快; 给出一个 q,很难算出一个 p 满足 q = H(p);给出一个 p1,很难算出一个不等于 p1 的 p2 使得 H(p1)=H(p2)。正因为有这样的特性,Hash 算法经常被用来保存密码————这样不会泄露密码明文,又可以校验输入的密码是否正确。常用的 Hash 算法有 MD5、SHA1 等。

破解 Hash 的任务就是,对于给出的一个 q,反算出一个 p 来满足 q = H(p)。通常我们能想到的两种办法,一种就是暴力破解法,把 P 中的每一个 p 都算一下 H(p),直到结果等于 q;另一种办法是查表法,搞一个很大的数据 库,把每个 p 和对应的 q 都记录下来,按 q 做一下索引,到时候查一下就知道了。这两种办法理论上都是可以的,但是前一种可能需要海量的时间,后一种需要海量 的存储空间,以至于以目前的人类资源无法实现。

扩展

[[2021-06-29-jwt-authentication]] 是一次性认证完毕加载信息到 token 里的,token 的信息内含过期信息。过期时间过长则被重放攻击的风险太大,而过期时间太短则请求端体验太差(动不动就要重新登录)

第三方认证协议 Oauth2.0 RFC6749 ,它采取了另一种方法:refresh_token,一个用于更新令牌的令牌。在用户首次认证后,签发两个 token: 一个为 access_token,用于用户后续的各个请求中携带的认证信息;另一个是 refresh_token,为 access_token 过期后,用于申请一个新的 access_token

由此可以给两类不同 token 设置不同的有效期,例如给 access_token 仅 1 小时的有效时间,而 refresh_token 则可以是一个月。api 的登出通过 access token 的过期来实现(前端则可直接抛弃此 token 实现登出),在 refresh token 的存续期内,访问 api 时可执 refresh token 申请新的 access token(前端可存此 refresh token,access token 过其实进行更新,达到自动延期的效果)。refresh token 不可再延期,过期需重新使用用户名密码登录。

这种方式的理念在于,将证书分为三种级别:

  • access token 短期证书,用于最终鉴权
  • refresh token 较长期的证书,用于产生短期证书,不可直接用于服务请求
  • 用户名密码 几乎永久的证书,用于产生长期证书和短期证书,不可直接用于服务请求

reference


2017-08-12 itsdangerous , python , sign

Java enum 相等比较 == or equal

能否使用 == 来针对 enum 来比较?

答案是:YES, 枚举谨慎的实例化管理允许使用 == 来进行比较,JLS 8.9 Enums 中有Java 语言的规范定义:

枚举类型除了定义时的枚举常量外没有其他实例

如果显示的实例化枚举类型,会产生编译时异常。final clone 方法保证了 Enum 变量不会被 clone, 序列化的机制也保证了重复的实例在反序列化时不会创建额外的枚举变量。通过反射实例化 Enum 类型是被禁止的。所有这四种方式确保了 enum 类型不存在额外的实例,除了定义时的常量

因为每一个枚举常量只有一个实例,因此使用 == 来代替 equals 方法来比较两个枚举是被允许的.

== 和 equals 的区别

使用 == 来代替 equals 也存在两点需要注意的问题。

== 不会抛出空指针异常

enum Color { BLACK, WHITE };

Color nothing = null;
if (nothing == Color.BLACK);      // runs fine
if (nothing.equals(Color.BLACK)); // throws NullPointerException

不过 == 有编译时类型检查,这一点还是很不错的

enum Color { BLACK, WHITE };
enum Chiral { LEFT, RIGHT };

if (Color.BLACK.equals(Chiral.LEFT)); // compiles fine
if (Color.BLACK == Chiral.LEFT);      // DOESN'T COMPILE!!! Incompatible types!

总结

对于不可变类,并且该类能够有效的控制实例数量, == 就是可用的。

在 Effective Java 一书中指出:

考虑静态工厂方法代替构造器,它使得不可变类可以确保不存在两个相等的实例,即当且仅当 a==b 时才有 a.equals(b) 成立,如果类能够保证这一点,它的客户端可以使用 == 来代替 equals 方法,这样可以提升性能,而枚举类型可以保证这一点。

所以在枚举比较中可以使用 ==

  1. 可以正常工作
  2. 更快
  3. 运行时安全
  4. 编译期也是安全的。

reference


2017-08-10 Java , enum

在 Ubuntu 下安装并使用 Cinnamon

Ubuntu 16.04 LTS 或者 Ubuntu 17.04 下可以通过 PPA 来安装 Cinnamon,感谢维护者

命令如下:

sudo add-apt-repository ppa:embrosyn/cinnamon
sudo apt update && sudo apt install cinnamon

当安装完成之后,Log out 或者 重启,在登录界面选择 Cinnamon 来使用。

我在使用一段时间之后才发现没有安装 Nemo 的插件,以至于右击都没有压缩的选项,通过一下步骤安装 Nemo 以及相关套件。

安装 Nemo

sudo add-apt-repository ppa:noobslab/mint
sudo apt-get update
sudo apt-get install nemo

安装插件

sudo apt-get install nemo-compare nemo-dropbox nemo-fileroller nemo-pastebin nemo-seahorse nemo-share nemo-preview nemo-rabbitvcs

安装完成之后退出 nemo :

nemo -q

然后重启 nemo 即可。

关于 Nemo 更多的使用,可以参考我博客上另外的文章。

reference


2017-08-07 Ubuntu , Linux , Cinnamon , LinuxMint

给常用的 git 命令添加 alias 提升效率

之前有写过 Git alias 的文章, 不过已经过去了很多时间,现在对 Git 命令越来越熟悉就希望更快的提高输入效率,也越来越感受到 alias 的重要性,不管是直接在 bash 中的 alias 还是 Git 的 alias。所以准备找一些合适的 alias 添加到自己的 gitconfig 文件中长期使用。

添加 alias

使用命令的方式添加

git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.br branch
git config --global alias.hist "log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short"
git config --global alias.type 'cat-file -t'
git config --global alias.dump 'cat-file -p'

git status, git add, git commit, git checkoutgit branch 是最常见的 git 命令,给他们设置 alias 能提高不少效率。使用以上命令添加 alias ,其实作用等同于直接编辑 HOME 目录下的 gitconfig 文件, vim ~/.gitconfig:

[alias]
  co = checkout
  ci = commit
  st = status
  br = branch
  hist = log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short
  type = cat-file -t
  dump = cat-file -p

如果这样设置之后就可以使用 git co <branch> 来切换分支了。

bash alias

可以在 ~/.bashrc 或者 ~/.zshrc 中设置 alias g=git 这样就可以使用 g checkout 来代替 git checkout 了。

oh-my-zsh

除了使用 git 自身的 alias 还可以使用 oh-my-zsh 来进一步简化 git 命令的使用。

使用 oh-my-zsh 简化 git 命令

比如一个正常的 git 提交流程:

  • git add .
  • git commit -m ‘fix: some fix’
  • git push

使用 oh-my-zsh 之后,可以简化成

  • gaa
  • gcm “fix:some fix”
  • gp

安装 oh-my-zsh 后默认会打开 git 插件,可以使用其提供的自动补全。

罗列几个常用的作为示例,展示它们的作用:

快捷键 git 命令 描述
g git git
gp git push 推送
gl git pull 拉取
gaa git add –all 添加当前项目所有文件修改、增删的文件到缓存区
gc! git commit -v –amend 修正上次提交
gcm git commit -m 提交项目到本地库,其中-a 表示不用再次输入 git add 命令
gcb git checkout -b 将特定分支上暂存储区的内容替换当下工作区的内容,
gcm git checkout $(git_main_branch) 切到 main 或者 master
gcd git checkout $(git_develop_branch) 切到 develop
gbD git branch -D 删除分支
glods git log –graph –date=short 查看提交记录
gm git merge 合并分支
grb git rebase 变基
grhh git reset –hard 重置
gcp git cherry-pick <commitId> 从其他分支 选取一次提交
gsta git stash push 保存修改为暂存
gstp git stash pop 弹出暂存

结合 fzf

结合 fzf 的使用可以充分发挥 fzf 模糊搜索的能力。

比如我自己定义了 gcbr 表示 git checkout branch,然后 fzf 会根据 git 的信息将我所有的本地分支和远程分支拉取出来,然后我就可以进行模糊搜索,直接回车就可以切换到该分支。

reference


2017-08-04 git , alias , linux , oh-my-zsh , zsh , bash

每天学习一个命令:使用 split 分割文件

split 命令可以用来分割文件,支持按文本分割,也支持二进制分割。常见的格式

split [OPTION]... [FILE [PREFIX]]

输出的结果为多个文件 PREFIXaa,PREFIXab … 默认的大小是 1000 行,默认的 PREFIX 是 x

如果没有 FILE 文件,或者当 FILE 参数是 - 时,会从标准输入读取。

举例

按文件大小

-C 指定分割文件大小:

split -C 10M large_file.mp4 small

将大文件 large_file.mp4 按照 10M 大小进行分割,并指定分割后文件前缀 small,不指定前缀时, 自动对分割文件名命名,一般以 x 开头

按行分割

-l 选项后接分割行

split -l 1000 large_file.txt small

二进制分割

split -b 100M data.bak smail

合并

cat small* > new_file.txt

2017-08-04 split , command , linux

Python logging 模块使用

日志是每一个编程语言必备的模块,借助日志不仅可以监控在线服务的状态,也可以在出问题之后迅速的定位问题。

基本使用

# -*- coding: utf-8 -*-

import logging
import sys

# 获取 logger 实例,如果参数为空则返回 root logger
# 最基本的入口,该方法参数可以为空,默认的 logger 名称是 root,如果在同一个程序中一直都使用同名的 logger,其实会拿到同一个实例,使用这个技巧就可以跨模块调用同样的 logger 来记录日志
logger = logging.getLogger("AppName")

# 指定 logger 输出格式
formatter = logging.Formatter('%(asctime)s %(levelname)-8s: %(message)s')

# 文件日志
file_handler = logging.FileHandler("test.log")
file_handler.setFormatter(formatter)  # 可以通过 setFormatter 指定输出格式

# 控制台日志
console_handler = logging.StreamHandler(sys.stdout)
console_handler.formatter = formatter  # 也可以直接给 formatter 赋值

# 为 logger 添加的日志处理器
logger.addHandler(file_handler)
logger.addHandler(console_handler)

# 指定日志的最低输出级别,默认为 WARN 级别
logger.setLevel(logging.INFO)

# 输出不同级别的 log
logger.debug('this is debug info')
logger.info('this is information')
logger.warn('this is warning message')
logger.error('this is error message')
logger.fatal('this is fatal message, it is same as logger.critical')
logger.critical('this is critical message')

# 2016-10-08 21:59:19,493 INFO    : this is information
# 2016-10-08 21:59:19,493 WARNING : this is warning message
# 2016-10-08 21:59:19,493 ERROR   : this is error message
# 2016-10-08 21:59:19,493 CRITICAL: this is fatal message, it is same as logger.critical
# 2016-10-08 21:59:19,493 CRITICAL: this is critical message

# 移除一些日志处理器
logger.removeHandler(file_handler)

格式化输出

# 格式化输出

service_name = "Booking"
logger.error('%s service is down!' % service_name)  # 使用 python 自带的字符串格式化,不推荐
logger.error('%s service is down!', service_name)  # 使用 logger 的格式化,推荐
logger.error('%s service is %s!', service_name, 'down')  # 多参数格式化
logger.error('{} service is {}'.format(service_name, 'down')) # 使用 format 函数,推荐

# 2016-10-08 21:59:19,493 ERROR   : Booking service is down!

异常

当你使用 logging 模块记录异常信息时,不需要传入该异常对象,只要你直接调用 logger.error() 或者 logger.exception() 就可以将当前异常记录下来。

# 记录异常信息

try:
    1 / 0
except:
    # 等同于 error 级别,但是会额外记录当前抛出的异常堆栈信息
    logger.exception('this is an exception message')

# 2016-10-08 21:59:19,493 ERROR   : this is an exception message
# Traceback (most recent call last):
#   File "D:/Git/py_labs/demo/use_logging.py", line 45, in <module>
#     1 / 0
# ZeroDivisionError: integer division or modulo by zero

常见配置

通过日志名称来区分同一程序的不同模块

logger = logging.getLogger("App.UI")
logger = logging.getLogger("App.Service")

import logging
import time

logging.basicConfig(format="%(asctime)s %(levelname)s %(message)s",
                        datefmt="%Y %b %d %H:%M:%S",
                        filename="./log.log",
                        filemode="w",               # default is "a"
                        level=logging.INFO)

while True:
    for i in range(6):
        logging.log(i*10, "a log")                  # logging.log(level, msg)
        time.sleep(1)

fmt 中允许使用的变量可以参考下表

  • %(name)s Logger 的名字
  • %(levelno)s 数字形式的日志级别
  • %(levelname)s 文本形式的日志级别
  • %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
  • %(filename)s 调用日志输出函数的模块的文件名
  • %(module)s 调用日志输出函数的模块名
  • %(funcName)s 调用日志输出函数的函数名
  • %(lineno)d 调用日志输出函数的语句所在的代码行
  • %(created)f 当前时间,用 UNIX 标准的表示时间的浮点数表示
  • %(relativeCreated)d 输出日志信息时的,自 Logger 创建以来的毫秒数
  • %(asctime)s 字符串形式的当前时间。默认格式是“2003-07-08 16:49:45,896”。逗号后面的是毫秒
  • %(thread)d 线程 ID。可能没有
  • %(threadName)s 线程名。可能没有
  • %(process)d 进程 ID。可能没有
  • %(message)s 用户输出的消息

Handler 日志处理器

最常用的是 StreamHandler 和 FileHandler, Handler 用于向不同的输出端打 log,比如可以将一份日志分别输出到文件和终端。

console = logging.StreamHandler(stream=sys.stdout)                    # 默认流为sys.stderr
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger().addHandler(console)

files = logging.FileHandler("log2.log", mode="a", encoding="utf-8")   # 设置文件流
files.setLevel(logging.WARNING)
formatter = logging.Formatter("%(levelname)s %(message)s")
files.setFormatter(formatter)
logging.getLogger().addHandler(files)

说明:

  • StreamHandler instances send error messages to streams (file-like objects).
  • FileHandler instances send error messages to disk files.
  • RotatingFileHandler instances send error messages to disk files, with support for maximum log file sizes and log file rotation.
  • TimedRotatingFileHandler instances send error messages to disk files, rotating the log file at certain timed intervals.
  • SocketHandler instances send error messages to TCP/IP sockets.
  • DatagramHandler instances send error messages to UDP sockets.
  • SMTPHandler instances send error messages to a designated email address.

日志格式配置的方式

logging 的配置大致有下面几种方式。

  • 通过代码进行完整配置,参考开头的例子,主要是通过 getLogger 方法实现。
  • 通过代码进行简单配置,下面有例子,主要是通过 basicConfig 方法实现。
  • 通过配置文件,下面有例子,主要是通过 logging.config.fileConfig(filepath)

通过 basicConfig 配置 logger : https://docs.python.org/2/library/logging.html#logging.basicConfig

日志重复输出的坑

可能的原因有很多,但总结下来无非就一个,日志中使用了重复的 handler

切记添加 handler 时不要重复。


2017-08-03 python , logging , logger

电子书

本站提供服务

最近文章

  • AI Shell 让 AI 在命令行下提供 Shell 命令 AI Shell 是一款在命令行下的 AI 自动补全工具,当你想要实现一个功能,敲一大段命令又记不住的时候,使用自然语言让 AI 给你生成一个可执行的命令,然后确认之后执行。
  • 最棒的 Navidrome 音乐客户端 Sonixd(Feishin) Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。 Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。
  • 中心化加密货币交易所 Gate 注册以及认证 Gate.io 是一个中心化的加密货币交易所。Gate 中文通常被称为「芝麻开门」,Gate 创立于 2013 年,前身是比特儿,是一家致力于安全、稳定的数字货币交易所,支持超过 1600 种数字货币的交易,提供超过 2700 个交易对。
  • 不重启的情况下重新加载 rTorrent 配置文件 因为我在 Screen 下使用 rTorrent,最近经常调试修改 rtorrent.rc 配置文件,所以想要找一个方法可以在不重启 rTorrent 的情况重新加载配置文件,网上调查了一下之后发现原来挺简单的。
  • Go 语言编写的网络穿透工具 chisel chisel 是一个在 HTTP 协议上的 TCP/UDP 隧道,使用 Go 语言编写,10.9 K 星星。