0%

JSON Web Token (JWT)

参考资料:Introduction to JSON Web Tokens

简介

JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方法,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。

组成部分

JSON Web Token由三个部分组成,使用(.)分割。

  • Header
  • Payload
  • Signature

因此JWT的格式为:xxxx.yyyy.zzzz

第一部分Header

通常包括两部分:令牌的类型(即JWT),以及使用的哈希算法(如HMAC SHA256或RSA)。

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

然后,这个JSON是Base64Url编码形成JWT的第一部分。

第二部分Payload

通常包括claims, claims是关于实体(通常是用户)和附加元数据的声明。

这里有三个类型的声明:

  • reserved
  • public
  • private

Reserved claims

这些是一组预定义的声明,不是强制性的,而是建议的,以提供一组有用的,可互操作的声明。其中有些是:

  • iss (issuer)
  • exp (expiration time)
  • sub (subject)
  • aud (audience)
  • 其他

PS请注意,声明名称只有三个字符,因为JWT意味着紧凑。

Public claims

这些可以由使用JWT的人随意定义。但为避免冲突,应在
IANA JSON Web Token注册表
中定义它们,或者将其定义为包含防冲突命名空间的URI。

Private claims

这些是为共享使用它们的各方之间共享信息而创建的自定义声明。

例如

1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

该payload被Base64Url编码以形成JSON Web令牌的第二部分。

第三部分Signature

创建签名部分,必须使用以下3个数据根据header中指定的算法, 并签名用于验签。

  • base64编码的header
  • 编码的payload
  • 一个secret

例如:如果要使用HMAC SHA256算法,将以以下方式创建签名:

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

最后拼接起来

输出是三个由点(.)分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递这些字符串,与基于XML的标准(例如SAML)相比,它更紧凑。

下面这个就是编码签名后的JWT结果字符串:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c.

可以使用JWT Debug 工具测试签名和验签名。

如何使用JWT

获取JWT并将其用于访问API或资源:

  • 客户端从授权服务器登录
  • 登录成功后授权服务器下发JWT令牌返回给客户端
  • 客户端使用JWT令牌访问受保护的API资源服务器

请注意,使用签名的令牌,令牌中包含的所有信息都会暴露给用户或其他方(因使用的是Base64可以反编译获得原文),即使他们无法更改其中的数据,但不应将机密信息放入令牌中,由于缺乏安全性,不应该将敏感的会话数据存储在浏览器中。

在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web Token。由于令牌是凭据,因此必须格外小心以防止安全问题。通常,令牌的保留时间不应超过要求的时间。

每当用户想要访问受保护的路由或资源时,用户代理应发送JWT,通常在使用Bearer模式的Authorization header中。
header的内容应如下所示: Authorization: Bearer <token>

在某些情况下,这可以是无状态授权机制。服务器的受保护路由将在header中检查有效的JWT,如果存在,则将允许用户访问受保护的资源。如果JWT包含必要的数据,则可以减少查询数据库中某些操作的需求,尽管并非总是如此。

如果令牌是在header中发送的,则跨域资源共享(CORS)不会成为问题,因为它不使用cookie。

总结

JSON Web Token(简称JWT)使用点(.)分隔三个Base64加密部分拼接在一起的字符串。

优点

  1. 轻量跨语言,使用字符串传输,不同开发语言系统都能使用
  2. 无状态,无需消耗缓存或其他数据存储保存状态
  3. 可跨域
  4. 可承载数据传输到不同系统,减少数据库的访问
  5. 可设置有效时间,在网关层/入口拦截器处理判断
  6. 签名和承载数据不可更改

缺点

  1. 承载数据完全暴露
  2. 无状态,不能有效管理token,需要等有效时间失效
  3. 有效时间续期问题

扩展

现在开发的系统是生成一个token(如UUID字符串),保存在缓存中,由服务器管理,校验和存储需要一些资源,当大量请求的时候后端会有一些压力。

因此参考了JWT的特性,把token字符串改为JWT的形式,token放在缓存,token状态因业务问题还是需要保留。这样的好处是保持业务不变,利用JWT的承载数据校验有效时间和身份等判断,减少与缓存的交互压力,可以很大程度有效过滤掉无用的token请求(虽然没了JWT的无状态等特性)。

题目

实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sqrtx

解法

二分法

  1. 定义左边界0,右边界x;取左右边界的中位数。
  2. 计算中位数的平方,判断与x之间的大小;小于x则收缩左边界,大于x则收缩右边界,如此循环
  3. 等右边界贴近左边界(甚至<)时,左边界则是最优解
1
2
3
4
5
6
7
8
9
10
PS:取出中位数时一定要+1

否则会存在这种情况,进退两难进入死循环 如:
中位数:46339 left:46339 right:46340

而+1之后 如:
中位数:46339 left:46337 right:46340
中位数:46340 left:46339 right:46340

此时left:46339就是最优解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static long mySqrt(int x) {
// 要把搜索的范围设置成长整型, 测试用例 2147395599 会超出int的最大长度

// 比较特殊的是 0 和 1,因此左边界设置0,有边界设置 x/2 + 1(一个非负数n,它的平方根不会大于(n/2+1))

long left = 0;
long right = x / 2 + 1;
// 将在左右区间内寻找
while (left < right) {
// 计算左右边界的中位数
long mid = (left + right + 1) / 2;
System.out.println("中位数:" + mid + " (left:" + left + " right:" + right + ")");
// 中位数平方,判断是否接近
long square = mid * mid;
if (square > x) {
// 结果>x,右边界收缩到中位数,但是中位数明显不是, -1 ,准备下次循环重新计算
right = mid - 1;
} else {
// 结果<=x,左边界等于 中位数
left = mid;
}
}
// 此时左边界已经<=x,且右边界贴近(甚至<)左边界极限,说明左边界已经是最近似值
return (int) left;
}

现状

使用手机软件记账,且只记录支出流水,原因如下:

  • 不放心把余额记录在别人的服务器上(真香警告)。
  • 贪方便,追查明细到每一笔的流向非常麻烦,因此只记录支出,毕竟知道自己花了多少,对一对还是有点用的,得益于这种支出记账法的方便坚持了两年。

前言

今天无意中看到了一种叫复式记账法,查了一下发现是会计财务用的记账法,记账菜鸡的我看上去甚是高级,也简单用Excel试了一下,感觉清晰明了,于是开始搜查资料落实使用。

引用

什么是复式记账法

查询了维基百科和百度百科得知:

  • 在会计学中,复式簿记(又称为复式记账法)是商业及其他组织上记录金融交易的标准系统。

  • 该系统之所以称为复式簿记,是因为每笔交易都至少记录在两个不同的账户当中。每笔交易的结果至少被记录在一个借方和一个贷方的账户,且该笔交易的借贷双方总额相等,即“有借必有贷,借贷必相等”。

  • 借方项目通常记在左边,贷方则记在右边,空白账簿看起来像个T字,故账户也被称为T字帐。

  • 等式控制,

    复式簿记由会计等式进行控制,如果收入等于费用,则以下等式就会成立:

    资产 = 负债 + 业主权益

    事实上,收入一般都不会等于费用,因此,在正常情况下,以上等式必须扩充为:

    资产 = 负债 + 业主权益 + (收入 - 费用)
  • 可以全面、清晰地反映出经济业务的来龙去脉,而且还能通过会计要素的增减变动,全面系统地反映经济活动的过程和结果。

  • 记账规则:有收必有付,收付必相等。

整理概念和使用

  1. 概念

    • 复式记账又分为了借贷记账法,和正负记账法。借 (debits),贷 (credits),两者在交易中是双向流动的,因此可以反映出经济活动的来龙去脉。
    • 资产是负债和权益的总和,负债(Liabilities)也是资产的一部分。
    • 等式:Assets (资产) = Liabilities (债务) + Equity(权益,或者叫净资产,或者抵押资产)
  2. 使用方法(参考BeanCount软件)

    1. 账户类型
      • 资产 + Assets —— 储蓄卡余额、支付宝余额、股票账户余额、房子、车子固定资产等;
      • 负债 - Liabilities —— 信用卡、房贷、车贷, 贷款等;
      • 收入 - Income —— 工资、奖金, 投资收益等;
      • 费用 + Expenses —— 外出就餐、购物、旅行等;
      • 权益 - Equity —— 这个账户比较特殊,在账户初始化、误差处理等少数场合使用。
      1. 此外,账户后的金额是带有符号的
      • 支出账户:一般为正数。表示花费多少钱。
      • 收入账户:一般为负数。表示收入多少钱。
      • 投资收入账户可能出现正数,则表示投资亏损。
      • 资产账户:可正可负。正数表示有钱存入,余额增加; 负数表示有钱转出,余额减少。
      • 负债账户:可正可负。正数表示还款,负债减少; 负数表示借款,负债增加。

支出为正,收入为负,有点反直觉,是会计恒等式逻辑所致。会计恒等式具体表述如下:

(Assets + Expenses) + (Liabilities + Income) + Equity = 0

小试牛刀

操作记录 资产+ 负债- 收入- 费用+ 权益-
发工资 ¥100.00 100 -100
吃饭 ¥10.00 -10 10
信用卡购物 ¥199.00 -199 199
发工资 ¥200.00 200 -200
还信用卡 ¥199.00 -199 199
统计 91 -300 209

每行记录都是根据等式持平=0,可以清晰知道金额的流向。但是每次操作都需要记录两个以上的账户

例如信用卡购物的资金流向为:负债->(资产)->费用

寻找使用自己的记账法

以上为复式记账法的个人理解,优缺点都很明显,因此根据我个人需求进行分析。

需求

  1. 记账的主要需求用于控制消费。
  2. 控制消费需要 消费数据 和 负债数据。
  3. 需要一次业务一次操作。
  4. 账户优先级高低:负债>费用>资产

分析

关注的是负债和费用(即消费),其中消费包括 负债消费 和 资金消费
因此需要两个账户:

  • 负债账户(主负债流水:信用卡,花呗,京东)
  • 资金账户(主资金流水:微信,支付宝,储蓄卡,现金)

根据个人情况对应复式记账法分析行为:

业务操作 复式记账法操作(流向) 我的操作
收入 收入-资产 个人情况,不操作
资金消费 资产-费用 资金账户-支出
负债消费 负债-费用 负债账户-支出
还款 资产-负债 负债账户-还款

其中支出和还款为行为操作

  • 支出为消费记录(记录的集合即复式的费用账户)
  • 还款为负债账户的记录(记录集合即复式的还款流向)

还有借钱,负债还款都是用手机软件的功能,会形成操作记录

总结

复式记账法的特点是等式可以控制平账,且可看金额流向。
而我主要控制消费,关注消费情况和负债是否在合理消费,同时为了减少记账操作。
了解复式记账法可以更灵活调整适合自己的记账方法,网上也有复式记账的手机软件,无论是复式还是单账都要寻找适合自己的,不要为了记账而记账。

前言

因项目的DTO越来越多,且不同层级的DTO和javabean需要转换,大量使用bean.copy方法,造成性能下降,阅读性差的问题,准备引入MapStruct进行javabean数据的映射转换。

MapStruct是什么

MapStruct是一个Java注释处理器,用于为Java bean类生成类型安全和高性能的映射器。它使您不必手工编写映射代码,这是一个繁琐且容易出错的任务。该生成器具有合理的默认值和许多内置的类型转换,但是在配置或实现特殊行为时,它会自动退出。

优点

  1. 通过使用普通方法调用而不是反射来快速执行
  2. 编译时类型安全。只能映射彼此映射的对象和属性,因此不会将实体意外映射到客户DTO等等。
  3. 独立代码-没有运行时依赖
  4. 在构建时清除错误报告,如:
    a. 映射不完整(并非所有目标属性都被映射)
    b. 映射不正确(找不到正确的映射方法或类型转换)
  5. 易于调试的映射代码(或可以手动编辑,例如,如果生成器中有错误)

运行环境

这里使用的是MapStruct 1.3.1版本,需要Java 1.8或以上(1.2.x版本好像不支持Java 1.8,需要其他包代替)

简单说明

需要在两种类型之间创建映射,请声明一个映射器类,如下所示:

1
2
3
4
5
6
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToCarDto(Car car);
}

在编译时,MapStruct将生成此接口的实现。生成的实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即不涉及反射。默认情况下,如果属性在源和目标中具有相同的名称,则将映射它们,但是您可以使用@Mapping和少量其他注释来控制此属性和许多其他方面。

开发工具插件

IntelliJ idea 中可在插件市场中下载使用插件(https://plugins.jetbrains.com/plugin/10036-mapstruct-support
PS:这里用到Lombok,因此也需要下载idea插件市场的一个Lombok插件

确保您idea版本至少为 2018.2.x(由于对maven-compiler-plugin的注解处理器的支持来自该版本,因此是必需的)。
在IntelliJ中启用注释处理(Build, Execution, Deployment -> Compiler -> Annotation Processors)

Maven环境配置

对于基于Maven的项目,将以下内容添加到您的POM文件中以使用MapStruct(依赖关系在Maven Central中可用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
...
<properties>
<org.mapstruct.version>1.3.1.Final</org.mapstruct.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>

<!--添加Lombok的注册处理器-->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
...

碰到的问题

当MapStruct和Lombok混合使用时,会出现异常,找不到字段:
Warning:java: 来自注释处理程序 'net.java.dev.hickory.prism.internal.PrismGenerator' 的受支持 source 版本 'RELEASE_6' 低于 -source '1.8'
Error:(101, 16) java: No property named "numberOfSeats" exists in source parameter(s). Did you mean "null"?

解决方法只需要在<maven-compiler-plugin>节点的<annotationProcessorPaths>中添加:

1
2
3
4
5
6
<!--添加Lombok的注册处理器-->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>

下面是代码

需要 说明
源实体 Car 数据来源实体,数据库实体(this) 转换到 DTO
目标实体 CarDto 转换目标实体,DTO 转换到 响应实体(this)
映射interface CarMapper 定义用于转换映射的interface,MapStruct会实现这个接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package raven.spring.study;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.ToString;
import org.junit.jupiter.api.Test;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

class MapStructTests {

@Test
public void shouldMapCarToDtoTest() {

// 实例化数据对象
Car car = new Car( "Morris", 5, CarType.SEDAN ,200.19);

// 获得映射实例, 并调用转换方法
CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);

System.out.println(car);
System.out.println(carDto);

System.out.println("同类型同名:" + carDto.getMake().equals("Morris"));
System.out.println("同类型非同名" + (carDto.getSeatCount() == (5)));
System.out.println("字符串-枚举:" + carDto.getType().equals("SEDAN"));
System.out.println("非同类型非同名:" + carDto.getMaxCarSpeed().equals(String.valueOf(car.getMaxSpeed())));

/*
打印结果:
Car{make='Morris', numberOfSeats=5, type=CarType{key='SEDAN'}, maxSpeed=200.19}
CarDto{make='Morris', seatCount=5, type='SEDAN', maxCarSpeed='200.19'}
同类型同名:true
同类型非同名true
字符串-枚举:true
非同类型非同名:true
*/

}

}

// 数据实体->DTO 两种类型非常相似,只有座位数属性具有不同的名称,并且类型属性在Car类中具有特殊的枚举类型,但在DTO中是纯字符串。

/**
* 数据实体
*/

@Data
@AllArgsConstructor
@ToString
class Car {

private String make;
private int numberOfSeats;
private CarType type;
private double maxSpeed;
}

/**
* 传输DTO
*/

@Data
@ToString
class CarDto {

private String make;
private int seatCount;
private String type;
private String maxCarSpeed;
}


/**
* 枚举
*/

@AllArgsConstructor
@Getter
enum CarType {
SEDAN("SEDAN");
private String key;
}

/**
* 映射器
* 从Car对象中创建CarDto对象,需要定义一个映射器接口
*/

// @Mapper将接口标记为映射接口,并让MapStruct处理器在编译期间启动。
// 对于源对象和目标对象中名称不同的属性,@Mapping可用于配置名称。
// 当然,一个接口中可以有多种映射方法,MapStruct将为所有接口生成一个实现。
@Mapper
interface CarMapper {

// 可以从Mappers类检索接口实现的实例。按照约定,该接口声明一个成员INSTANCE,为客户端提供对映射器实现的访问。
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );

// 实际的映射方法将源对象作为参数并返回目标对象。其名称可以自由选择。
// 在必要和可能的情况下,将对源和目标中具有不同类型的属性执行类型转换,例如type属性将从枚举类型转换为字符串。
@Mapping(source = "numberOfSeats", target = "seatCount")
@Mapping(source = "maxSpeed", target = "maxCarSpeed")
CarDto carToCarDto(Car car);
}

总结

使用起来还是挺方便的,只需要新建一个映射器即可使用,不同的类型或者字段也能标记映射,阅读性更高,维护性也高。

前言

Github Pages之前使用jekyll搭建,好处是博客代码和Markdown可以放在同一个github仓库统一管理版本控制,但是换博客主题感觉有点麻烦,就有了jekyll换成Hexo的想法。

Hexo介绍

Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。

官网文档:https://hexo.io/zh-cn/docs/index.html

与jekyll相比:

  • jekyll提交Markdown到Github Pages就可以渲染部署使用;Hexo需要本地生成(编译?)后再推到服务器或者Github Pages才能部署使用。
  • jekyll放在Github Pages上的文件是Markdown;Hexo放到Github Pages上的文件是Html。
  • Hexo可以本地运行,预览博客效果
  • Hexo更换主题很方便;jekyll则Markdown放到主题文件里,更改比较麻烦

Hexo环境

  • NodeJs(Node.js 版本需不低于 8.10,建议使用 Node.js 10.0 及以上版本)
  • Git

Hexo本地环境搭建

  1. 安装Hexo
    $ npm install -g hexo-cli
  2. 初始化
    $ hexo init <folder>
    $ cd <folder>
    $ npm install
  3. 下载博客主题,这里用的是Nexthttps://github.com/theme-next/hexo-theme-next
  4. clone或者下载好后放到theme文件夹
    • 根目录下的_config.yml是站点配置(即博客的生成配置),<folder>/theme/<主题文件夹>下的_config.yml是主题配置
  5. 修改站点配置_config.yml
    • 有空格bug,需要删除空格(菜单路径都要删除空格)如: _config.yml 95行home: / || home改为home: /|| home
    • 语言改为中文 language: zh-Hans (Next 6.0后使用 zh-CN)
  6. 添加页面
    • 添加tags
    • 命令:$ hexo new page tags
      确认站点配置文件里有tag_dir: tags
      确认主题配置文件里有tags: /tags
      编辑站点的/source/tags/index.md,添加
      1
      2
      3
      4
      title: tags
      date: 2015-10-20 06:49:50
      type: "tags"
      comments: false
    • 添加分类
    • 命令:$ hexo new page categories
      确认站点配置文件里有category_dir: categories
      确认主题配置文件里有categories: /categories
      编辑站点的source/categories/index.md,添加
      1
      2
      3
      4
      title: categories
      date: 2015-10-20 06:49:50
      type: "categories"
      comments: false
  7. 添加本地搜索
    • 安装插件 hexo-generator-searchdb,在站点的根目录下执行以下命令:
      $ npm install hexo-generator-searchdb --save
    • 编辑站点配置文件,新增以下内容到任意位置:
      1
      2
      3
      4
      5
      search:
      path: search.xml
      field: post
      format: html
      limit: 10000
    • 编辑主题配置文件,启用本地搜索功能:
      1
      2
      3
      # Local search
      local_search:
      enable: true
  8. 支持推送Github pages
    • 安装插件,在站点的根目录下执行以下命令:
      $ npm install hexo-deployer-git --save
    • 编辑站点配置文件,新增以下内容到任意位置:
      1
      2
      3
      4
      deploy:
        type: git
        repository: https://github.com/winskin/winskin.github.io.git
        branch: master
  9. 主要命令说明:
操作 命令 说明
新建页面 hexo new 默认新建到_posts即为博文,也可以新建其他类型
生成文件 hexo g 生成文件,为html文件,用于部署
清理生成文件 hexo clean 清理生成的文件夹public,和文件db.json
本地运行 hexo s 本地运行public文件,访问地址问:localhost:4000(刷新配置时不需要重启)
发布部署 hexo d 发布到仓库,这里使用的是github,自动提交push,并Github Pages会自动运行渲染
  1. 总结

因为Hexo把Markdown和发布用的html分成两个部分,即需要两个仓库存储(或一个服务一个版本控制),我是新建一个仓库存储,再其他电脑下载的文件后,装上NodeJs和Git就能快速搭建已有的博客环境,与jekyll相比麻烦了不少,但是更加灵活,各有好处吧。

前言

开发微信第三方平台中,使用微信解密,开发环境一切正常,生产环境解密异常java.security.InvalidKeyException: Illegal key size

原因

由于jdk限制策略,导致只能128位key进行加解密,而256位加解密则抛出异常。

解决方法

如何从Centos中找到java的路径

命令:

  • which java

    [结果:/usr/bin/java]

  • ls -lrt /usr/bin/java

    [结果:/usr/bin/java -> /etc/alternatives/java]

  • ls -lrt /etc/alternatives/java

    [结果:/etc/alternatives/java -> /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.212.b04-0.el7_6.x86_64/jre/bin/java]

  • cd /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.212.b04-0.el7_6.x86_64/jre/bin/java

    该目录就是java的目录。

前言

SpringBoot1.x升级到SpringBoot2.x;SpringCloud从Camden升级到Hoxton后出现的不兼容问题。

版本

升级前名字 升级前版本号 升级后名字 升级后版本号
org.springframework 4.x org.springframework 5.2.2.RELEASE
org.springframework.boot 1.4.x org.springframework.boot 2.2.2.RELEASE
spring-cloud Camden.RELEASE (1.x) spring-cloud Hoxton.RELEASE (2.2.0)
spring-cloud-starter-feign 1.2.7.RELEASE spring-cloud-starter-openfeign 2.2.0.RELEASE
spring-cloud-starter-hystrix 1.2.7.RELEASE spring-cloud-starter-netflix-hystrix 2.2.0.RELEASE

升级后,其中的FeignHystrix包名完全更改,可以看出Feign的1.x使用的是Netflix开发的包,而2.x使用的是OpenFeign包。

在SpringCloud中使用Feign的区别

Netflix版本Feign提供的spring扫描器与OpenFeign版本的不一样,因此会出现以下情况。
  1. Netflix版本(spring-cloud-starter-feign 1.2.7.RELEASE)

    1
    2
    3
    @FeignClient(value=ServiceConfig.SERVICE_NAME,      //客户端名称,负载均衡用,Eureka中充当服务名
    path=ServiceConfig.CONTEXT_PATH //全局路径/前缀,微服务中一般为项目名等(如/login-service)
    )

    value是客户端的名称,但在Eureka中充当的是服务名,Spring扫描Bean时使用的是ClassName,因此使用@FeignClient注解所在类的类名充当bean的唯一标识。

  2. OpenFeign版本(spring-cloud-starter-openfeign 2.2.0.RELEASE)

    1
    2
    3
    4
    @FeignClient(contextId="client_id",          //客户端名称,只区分每个客户端类的bean唯一标识,与服务id不同
    value=ServiceConfig.SERVICE_NAME, //客户端名称,负载均衡用,Eureka中充当服务名
    path=ServiceConfig.CONTEXT_PATH //全局路径/前缀,微服务中一般为项目名等(如/login-service)
    )

    在2.2.0版本中,使用和1.2.7是一样的,但是Spring扫描Bean的时候使用的是name/value,因此在Eureka中同服务的客户端value都是同一个服务名,启动时候会报错:bean重复。

升级后的解决方法(两种方法选择其中一种即可)

  • 在@FeignClient后加入参数contextId="client_id",为客户端的唯一标识,只用于区分bean。
  • 在配置中加入spring.main.allow-bean-definition-overriding=true,允许bean覆盖。

Base64Encoder和Base64Decoder在Jdk9被删除

参考转载博客 来自https://blog.csdn.net/Cha0DD/article/details/87794268

问题起因

项目使用了Base64Encoder和Base64Decoder两个类,都来自sun包,idea编译时会提示该类是内部Api,可能会在未来版本删除,在升级springboot2的频繁编译测试后强迫症犯了

查询结果

  • Jdk8的使用,所在包 package sun.misc;

    1
    2
    3
    4
    5
    BASE64Encoder encoder = new BASE64Encoder();
    String imagestr =  encoder.encode(captcha);

    BASE64Decoder decoder = new BASE64Decoder();
    byte[] bytes = decoder.decodeBuffer(imagestr);
  • Jdk9的使用,已经无法使用sun.misc.BASE64Encodersun.misc.BASE64Decoder

    1
    2
    3
    4
    5
    6
    7
    8
    import java.util.Base64.Encoder;
    import java.util.Base64.Decoder;

    Encoder encoder = Base64.getEncoder();
    String result = encoder.encodeToString(byteArray);

    Decoder decoder = Base64.getDecoder();
    byte[] result = decoder.decode(str);

原因

原因是/lib/tool.jar和/lib/rt.jar已经从Java SE 9中删除

Python循环陷阱-删除

使用数组循环删除时引发的问题

Demo Debug

  1. Demo1

    • 正常循环数组,一共有6个元素,从索引0开始到索引5结束(下图为未开始循环)
      Image text

    • 当循环第一次的时,i=1,即list_1[1]取得2,进入if判断ture并删除;循环第二次时,i=2,即list_1[2]取得4(下图为循环第2次)
      Image text

    • 当第五次循环时,i=5,即list_1[5],数组越界报错IndexError,(下图为循环第5次)
      Image text

      由此可见,触发删除元素时list_1[1]=2变为list_1[1]=3,后面的元素下标索引全部前进一位,补充删除的位置,同时位数-1

  2. Demo2

    • 正常循环数组,一共有6个元素,从索引0开始到索引5结束(下图为第1次循环,未删除)
      Image text

    • 第一次循环结束并删除第一个元素后,进入循环第二次判断,可见list_2[0]=1变成list_2[0]=2, 与Demo1一样全部元素前进一位
      Image text

    • 循环第三次,下标索引为2,获得i=5,执行完成后,因数组长度为3,已遍历完毕,打印[2, 4, 6]
      Image text

      由此可见,每次删除一个元素,该元素后面的元素都会向前一位,同时数组长度-1,Demo2的循环删除数组也不会清空,因前进一位躲过一劫,数组长度为原长度的1/2

解决方法

  1. 方法1:反向遍历

    1
    2
    3
    4
    5
    # 问题二
    list_2 = [1, 2, 3, 4, 5, 6]
    for i in list_2[::-1]:
    list_2.remove(i)
    print(list_2) # []

    使用反向遍历,每次删除元素都会向前,而每次遍历都是最后一位开始,则可以正常遍历所有元素

  2. 方法2:copy一个临时数组,用于遍历

    1
    2
    3
    4
    5
    6
    # 问题二
    list_2 = [1, 2, 3, 4, 5, 6]
    list_tmp = list_2.copy()
    for i in list_tmp:
    list_2.remove(i)
    print(list_2) # []

    使用copy()方法复制出一个临时数组list_tmp,循环遍历list_tmp,操作则使用list_2原数组

  3. Demo1的解决方法也可以方法2的copy临时数组处理

    1
    2
    3
    4
    5
    6
    7
    8
    # 问题一
    list_1 = [1, 2, 3, 4, 5, 6]
    list_tmp = list_1.copy()
    for i in range(len(list_tmp)):
    if list_tmp[i] == 2:
    list_1.pop(i)

    print(list_1) # [1, 3, 4, 5, 6]

总结

python的数组循环删除会直接影响或打乱下标索引的位置,若删除元素应尽量避免使用下标索引获取元素,且copy临时数组的性能比较糟糕,建议使用反向循环(即方法1)

雪花算法生成分布式id

介绍

雪花算法的原始版本是scala版,用于生成分布式ID(纯数字,时间顺序),订单编号等。
SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter数据中心ID和workerId机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。

其他id:

  1. 自增ID:对于数据敏感场景不宜使用,且不适合于分布式场景。
  2. GUID:采用无意义字符串,数据量增大时造成访问过慢,且不宜排序。

图解

Image text

  • 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
  • 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截),
  • 开始时间截一般是我们的id生成器开始使用的时间,由我们程序来指定的。
  • 41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
  • 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
  • 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
    加起来刚好64位,为一个Long型。(转换成字符串后长度最多19)

优点:

  1. 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
  2. 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
  3. 可以根据自身业务特性分配bit位,非常灵活。

缺点:

  1. 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。