开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Git版本回退方法论(可能解决你101%遇到的Git版本问题

发表于 2021-11-01

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」

1 本地回退

你在本地做了错误的 commit,先找到要回退的版本的commit id:

1
bash复制代码git reflog


接着回退版本:

1
bash复制代码git reset --hard cac0

cac0就是你要回退的版本的commit id的前面几位

回退到某次提交。回退到的指定提交以后的提交都会从提交日志上消失。

工作区和暂存区的内容都会被重置到指定提交的时候,如果不加--hard则只移动HEAD指针,不影响工作区和暂存区的内容。

结合git reflog找回提交日志上看不到的版本历史,撤回某次操作前的状态
这个方法可以对你的回退操作进行回退,因为这时候git log已经找不到历史提交的hash值了。

2 远程回退

2.1 回退自己的远程分支

你的错误commit已经推送到远程分支,就需要回滚远程分支。

  • 首先要回退本地分支:
1
2
bash复制代码git reflog
git reset --hard cac0

  • 由于本地分支回滚后,版本将落后远程分支,必须使用强制推送覆盖远程分支,否则后面将无法推送到远程分支。
1
bash复制代码git push -f

  • 注意修正为git push -f origin branch_name

2.2 回退公共远程分支

如果你回退公共远程分支,把别人的提交给丢掉了怎么办?

本人毕业时在前东家 hw 经常干的蠢事。

分析

假如你的远程master分支情况是这样的:

1
bash复制代码A1–A2–B1

A、B分别代表两个人
A1、A2、B1代表各自的提交
所有人的本地分支都已经更新到最新版本,和远程分支一致

这时发现A2这次commit有误,你用reset回滚远程分支master到A1,那么理想状态是你的同事一拉代码git pull,他们的master分支也回滚了
然而现实却是,你的同事会看到下面的提示:

1
2
3
4
5
bash复制代码$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working directory clean

也就是说,你的同事的分支并没有主动回退,而是比远程分支超前了两次提交,因为远程分支回退了。
不幸的是,现实中,我们经常遇到的都是猪一样的队友,他们一看到下面提示:

1
2
3
4
5
bash复制代码$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working directory clean

就习惯性的git push一下,或者他们直接用的SourceTree这样的图形界面工具,一看到界面上显示的是推送的提示就直接点了推送按钮,卧槽,辛辛苦苦回滚的版本就这样轻松的被你猪一样的队友给还原了,所以,只要有一个队友push之后,远程master又变成了:

1
bash复制代码A1 – A2 – B1

这就是分布式,每个人都有副本。

用另外一种方法来回退版本。

3 公共远程回退

使用git reset回退公共远程分支的版本后,需要其他所有人手动用远程master分支覆盖本地master分支,显然,这不是优雅的回退方法。

1
2
3
bash复制代码git revert HEAD                     //撤销最近一次提交
git revert HEAD~1 //撤销上上次的提交,注意:数字从0开始
git revert 0ffaacc //撤销0ffaacc这次提交

git revert 命令意思是撤销某次提交。它会产生一个新的提交,虽然代码回退了,但是版本依然是向前的,所以,当你用revert回退之后,所有人pull之后,他们的代码也自动的回退了。但是,要注意以下几点:

  • revert 是撤销一次提交,所以后面的commit id是你需要回滚到的版本的前一次提交
  • 使用revert HEAD是撤销最近的一次提交,如果你最近一次提交是用revert命令产生的,那么你再执行一次,就相当于撤销了上次的撤销操作,换句话说,你连续执行两次revert HEAD命令,就跟没执行是一样的
  • 使用revert HEAD~1 表示撤销最近2次提交,这个数字是从0开始的,如果你之前撤销过产生了commi id,那么也会计算在内的
  • 如果使用 revert 撤销的不是最近一次提交,那么一定会有代码冲突,需要你合并代码,合并代码只需要把当前的代码全部去掉,保留之前版本的代码就可以了
  • git revert 命令的好处就是不会丢掉别人的提交,即使你撤销后覆盖了别人的提交,他更新代码后,可以在本地用 reset 向前回滚,找到自己的代码,然后拉一下分支,再回来合并上去就可以找回被你覆盖的提交了。

4 revert 合并代码,解决冲突

使用revert命令,如果不是撤销的最近一次提交,那么一定会有冲突,如下所示:

1
2
3
4
5
6
bash复制代码<<<<<<< HEAD
全部清空
第一次提交
=======
全部清空
>>>>>>> parent of c24cde7... 全部清空

解决冲突很简单,因为我们只想回到某次提交,因此需要把当前最新的代码去掉即可,也就是HEAD标记的代码:

1
2
3
4
bash复制代码<<<<<<< HEAD
全部清空
第一次提交
=======

把上面部分代码去掉就可以了,然后再提交一次代码就可以解决冲突了。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

springboot聚合工程讲解与部署

发表于 2021-11-01

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

1.什么是聚合工程

如下图所示,拥有父子包结构的springboot工程叫做聚合工程。其中父包包括了多个子包(多个项目)。
在这里插入图片描述

2.聚合工程优势

  1. 组件化管理通用功能,动态的添加功能。
  2. 统一管理jar包的版本号
  3. 提高复用性

3.创建聚合工程

  1. 首先创建一个springboot项目,然后删除src文件夹 。
  2. 创建子工程
    在这里插入图片描述
    选择spring boot项目,然后继续,创建项目。如果是工具,配置,组件化的项目(不需要启动)可以继续删除启动类和resources文件夹。
    在这里插入图片描述
  3. 修改父工程和子工程pom文件。然后聚合工程就搭建好了,这里最重点是pom文件的配置和标签,下文就开始介绍。

4.父工程详解

ps:这里以若依开源项目为例,结构如下,我们主要讲解红框里面的配置,父工程为RuoYi-Cloud,包括ruoyi-auth,ruoyi-gateway,ruoyi-visual,ruoyi-api,ruoyi-common子项目。而ruoyi-modules子工程也包括多个子工程。
在这里插入图片描述#

1.pom文件

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
bash复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>2.5.0</version>

<name>ruoyi</name>
<url>http://www.ruoyi.vip</url>
<description>若依微服务系统</description>

<properties>
<ruoyi.version>2.5.0</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
<spring-boot-admin.version>2.3.1</spring-boot-admin.version>
<spring-boot.mybatis>2.1.4</spring-boot.mybatis>
<swagger.fox.version>2.9.2</swagger.fox.version>
<swagger.core.version>1.5.24</swagger.core.version>
<tobato.version>1.26.5</tobato.version>
<kaptcha.version>2.3.2</kaptcha.version>
<pagehelper.boot.version>1.3.0</pagehelper.boot.version>
<druid.version>1.2.4</druid.version>
<dynamic-ds.version>3.2.1</dynamic-ds.version>
<commons.io.version>2.5</commons.io.version>
<commons.fileupload.version>1.3.3</commons.fileupload.version>
<velocity.version>1.7</velocity.version>
<fastjson.version>1.2.75</fastjson.version>
<minio.version>8.0.3</minio.version>
<poi.version>4.1.2</poi.version>
<common-pool.version>2.6.2</common-pool.version>
</properties>

<!-- 依赖声明 -->
<dependencyManagement>
<dependencies>

<!-- SpringCloud 微服务 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- SpringCloud Alibaba 微服务 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- SpringBoot 依赖配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- SpringBoot 监控客户端 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${spring-boot-admin.version}</version>
</dependency>

<!-- FastDFS 分布式文件系统 -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>${tobato.version}</version>
</dependency>

<!-- Mybatis 依赖配置 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${spring-boot.mybatis}</version>
</dependency>

<!-- Swagger 依赖配置 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>${swagger.core.version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger.core.version}</version>
</dependency>

<!-- 验证码 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version>
</dependency>

<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.boot.version}</version>
</dependency>

<!-- io常用工具类 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>

<!-- excel工具 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>

<!-- 文件上传工具类 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons.fileupload.version}</version>
</dependency>

<!-- 代码生成使用模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>${velocity.version}</version>
<exclusions>
<exclusion>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- JSON 解析器和生成器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>

<!-- 公共资源池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${common-pool.version}</version>
</dependency>

<!-- 核心模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-core</artifactId>
<version>${ruoyi.version}</version>
</dependency>

<!-- 接口模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-swagger</artifactId>
<version>${ruoyi.version}</version>
</dependency>

<!-- 安全模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-security</artifactId>
<version>${ruoyi.version}</version>
</dependency>

<!-- 权限范围 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datascope</artifactId>
<version>${ruoyi.version}</version>
</dependency>

<!-- 多数据源 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datasource</artifactId>
<version>${ruoyi.version}</version>
</dependency>

<!-- 日志记录 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-log</artifactId>
<version>${ruoyi.version}</version>
</dependency>

<!-- 缓存服务 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId>
<version>${ruoyi.version}</version>
</dependency>

<!-- 系统接口 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-api-system</artifactId>
<version>${ruoyi.version}</version>
</dependency>

</dependencies>
</dependencyManagement>

<modules>
<module>ruoyi-auth</module>
<module>ruoyi-gateway</module>
<module>ruoyi-visual</module>
<module>ruoyi-modules</module>
<module>ruoyi-api</module>
<module>ruoyi-common</module>
</modules>
<packaging>pom</packaging>

<dependencies>
<!-- swagger -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.10</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>

</project>
1
2
3
4
5
6
7
8
9
js复制代码<name><url><description>都是非必须,字面意思。
<groupId>:域 例如com
<artifactId>:公司名 groupid和artifactId被统称为“坐标”是为了保证项目唯一性而提出的
<version>:项目的版本号
<properties>:属性的占位符 如java中的常量

<dependencyManagement>:在我们项目顶层的POM文件中,我们会看到dependencyManagement元素。通过它元素来管理jar包的版本,让子项目中引用一个依赖而不用显示的列出版本号。Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用在这个dependencyManagement元素中指定的版本号。

<dependencies>:指定所有的子工程都下载该包。

白话说 如果父工程有,且groupId和artifactId相同,那么子工程不需要指定版本号, 直接使用父工程版本号。

例如:当子工程有groupId为org.springframework.cloud时,引用当前父工程指定版本。子工程不用在写版本号了。

1
2
3
4
5
6
7
8
js复制代码<!-- SpringCloud 微服务 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
1
2
3
js复制代码<modules>:指当前父工程包含的子工程 如果这里不引入 那么 刷新父工程pom时 子工程不会下载jar包
<build>:打包配置
<packaging>pom</packaging>在聚合工程中 一定要配置 指的是在引入其他工程时 直接引入代码,否则就会打成jar包 这样就会报错了

5.ruoyi-modules子工程配置

这里我们选择一个最有代表性的子工程ruoyi-modules来介绍。

1.pom文件

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
js复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>2.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<modules>
<module>ruoyi-system</module>
<module>ruoyi-gen</module>
<module>ruoyi-job</module>
<module>ruoyi-file</module>
</modules>

<artifactId>ruoyi-modules</artifactId>
<packaging>pom</packaging>

<description>
ruoyi-modules业务模块
</description>

</project>

这里主要配置是

1
2
js复制代码<parent>:内容就是父工程的<groupId><artifactId><version>
<modules>:指该工程包含ruoyi-system,ruoyi-gen,ruoyi-job,ruoyi-file子工程

2.ruoyi-system子项目配置

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
bash复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>2.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>ruoyi-modules-system</artifactId>

<description>
ruoyi-modules-system系统模块
</description>

<dependencies>

<!-- SpringCloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- SpringCloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- SpringCloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- SpringBoot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- Swagger UI -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.fox.version}</version>
</dependency>

<!-- Mysql Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- RuoYi Common DataSource -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datasource</artifactId>
</dependency>

<!-- RuoYi Common DataScope -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datascope</artifactId>
</dependency>

<!-- RuoYi Common Log -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>

<!-- RuoYi Common Swagger -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-swagger</artifactId>
</dependency>

</dependencies>

<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
1
2
js复制代码<parent>:配置父工程ruoyi-modules的<groupId><artifactId><version>
<dependencies>:本项目的依赖

如果想引入其他工程,只需要加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
js复制代码        <!-- RuoYi Common DataScope -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datascope</artifactId>
</dependency>

<!-- RuoYi Common Log -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>

<!-- RuoYi Common Swagger -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-swagger</artifactId>
</dependency>

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

高级JAVA开发必备技能:java8 新日期时间API((一

发表于 2021-11-01

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」

❤️作者简介:Java领域优质创作者🏆,CSDN博客专家认证🏆,华为云享专家认证🏆

❤️技术活,该赏

❤️点赞 👍 收藏 ⭐再看,养成习惯

大家好,我是小虚竹。之前有粉丝私聊我,问能不能把JAVA8 新的日期时间API(JSR-310)知识点梳理出来。答案是肯定的,谁让我宠粉呢。由于内容偏多,会拆成多篇来写。

闲话就聊到这,请看下面的正文。

第一节:概念知识

时区

由于世界各国家与地区经度不同,地方时也有所不同,因此会划分为不同的时区。
正式的时区划分包括24个时区,每一时区由一个英文字母表示,每隔经度15°划分一个时区。
为了克服时间上的混乱,1884年在华盛顿召开的一次国际经度会议(又称国际子午线会议)上,规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为中时区(零时区)、东1—12区,西1—12区。每个时区横跨经度15度,时间正好是1小时。最后的东、西第12区各跨经度7.5度,以东、西经180度为界。每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时,相邻两个时区的时间相差1小时。
例如,中国东8区的时间总比泰国东7区的时间早1小时,而比日本东9区的时间晚1小时。
–引用自百度百科

时区经度分布如列表所示:

时区 时区经度范围 时区中心线
UTC(0时区) 7.5°W~7.5°E 0°
UTC+1(东1区) 7.5°E~22.5°E 15°E
UTC+2(东2区) 22.5°E~37.5°E 30°E
UTC+3(东3区) 37.5°E~52.5°E 45°E
UTC+4(东4区) 52.5°E~67.5°E 60°E
UTC+5(东5区) 67.5°E~82.5°E 75°E
UTC+6(东6区) 82.5°E~97.5°E 90°E
UTC+7(东7区) 97.5°E~112.5°E 105°E
UTC+8(东8区) 112.5°E~127.5°E 120°E
UTC+9(东9区) 127.5°E~142.5°E 135°E
UTC+10(东10区) 142.5°E~157.5°E 150°E
UTC+11(东11区) 157.5°E~172.5°E 165°E
UTC12(东、西12区) 172.5°E~172.5°W 180°
UTC-11(西11区) 172.5°W~157.5°W 165°W
UTC-10(西10区) 157.5°W~142.5°W 150°W
UTC-9(西9区) 142.5°W~127.5°W 135°W
UTC-8(西8区) 127.5°W~112.5°W 120°W
UTC-7(西7区) 112.5°W~97.5°W 105°W
UTC-6(西6区) 97.5°W~82.5°W 90°W
UTC-5(西5区) 82.5°W~67.5°W 75°W
UTC-4(西4区) 67.5°W~52.5°W 60°W
UTC-3(西3区) 52.5°W~37.5°W 45°W
UTC-2(西2区) 37.5°W~22.5°W 30°W
UTC-1(西1区) 22.5°W~7.5°W 15°W

实际上,常常1个国家或1个省份同时跨着2个或更多时区,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。例如,中国幅员宽广,差不多跨5个时区,但为了使用方便简单,实际上在只用东八时区的标准时即北京时间为准。

UTC

协调世界时,又称世界统一时间、世界标准时间、国际协调时间。由于英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC。
协调世界时是以原子时秒长为基础,在时刻上尽量接近于世界时的一种时间计量系统。
国际原子时的准确度为每日数纳秒,而世界时的准确度为每日数毫秒。许多应用部门要求时间系统接近世界时UT,对于这种情况,一种称为协调世界时的折中时标于1972年面世。为确保协调世界时与世界时相差不会超过0.9秒,在有需要的情况下会在协调世界时内加上正或负闰秒。因此协调世界时与国际原子时之间会出现若干整数秒的差别,两者之差逐年积累,便采用跳秒(闰秒)的方法使协调时与世界时的时刻相接近,其差不超过1s。它既保持时间尺度的均匀性,又能近似地反映地球自转的变化。
–引用自百度百科

协调世界时跟地区位置没有相关,不代表当前时刻某个地方的时间,所以在说某个地方时间时要加上时区。例如:中国就是UTC+8。

UTC是时间标准,这个标准把世界分成UTC-12到UTC+12共24个时区。

GMT

GMT(Greenwich Mean Time)别名:格林尼治时间(有时候翻译也叫格林威治),中文名:世界时。

GMT是指格林尼治所在地的标准时间,也是表示地球自转速率的一种形式。以地球自转为基础的时间计量系统。地球自转的角度可用地方子午线相对于地球上的基本参考点的运动来度量。为了测量地球自转,人们在地球上选取了两个基本参考点:春分点(见分至点)和平太阳点,由此确定的时间分别称为恒星时和平太阳时。
–引用自百度百科

GMT并不等于UTC,只是格林尼治刚好在0时区上。所以GMT = UTC+0才是对的。

CST

CST可视为美国、澳大利亚、古巴或中国的标准时间
美国中部时间:Central Standard Time (USA) UT-6:00
澳大利亚中部时间:Central Standard Time (Australia) UT+9:30
中国标准时间:China Standard Time UT+8:00
古巴标准时间:Cuba Standard Time UT-4:00

–引用自百度百科

所以在换算CST时间时,要注意对应的时区。这是一个坑。

美国中部时间:CST=UTC/GMT-6;

中国标准时间:CST=UTC/GMT+8;

DST

DST(Daylight Saving Time)中文名:夏令时。

表示为了节约能源,人为规定时间的意思。也叫夏时制,夏令时(Daylight Saving Time:DST),又称“日光节约时制”和“夏令时间”,在这一制度实行期间所采用的统一时间称为“夏令时间”。一般在天亮早的夏季人为将时间调快一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。全世界有近110个国家每年要实行夏令时。
–引用自百度百科

中国实现DST时间范围:1986年至1991年。

ISO-8601

国际标准化组织的国际标准ISO 8601是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前最新为第三版ISO8601:2004,第一版为ISO8601:1988,第二版为ISO8601:2000
–引用自百度百科

年由4位数字组成YYYY,或者带正负号的四或五位数字表示±YYYYY。以公历公元1年为0001年,以公元前1年为0000年,公元前2年为-0001年。

月、日用两位数字表示:MM、DD。

只使用数字为基本格式。使用短横线”-“间隔开年、月、日为扩展格式。

小时、分和秒都用2位数表示,对UTC时间最后加一个大写字母Z,其他时区用实际时间加时差表示。如UTC时间下午2点30分5秒表示为14:30:05Z或143005Z,当时的北京时间表示为22:30:05+08:00或223005+0800,也可以简化成223005+08。

注:大家还记得java的Date类吗?它默认就是使用ISO-8601表示的。

第二节:JDK8之前:时区/偏移量TimeZone

在JDK8之前,我们一直用java.util.TimeZone来表示和处理时区和偏移量。

**TimeZone.getDefault()**获得当前JVM所运行的时区,那它是怎么获取默认时区的呢,之前有写过分析文章,有兴趣的可以了解下,这里就不再重复了。

JDK获取默认时区的风险和最佳实践

有时候需要做时区的时间转换,比如一个时间要用北京时间和纽约时间显示。实现:

这里没有到SimpleDateFormat 来格式化时间是因为它是线程不安全的。选用线程安全的FastDateFormat,

Apache Commons Lang包支持。

有兴趣可以了解下FastDateFormat 的源码分析:java的SimpleDateFormat线程不安全出问题了,虚竹教你多种解决方案

1
2
3
4
5
6
7
8
9
java复制代码		String patternStr = "yyyy-MM-dd HH:mm:ss";
// 北京时间(new出来就是默认时区的时间)
Date bjDate = new Date();
// 得到纽约的时区
TimeZone newYorkTimeZone = TimeZone.getTimeZone("America/New_York");
// 根据此时区 将北京时间转换为纽约的Date
FastDateFormat fastDateFormat = FastDateFormat.getInstance(patternStr,newYorkTimeZone);
System.out.println("这是北京时间:" + FastDateFormat.getInstance(patternStr).format(bjDate));
System.out.println("这是纽约时间:" + fastDateFormat.format(bjDate));

image-202108109417345

19-7=12 北京时间比纽约时间快12小时。

image-202108101999961

第三节:JDK8开始支持:时区/偏移量 ZoneId/ZoneOffset

JDK8中ZoneId表示时区的ID,ZoneOffset表示Greenwich/UTC的偏移量。

image-2021081095044291

ZoneId 是用来替换java.util.TimeZone 的。

我们来研究下ZoneId ,ZoneId代表一个时区的ID,它是确定的。但是时区ID是有对应的规则,规则变化为java.time.zone.ZoneRules 决定。像夏令时规则是由各国政府定的,可能会变化,不同的年还不一样,这个就交给JDK底层机制来保持同步,我们调用者不需要关心(不!要关心!当技术不再是黑盒时,才能做到心里有底! )。

时区的规则发生变化时,如何同步时区

TZUpdater 工具介绍

​ 提供的 TZUpdater 工具 允许您使用更新的时区数据更新已安装的 Java 开发工具包 (JDK) 和 Java 运行时环境 (JRE) 软件,以适应不同国家/地区的夏令时 (DST) 更改。Oracle 依赖于通过 IANA 的时区数据库公开提供的时区数据。

如果您无法使用 Oracle 最新的 JDK 或 JRE 更新版本,或者如果最新版本上的时区数据不是最新可用的,TZUpdater 工具提供了一种更新时区数据的方法,同时保持其他系统配置和依赖项不变.

TZUpdater 工具用法

TZUpdater 工具用于执行该工具的 JDK/JRE 软件实例。每次执行都会修改 JDK/JRE 软件。要将工具管理到 JDK/JRE 软件的多个实例。

在安装的 JDK/JRE 软件上运行 TZUpdater 工具之前,您必须停止操作系统上的 JDK/JRE 软件的任何正在运行的服务。

使用以下命令运行 TZUpdater 工具:

1
复制代码java -jar tzupdater.jar options

要成功更新时区数据,您应该确保您有足够的权限来修改JDK_HOME /jre/lib或JRE_HOME /lib目录。

如果未指定任何选项,则会显示用法消息。要更新时区数据,请使用-l或-f选项。

选项 描述
-h, --help 将用法打印到stdout并退出。如果指定此选项,则其他选项将被忽略。
-V, --version 打印工具版本、JRE 中的 tzdata 版本以及工具将更新到的 tzdata 版本,然后退出。
-l, --location url-link-to-archive-file 从提供的tzdata.tar.gz包中编译、测试和更新 JRE 时区数据,例如-l https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz. 支持的 URL 协议:http://、https://、file://。如果未提供 URL 链接,该工具将使用位于 的最新 IANA tzdata 包https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz。
-f, --force 强制 tzdata 更新。如果更新到较旧的 tzdata 版本,请使用此选项。
-v, --verbose 向 显示详细消息stdout。

手动升级

注意:

1、在安装的 JDK/JRE 软件上运行 TZUpdater 工具之前,您必须停止操作系统上的 JDK/JRE 软件的任何正在运行的服务。

2、要成功更新时区数据,您应该确保您有足够的权限来修改JDK_HOME /jre/lib或JRE_HOME /lib目录。(linux系统:JRE目录要有写权限;windows系统:用管理员身份运行cmd)

3、如果系统上有多个JDK/JRE ,需要将该工具用于每个JDK/JRE中(每个JDK/JRE都要操作一遍)

4、更新成功后,要重新启动此 JDK/JRE 实例上的应用程序服务(如果还没更新,重启下服务器试试)

操作步骤:

1、下载Oracle官方提供的tzupdater.jar包;下载地址

www.oracle.com/java/techno…

把tzupdater.jar放到java目录bin目录下,比如

1
makefile复制代码“C:\Program Files\JAVA\java-1.8.0-openjdk-1.8.0.201\bin\tzupdater.jar”;

image-20210811935505

2、查看当前时区数据库版本,以windows为例,用管理员身份运行cmd,切换到tzupdater.jar对应的目录:

1
复制代码java -jar tzupdater.jar -V

image-20210811933080

3、在线更新,以windows为例,用管理员身份运行cmd,切换到tzupdater.jar对应的目录:(第3种和第4种更新方式任选一种)

1
java复制代码java -jar tzupdater.jar -l https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz

image-2021081193461

如图所示,已经更新成功到了tzdata2021a版本了。

更新后的文件是放在jre/lib/tzdb.dat ,如图所示,它有备份历史的版本。

image-2021081198385

4、离线更新:要先下载最新的时区数据,下载地址:

data.iana.org/time-zones/…

image-202108119194

以windows为例,用管理员身份运行cmd。切换到tzupdater.jar对应的目录:

1
java复制代码java -jar tzupdater.jar -l file:///[path]/tzdata.tar.gz

注:

windows建议放在C盘根目录下,路径目录也不要有中文;

用管理员身份运行cmd(需要写权限);

如上面的命令所示,file后面的/是3个

5、以上执行完后,用第2步的查看当前时区数据库版本命令,查看是否更新成功。

image-2021081193461

服务自动化升级

思路步骤:

1、设置定时任务(操作系统配置就行),执行tzupdater 更新时区的命令脚本;

2、新开一个时区服务,用来对外提供时区和夏令时规则读取服务,独立部署;

3、在时区服务中,写个同步按钮,用来执行tzupdater 更新时区的命令脚本;

4、在时区服务中,将timeZone数据定时写到自定义的时区表中。提供维护功能,可以自定义新增修改删除timeZone数据。

此思路的好处:

1、其他服务不需要停止服务来更新时间,直接通过调用时区服务的数据,可保证获取到最新的时区数据;

2、自动化的好处,避免了手动维护时区的繁琐,人工介入有引发问题的风险;

3、时区服务和其他业务服务是拆分的,方便未来的扩展。

系统默认的ZoneId

1
2
3
4
5
6
java复制代码	@Test
public void timeZoneTest2(){
System.out.println("JDK 8之前做法:"+TimeZone.getDefault());

System.out.println("JDK 8之后做法:"+ZoneId.systemDefault());
}

image-202108109107753

1
2
3
4
java复制代码ZoneId.systemDefault()方法实现上是调用了TimeZone:
public static ZoneId systemDefault() {
return TimeZone.getDefault().toZoneId();
}

所以两个的结果是一样的(Asia/Shanghai),这个很正常。

TimeZone.toZoneId() 是java8 后加的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码   /**
* Converts this {@code TimeZone} object to a {@code ZoneId}.
*
* @return a {@code ZoneId} representing the same time zone as this
* {@code TimeZone}
* @since 1.8
*/
public ZoneId toZoneId() {
String id = getID();
if (ZoneInfoFile.useOldMapping() && id.length() == 3) {
if ("EST".equals(id))
return ZoneId.of("America/New_York");
if ("MST".equals(id))
return ZoneId.of("America/Denver");
if ("HST".equals(id))
return ZoneId.of("America/Honolulu");
}
return ZoneId.of(id, ZoneId.SHORT_IDS);
}

指定字符串得到ZoneId和获取所有的zoneIds

1
2
3
java复制代码		System.out.println(ZoneId.of("America/New_York"));

System.out.println(ZoneId.of("Asia/Shanghai"));

image-20210810990025341

1
2
3
4
5
6
7
8
java复制代码	@Test
public void ZoneIdTest2(){
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
System.out.println("zoneIds长度:"+zoneIds.size());
for(String zoneId : zoneIds){
System.out.println(zoneId);
}
}

image-20210810210652005

指定的字符串不能乱写,不然会报错,要在ZoneId.getAvailableZoneIds() 的集合范围里。

从日期中获取时区

1
2
3
java复制代码System.out.println(ZoneId.from(ZonedDateTime.now()));

System.out.println(ZoneId.from(ZoneOffset.of("+8")));

image-202108109411428

从日期中获取时区只支持带有时区的TemporalAccessor ,像LocalDateTime,LocalDate是不可以的,会报错。

1
2
3
4
5
6
7
8
9
10
java复制代码		try {
System.out.println(ZoneId.from(LocalDateTime.now()));
}catch (Exception e){
e.printStackTrace();
}
try {
System.out.println(ZoneId.from(LocalDate.now()));
}catch (Exception e){
e.printStackTrace();
}

image-202108109721346

ZoneId是抽象类,它有两个继承实现类:

  • ZoneOffset:时区偏移量
  • ZoneRegion:地理区域

image-20210810927677

ZoneOffset(时区偏移量)

时区偏移量是时区与Greenwich/UTC之间的时间差,一般是固定的小时数和分钟数。

最小/最大偏移量

1
2
3
4
5
6
7
8
java复制代码	@Test
public void ZoneIdTest5(){
System.out.println("最小偏移量:" + ZoneOffset.MIN);
System.out.println("最小偏移量:" + ZoneOffset.MAX);
System.out.println("中心偏移量:" + ZoneOffset.UTC);
// 超出最大范围
System.out.println(ZoneOffset.of("+100"));
}

image-202108109251152

超出最大范围会报错

时分秒构造偏移量

1
2
3
4
5
6
7
8
java复制代码	@Test
public void ZoneIdTest6(){
System.out.println(ZoneOffset.ofHours(10));
System.out.println(ZoneOffset.ofHoursMinutes(10, 10));
System.out.println(ZoneOffset.ofHoursMinutesSeconds(10, 10, 10));

System.out.println(ZoneOffset.ofHours(-10));
}

image-202108109621080

挺方便的,也简单好理解。偏移量可以精确到秒级。

image-202108109915102

ZoneRegion(地理区域)

ZoneRegion表示地理区域,格式是:洲(州、国家)/城市。最常见的区域分类是时区数据库(TZDB)。

which defines regions such as ‘Europe/Paris’ and ‘Asia/Tokyo’.(TZDB使用“Europe/Paris”和“Asia/Tokyo”来区分地区。)

1
2
3
java复制代码final class ZoneRegion extends ZoneId implements Serializable {
...
}

由源码可知,地理区域ZoneRegion是ZoneId的继承实现类。

但是我们发现这个不是对外使用的,ZoneRegion的修饰符是default(只能由同包下的类调用)。只能通过ZoneId来操作。

1
2
3
4
5
java复制代码	@Test
public void ZoneIdTest7(){
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);
}

image-2021081199155

博主在厦门,所以默认获取的时区ID是Asia/Shanghai。

ZoneId的实例是ZoneOffset或ZoneRegion

ZoneId of(String zoneId, boolean checkAvailable) 源码分析:

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
java复制代码/**
* Parses the ID, taking a flag to indicate whether {@code ZoneRulesException}
* should be thrown or not, used in deserialization.
*
* @param zoneId the time-zone ID, not null
* @param checkAvailable whether to check if the zone ID is available
* @return the zone ID, not null
* @throws DateTimeException if the ID format is invalid
* @throws ZoneRulesException if checking availability and the ID cannot be found
*/
static ZoneId of(String zoneId, boolean checkAvailable) {
Objects.requireNonNull(zoneId, "zoneId");
if (zoneId.length() <= 1 || zoneId.startsWith("+") || zoneId.startsWith("-")) {
return ZoneOffset.of(zoneId);
} else if (zoneId.startsWith("UTC") || zoneId.startsWith("GMT")) {
return ofWithPrefix(zoneId, 3, checkAvailable);
} else if (zoneId.startsWith("UT")) {
return ofWithPrefix(zoneId, 2, checkAvailable);
}
return ZoneRegion.ofId(zoneId, checkAvailable);
}


private static ZoneId ofWithPrefix(String zoneId, int prefixLength, boolean checkAvailable) {
String prefix = zoneId.substring(0, prefixLength);
if (zoneId.length() == prefixLength) {
return ofOffset(prefix, ZoneOffset.UTC);
}
...
}

image-20210819856624

由源码可知:

  1. zoneId长度小于等于1位,或者以“+”或“-”开头的,创建的是ZoneOffset实例
  2. 以“UTC”,“UT”或“GMT”开头的,创建的是ZoneRegion实例
  3. 不符合以上两种的,创建的是ZoneRegion实例
1
2
3
4
5
6
7
8
9
java复制代码	@Test
public void ZoneIdTest8(){

ZoneId zoneId1 = ZoneId.of("+8");
ZoneId zoneId2 = ZoneId.of("+08:00");
ZoneId zoneId3 = ZoneId.of("UT+8");
ZoneId zoneId4 = ZoneId.of("Asia/Shanghai");
System.out.println();
}

image-2021081194068

推荐相关文章

hutool日期时间系列文章

1DateUtil(时间工具类)-当前时间和当前时间戳

2DateUtil(时间工具类)-常用的时间类型Date,DateTime,Calendar和TemporalAccessor(LocalDateTime)转换

3DateUtil(时间工具类)-获取日期的各种内容

4DateUtil(时间工具类)-格式化时间

5DateUtil(时间工具类)-解析被格式化的时间

6DateUtil(时间工具类)-时间偏移量获取

7DateUtil(时间工具类)-日期计算

8ChineseDate(农历日期工具类)

9LocalDateTimeUtil(JDK8+中的{@link LocalDateTime} 工具类封装)

10TemporalAccessorUtil{@link TemporalAccessor} 工具类封装

其他

要探索JDK的核心底层源码,那必须掌握native用法

万字博文教你搞懂java源码的日期和时间相关用法

java的SimpleDateFormat线程不安全出问题了,虚竹教你多种解决方案

源码分析:JDK获取默认时区的风险和最佳实践

参考:

JSR-310:新日期时间API(一)

时区:baike.baidu.com/item/%E6%97…

UTC:baike.baidu.com/item/%E5%8D…

GMT:baike.baidu.com/item/%E4%B8…

CST:baike.baidu.com/item/CST/14…

DST:baike.baidu.com/item/%E5%A4…

ISO-8601:baike.baidu.com/item/ISO%20…

TZUpdater :www.oracle.com/java/techno…

IANA时区数据版本:data.iana.org/time-zones/…

JRE 软件中的时区数据版本:www.oracle.com/java/techno…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

带你学Dubbo---改造dubbo项目

发表于 2021-11-01

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」

改造 dubbo 项目

抽象分散在多个项目中的公共接口,实体类,异常,工具类到一个项目中,在其他项目如服务提供者,消费者共用公共的资源。

实现目标

用户访问电商网站浏览商品—选择商品购买

用户访问电商网站—查看用户信息(收件人地址)

image.png

项目是 web 应用,需要加入 spring web 开发jar:

maven 依赖

image.png

创建公共资源项目

服务提供者,消费者,网站等多个服务中共用,重复使用的类单独定义在一个项目.

A、 创建公共的 maven java project

项目名称:node-shop-interface 接口工程的Maven 坐标

image.png

B、复制之前的 Order 实体类、OrderService 业务接口到 node-shop-interface 项目,

image.png

C、 新建 Address 实体类

image.png

D、 新建 UserInfoService 接口

image.png

E、安装 jar 到 maven 仓库

使用 IDEA 的 maven 窗口执行 install

创建用户信息服务

A、 新建 web project

项目名称:node-shop-userservice

B、 maven pom.xml

image.png

image.png

在< build > 下的 < plugins>标签中加入 JDK1.8 编译插件

image.png

C、 创建 UserInfoServiceImpl 实现类

image.png

D、 dubbo 配置文件

文件名称:userservice-provider.xml

image.png

E、web.xml 注册 spring 监听器

image.png

创建订单服务

A、 新建 web project

项目名称:node-shop-orderservice

B、 maven pom.xml

image.png

image.png

在< build> 下的 < plugins>标签中加入 JDK1.8 编译插件

image.png

C、 创建 OrderService 接口实现类

image.png

D、 dubbo 配置文件

文件名称:dubbo-orderservice-provider.xml

image.png

E、web.xml 注册 spring 监听器

image.png

创建商品网站

F、新建 web project

项目名称:node-shop-web

G、 创建首页 index.jsp

image.png

页面代码:

image.png

image.png

image.png

js 代码

image.png

H、 maven pom.xml

image.png

image.png

在< build> 下的 < plugins>标签中加入 JDK1.8 编译插件

image.png

I、 创建 Spring 配置文件

文件名称:dispatcherServlet.xml

image.png

J、 创建 dubbo 配置文件

文件名称:dubbo-shop-consumer.xml

image.png

K、 web.xml 注册中央调度器 DispatcherServlet

image.png

L、项目结构

image.png

M、 创建商品信息实体类 Goods

image.png

N、 创建接口 ShopService

image.png

O、 创建接口实现类 ShopServiceImpl

image.png

P、 创建类 ShopController

接收来自页面的请求,处理订单,查询地址信息。

image.png

Q、 view-order.jsp

显示订单信息页面:

image.png

R、 view-address.jsp

显示收件人地址信息

页面加入jstl:<%@ taglib uri=”java.sun.com/jsp/jstl/co…“ prefix=”c” %>

使用 jstl 需要加入 jar:

image.png

image.png

image.png

dubbo 常用标签

Dubbo 中常用标签。分为三个类别:公用标签,服务提供者标签,服务消费者标签

公用标签

<dubbo:application/> 和 <dubbo:registry/>

A、配置应用信息

<dubbo:application name=”服务的名称”/>

B、配置注册中心

<dubbo:registry address=”ip:port” protocol=”协议”/>

服务提供者标签

配置暴露的服务

<dubbo:service interface=”服务接口名” ref=”服务实现对象 bean”>

服务消费者

配置服务消费者引用远程服务

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

mybatis-plus 逻辑删除

发表于 2021-11-01

mybatis-plus:逻辑删除

不做真正的删除,在查询时添加一个where条件

例如在某宝上有一条不可描述的购买信息不想被看到,你要删除他,但是从平台的角度考虑是不能随便就彻底的删除掉数据的,那么有没有一个两个都可以满足的解决方式呢,答案是有的——逻辑删除,不做真正意义的删除,在数据库中用一个字段做专门的标记,原本查询的sql语句就变成了这样:

select * from tableName where 原先的查询条件 and 用来标记的字段=设定的用于标记删除的值

而用户的删除行为也变成了update: update tablename set 用来标记的字段=设定的用于标记删除的值 where 用于删除的条件

配置

在application.yml配置文件中配置

1
2
3
4
5
6
yml复制代码mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

修改数据库字段

新增一个deleted字段,注意不能使用delete会报错

修改实体类Car并在该属性上打上@TableLogic注解

1
2
java复制代码@TableLogic
private Integer deleted;

运行测试:

1
2
3
4
5
java复制代码//根据id删除
@Test
void testDeleteById(){
autoMapper.deleteById(25);
}

运行测试方法,deleted字段被更新 以后的查找方法会被加上一个where条件 deleted=0;

从逻辑上实现了数据的删除

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring中的缓存技术

发表于 2021-11-01

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

image.png

可以通过注解或XML的方式配置Spring的缓存,本文仅阐述如何使用注解的方式,笔者认为这是一种更为优雅的方式。

如何配置一个CacheConfig

CacheConfig是用于声明所有缓存的一个统一的配置类,其标志是使用了@EnableCaching注解的Java配置类,如下面的代码所示

1
2
3
4
5
6
7
8
9
10
java复制代码@Configurable
@EnableCaching
public class CacheConfig {

@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}

}

其本质是创建了一个切面,根据注解和缓存的状态操作缓存中的数据。
@Bean方法用于会返回缓存管理器,Spring内置了五种内存管理器,分别为

  • SimpleCacheManager:需要传入自定义的Collection用于缓存对象的管理。
  • NoOpCacheManager:如果缓存中不存在,仅更新缓存但不会将实际结果返回。
  • ConcurrentMapManager:使用ConcurrentMap实现的缓存。
  • EhCacheCacheManager:基于EhCache实现的本地缓存,直接在JVM层面进行的缓存,速度快效率高
  • CompositeCacheManager:可以配置多个缓存管理器,查找缓存条目时遍历查找

CompositeCacheManager的使用方法如下所示,将ConcurrentMapCacheManager和RedisCacheManager包含在一个CacheManager中。

1
2
3
4
5
6
7
8
9
java复制代码@Bean
public CacheManager compositeCacheManager(ConcurrentMapCacheManager concurrentMapCacheManager, RedisCacheManager redisCacheManager) {
CompositeCacheManager compositeCacheManager = new CompositeCacheManager();
List<CacheManager> cacheManagerList = Lists.newArrayList();
cacheManagerList.add(concurrentMapCacheManager);
cacheManagerList.add(redisCacheManager);
compositeCacheManager.setCacheManagers(cacheManagerList);
return compositeCacheManager;
}

除了这五种Sping Data还引入了RedisCacheManager和GemfireCacheManager两个缓存管理器,这里笔者简单阐述一下RedisCacheManager的使用方法。

  1. 创建Redis的连接工厂Bean
  2. 创建RedisTemplate
  3. 创建Redis缓存管理器bean
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
java复制代码@Configurable
@EnableCaching
public class CacheConfig {

@Bean
public JedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.afterPropertiesSet();
return jedisConnectionFactory;
}

@Bean
public RedisTemplate<String, SourceSystemDto> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, SourceSystemDto> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

@Bean
public CacheManager redisCacheManager(RedisTemplate<String, SourceSystemDto> redisTemplate) {
return new RedisCacheManager(redisTemplate);
}

}

为方法或类添加注解以支持缓存

在Spring中启用缓存会创建一个切面,触发一个或更多的Spring的缓存注解。Spring提供了以下四个注解,均可用于方法和类上,如果应用到类上会作用于所有方法。

  • @Cacheable

调用方法前先从缓存中取出,如果不存在则调用方法并将方法的返回值放到缓存中,适用于query方法。

  • @CachePut

调用方法前不会从缓存中取用,但会把返回值放到缓存,适用于save和update操作。

  • @CacheEvict

从缓存中移除对象。

  • @Caching

一个分组的注解,用于指定复杂的缓存规则。

@Cacheable和@CachePut注解支持value、condition、key和unless四个属性

属性 类型 描述
value String[] 指定要使用缓存的名称
condition String SpEL表达式,仅为true的时候才会应用缓存
key String SpEL表达式,用来计算自定义缓存的key
unless String SpEL表达式,如果为true则不会将结果放到缓存中

下面的代码演示使用的方法

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
java复制代码public class EmployeeService {

/**
* 使用employeeCache缓存
* 使用id查找时使用缓存,将返回值的name和email作为key放入缓存中
*/

@Caching (
cacheable = {
@Cacheable(value = "employeeCache", key = "#id")
},
put = {
@CachePut(value = "employeeCacheName", key = "#result.name"),
@CachePut(value = "employeeCacheEmail", key = "#result.email")
}
)
public Employee getEmployee(String id) {
// TODO
}

/**
* 使用employeeCache缓存,自定义缓存为id,当enableCache为true时才会启用缓存
*/
@CachePut(value = "employeeCache", key = "#result.id", condition = "#employee.enableCache")
public Employee save(Employee employee) {

}

@CacheEvict(value = "employeeCache", key = "#id")
public void remove(String id) {

}


}


@Data
class Employee {
private String id;
private String name;
private String email;
private boolean enableCache;
}

在查询的getEmployee()方法上使用了@Caching注解对多个注解进行了分组,该方法在查询时使用了employeeCache缓存,同时在返回时将id-对象、name-对象、email-对象分别放入不同的缓存中。

在新增/修改的saveEmployee()方法上使用了@CachePut注解,将返回值解析为“id-对象”放入对应的缓存中。

在删除的remove()方法上使用了@CacheEvict注解,在方法调用后将对应的缓存条目从缓存中移除。

@CacheEvict注解额外提供了以下属性用于配置删除的策略

属性 类型 描述
allEntries boolean 为true时删除缓存中的所有值
beforeInvocation boolean 为true时在调用前移除缓存条目,为false则在调用后移除(默认)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

AJAX跨域访问问题详解 AJAX 跨域访问 参考资料

发表于 2021-11-01

AJAX 跨域访问

明确跨域问题产生的原因

首先了解一个概念:浏览器的同源策略

浏览器的同源策略

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

同源的定义

如果两个 URL 的 protocol、port (en-US) (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。(“元组” 是指一组项目构成的整体,双重/三重/四重/五重/等的通用形式)。

当我们使用前端通过浏览器对后台接口发起请求的时候,浏览器判断我们的请求的目标接口所在主机和本机的协议、域名(ip)、端口号是否相同,当三者有一个不同时,发生请求跨域错误这就是我们所说的请求跨域问题。

参照如下:

我们以http://origin.test/home为源,源向下列URL发起请求的结果进行对比:

注:源的协议为http,域名为origin.test,端口号为80。

目标URL 结果 原因
http://target.test/home 失败 协议、端口号相同,域名不同,不满足三要素同时成立的条件。域名不同导致浏览器判断其来自不同主机。
http://origin.test:8081/home 失败 协议、域名相同,端口号不同,不满足三要素同时成立的条件。源的端口号为默认端口80。
https://origin.test/home 失败 域名、端口号相同,协议不同,不满足三要素同时成立的条件。源的协议为http,目标URL的协议为https。(在自测接口的时候需要注意此点,有时浏览器会默认为我们加上https)
https://origin.test/index/page.html 成功 满足域名、端口号、协议同时相同。
https://origin.test/home/user.html 成功 满足域名、端口号、协议同时相同。

跨域问题的解决

在JavaEE的开发中,我们通过在Sping的配置类写入如下代码得以避免跨域问题:

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
java复制代码    /**
* 跨域配置
* @return
*/
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
//重写父类提供的跨域请求处理的接口
public void addCorsMappings(CorsRegistry registry) {
//添加映射路径
registry.addMapping("/**")
//放行哪些原始域
.allowedOriginPatterns("*")
//是否发送Cookie信息
.allowCredentials(true)
//放行哪些原始域(请求方式)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
//放行哪些原始域(头部信息)
.allowedHeaders("*")
//暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
.exposedHeaders("Header1", "Header2");
}
};
}

主要是在干一件事:为API所在的服务器设置允许哪些Origin服务器访问自己——.allowedOriginPatterns("*")

项目中遇见的问题

在前后端分离项目中,设置拦截器(Interceptor)实现登录拦截,LoginInterceptor内容如下:

注:拦截策略配置中,拦截的范围是/**。

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
java复制代码@Component
public class LoginInterceptor implements HandlerInterceptor {

@Autowired
private RedisService redisService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

HandlerMethod targetMethod = (HandlerMethod) handler;
if (!targetMethod.hasMethodAnnotation(CheckToken.class)){
return true;
}

String token = TokenUtil.getToken(request);
if (token == null) {
throw new LoginException("未登录,请登录后再操作。");
}

UserInfo userInfo = redisService.getUserByToken(token);
if (userInfo == null) {
redisService.removeToken(token);
throw new LoginException("登录超时,请重新登录。");
}
return true;
}
}

不用看拦截器preHandle方法中代码具体的内容,我们的问题是,在配置了拦截器后,前端对后端的接口发起请求,出现了跨域错误,且相同的请求浏览器发起了2次,两次请求的类型都不同,如下:
请添加图片描述
因为前端发起的是Ajax请求,类型是xhr,但浏览器发起的相同的请求是preflight类型,且点入Request发现其请求方式是OPTIONS,所以猜测其访问的并不是Controller的方法。
请添加图片描述

解决方案

解决此问题的方法是:在拦截器中加上判断handler类型是否不属是HanlderMethod的代码,如果不属于HanlderMethod,说明其不是要访问我们的控制器方法,直接将其放行。

1
2
3
java复制代码if (!(handler instanceof HandlerMethod)) {
return true;
}

修改后的LoginInterceptor拦截器如下:

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
java复制代码@Component
public class LoginInterceptor implements HandlerInterceptor {

@Autowired
private RedisService redisService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

// 如果是预请求,则放行
if (!(handler instanceof HandlerMethod)) {
return true;
}

HandlerMethod targetMethod = (HandlerMethod) handler;
if (!targetMethod.hasMethodAnnotation(CheckToken.class)){
return true;
}

String token = TokenUtil.getToken(request);
if (token == null) {
throw new LoginException("未登录,请登录后再操作。");
}

UserInfo userInfo = redisService.getUserByToken(token);
if (userInfo == null) {
redisService.removeToken(token);
throw new LoginException("登录超时,请重新登录。");
}
return true;
}
}

浏览器判断跨域的具体步骤

君子不器。既然解决了问题,我们就要明白其中的原理,不应拘泥于手段而不思考其背后的目的。

浏览器发起请求会用按照如下顺序顺序进行操作:

  1. 发起请求前,将目标URL的协议、域名、端口号和源依据同源对比策略进行对比(同源对比策略的演示上面已经阐述的很清楚)。
  2. 若同源,则直接发送数据请求,获取响应内容,操作完成。
  3. 若不同源,判断此AJAX请求为一个跨域请求:
  4. 此时浏览器会再发起一个OPTIONS请求,类型是preflight,其他的内容目标请求一样,确认目标URL所在服务器是否允许源所在服务器进行跨域访问,这个OPTIONS请求仅仅是为了确认是否允许跨域,不会真正访问Controller中的方法。
  5. 如果目标URL所在服务器不进行任何响应,则判断目标服务器不允许跨域,出现跨域错误。
  6. 如果目标URL所在服务器响应的内容是不允许当前服务器进行跨域,则判断目标服务器不允许跨域,出现跨域错误。

这就是我们解决方案的为什么要判断handler类型是否不属是HanlderMethod的原因。

参考资料

浏览器的同源策略

彻底理解浏览器的跨域

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

『超级架构师』可能是最好的Redis分布式锁实现

发表于 2021-11-01

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」。PS:已经更文多少天,N就写几。一定要写对文案,否则文章不计入在内。

前言

Hello 大家好,我是l拉不拉米,今天给大家分享redisson实现的多类型锁、支持几乎所有加锁场景的redis分布式锁的实现,还支持小型MQ和redis的各种数据操作,完整源码可以加我私聊。

本文已收录到 Github-java3c ,里面有我的系列文章,欢迎大家Star。

理论部分

在之前的文章Redis分布式锁中,介绍了通过redis实现分布锁的两种方式,分别是:

  1. 通过redis自带的命令:setNX
  2. 通过redis的客户端:redisson

作者更加推荐使用redisson客户端的方式,因为redisson支持更多的锁类型,譬如联锁、红锁、读写锁、公平锁等,而且redisson的实现更加简单,开发者只需要调用响应的API即可,无需关心底层加锁的过程和解锁的原子性问题。

在Redis分布式锁中,列出了redisson对于多种的锁类型的简单实现,即编程式实现。这样的实现完全能够满足我们的日常开发需求,但是缺点也很明显。

譬如:

  • 代码嵌入较多,不够优雅
  • 重复代码
  • 对锁的参数运用不直观
  • 容易忘掉解锁的步骤

使用过Spring的同学,肯定都知道@Transactional注解,Spring即支持编程式事务,也支持注解式(声明式)事务。

我们是否也可以参考这样的实现呢?

答案是:完全OK!

AOP就是专门干这种事的。

实战部分

1、引入redisson依赖

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.2</version>
</dependency>Copy to clipboardErrorCopied

2、自定义注解

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
java复制代码/**
* 分布式锁自定义注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {

/**
* 锁的模式:如果不设置自动模式,当参数只有一个.使用 REENTRANT 参数多个 MULTIPLE
*/
LockModel lockModel() default LockModel.AUTO;

/**
* 如果keys有多个,如果不设置,则使用 联锁
*
* @return
*/
String[] keys() default {};

/**
* key的静态常量:当key的spel的值是LIST、数组时使用+号连接将会被spel认为这个变量是个字符串,只能产生一把锁,达不到我们的目的,
* 而我们如果又需要一个常量的话。这个参数将会在拼接在每个元素的后面
*
* @return
*/
String keyConstant() default "";

/**
* 锁超时时间,默认30000毫秒(可在配置文件全局设置)
*
* @return
*/
long watchDogTimeout() default 30000;

/**
* 等待加锁超时时间,默认10000毫秒 -1 则表示一直等待(可在配置文件全局设置)
*
* @return
*/
long attemptTimeout() default 10000;
}

3、常量类

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码/**
* Redisson常量类
*/
public class RedissonConst {
/**
* redisson锁默认前缀
*/
public static final String REDISSON_LOCK = "redisson:lock:";
/**
* spel表达式占位符
*/
public static final String PLACE_HOLDER = "#";
}

4、枚举

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
java复制代码/**
* 锁的模式
*/
public enum LockModel {
/**
* 可重入锁
*/
REENTRANT,
/**
* 公平锁
*/
FAIR,
/**
* 联锁
*/
MULTIPLE,
/**
* 红锁
*/
RED_LOCK,
/**
* 读锁
*/
READ,
/**
* 写锁
*/
WRITE,
/**
* 自动模式,当参数只有一个使用 REENTRANT 参数多个 RED_LOCK
*/
AUTO
}

5、自定义异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码/**
* 分布式锁异常
*/
public class ReddissonException extends RuntimeException {

public ReddissonException() {
}

public ReddissonException(String message) {
super(message);
}

public ReddissonException(String message, Throwable cause) {
super(message, cause);
}

public ReddissonException(Throwable cause) {
super(cause);
}

public ReddissonException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

6、AOP切面

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
java复制代码   /**
* 分布式锁aop
*/
@Slf4j
@Aspect
public class LockAop {

@Autowired
private RedissonClient redissonClient;

@Autowired
private RedissonProperties redissonProperties;

@Autowired
private LockStrategyFactory lockStrategyFactory;

@Around("@annotation(lock)")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint, Lock lock) throws Throwable {
// 需要加锁的key数组
String[] keys = lock.keys();
if (ArrayUtil.isEmpty(keys)) {
throw new ReddissonException("redisson lock keys不能为空");
}
// 获取方法的参数名
String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) proceedingJoinPoint.getSignature()).getMethod());
Object[] args = proceedingJoinPoint.getArgs();
// 等待锁的超时时间
long attemptTimeout = lock.attemptTimeout();
if (attemptTimeout == 0) {
attemptTimeout = redissonProperties.getAttemptTimeout();
}
// 锁超时时间
long lockWatchdogTimeout = lock.watchdogTimeout();
if (lockWatchdogTimeout == 0) {
lockWatchdogTimeout = redissonProperties.getLockWatchdogTimeout();
}
// 加锁模式
LockModel lockModel = getLockModel(lock, keys);
if (!lockModel.equals(LockModel.MULTIPLE) && !lockModel.equals(LockModel.RED_LOCK) && keys.length > 1) {
throw new ReddissonException("参数有多个,锁模式为->" + lockModel.name() + ",无法匹配加锁");
}
log.info("锁模式->{},等待锁定时间->{}毫秒,锁定最长时间->{}毫秒", lockModel.name(), attemptTimeout, lockWatchdogTimeout);
boolean res = false;
// 策略模式获取redisson锁对象
RLock rLock = lockStrategyFactory.createLock(lockModel, keys, parameterNames, args, lock.keyConstant(), redissonClient);
//执行aop
if (rLock != null) {
try {
if (attemptTimeout == -1) {
res = true;
//一直等待加锁
rLock.lock(lockWatchdogTimeout, TimeUnit.MILLISECONDS);
} else {
res = rLock.tryLock(attemptTimeout, lockWatchdogTimeout, TimeUnit.MILLISECONDS);
}
if (res) {
return proceedingJoinPoint.proceed();
} else {
throw new ReddissonException("获取锁失败");
}
} finally {
if (res) {
rLock.unlock();
}
}
}
throw new ReddissonException("获取锁失败");
}

/**
* 获取加锁模式
*
* @param lock
* @param keys
* @return
*/
private LockModel getLockModel(Lock lock, String[] keys) {
LockModel lockModel = lock.lockModel();
// 自动模式:优先匹配全局配置,再判断用红锁还是可重入锁
if (lockModel.equals(LockModel.AUTO)) {
LockModel globalLockModel = redissonProperties.getLockModel();
if (globalLockModel != null) {
lockModel = globalLockModel;
} else if (keys.length > 1) {
lockModel = LockModel.RED_LOCK;
} else {
lockModel = LockModel.REENTRANT;
}
}
return lockModel;
}
}

这里使用了策略模式来对不同的锁类型提供实现。

7、锁策略的实现

先定义锁策略的抽象基类(也可以用接口):

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
java复制代码/**
* 锁策略抽象基类
*/
@Slf4j
abstract class LockStrategy {

@Autowired
private RedissonClient redissonClient;

/**
* 创建RLock
*
* @param keys
* @param parameterNames
* @param args
* @param keyConstant
* @return
*/
abstract RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient);

/**
* 获取RLock
*
* @param keys
* @param parameterNames
* @param args
* @param keyConstant
* @return
*/
public RLock[] getRLocks(String[] keys, String[] parameterNames, Object[] args, String keyConstant) {
List<RLock> rLocks = new ArrayList<>();
for (String key : keys) {
List<String> valueBySpel = getValueBySpel(key, parameterNames, args, keyConstant);
for (String s : valueBySpel) {
rLocks.add(redissonClient.getLock(s));
}
}
RLock[] locks = new RLock[rLocks.size()];
int index = 0;
for (RLock r : rLocks) {
locks[index++] = r;
}
return locks;
}

/**
* 通过spring Spel 获取参数
*
* @param key 定义的key值 以#开头 例如:#user
* @param parameterNames 形参
* @param args 形参值
* @param keyConstant key的常亮
* @return
*/
List<String> getValueBySpel(String key, String[] parameterNames, Object[] args, String keyConstant) {
List<String> keys = new ArrayList<>();
if (!key.contains(PLACE_HOLDER)) {
String s = REDISSON_LOCK + key + keyConstant;
log.info("没有使用spel表达式value->{}", s);
keys.add(s);
return keys;
}
// spel解析器
ExpressionParser parser = new SpelExpressionParser();
// spel上下文
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
Expression expression = parser.parseExpression(key);
Object value = expression.getValue(context);
if (value != null) {
if (value instanceof List) {
List valueList = (List) value;
for (Object o : valueList) {
keys.add(REDISSON_LOCK + o.toString() + keyConstant);
}
} else if (value.getClass().isArray()) {
Object[] objects = (Object[]) value;
for (Object o : objects) {
keys.add(REDISSON_LOCK + o.toString() + keyConstant);
}
} else {
keys.add(REDISSON_LOCK + value.toString() + keyConstant);
}
}
log.info("spel表达式key={},value={}", key, keys);
return keys;
}
}

再提供各种锁模式的具体实现:

  • 可重入锁:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码/**
* 可重入锁策略
*/
public class ReentrantLockStrategy extends LockStrategy {

@Override
public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
List<String> valueBySpel = getValueBySpel(keys[0], parameterNames, args, keyConstant);
//如果spel表达式是数组或者集合 则使用红锁
if (valueBySpel.size() == 1) {
return redissonClient.getLock(valueBySpel.get(0));
} else {
RLock[] locks = new RLock[valueBySpel.size()];
int index = 0;
for (String s : valueBySpel) {
locks[index++] = redissonClient.getLock(s);
}
return new RedissonRedLock(locks);
}
}
}
  • 公平锁:
1
2
3
4
5
6
7
8
9
10
java复制代码/**
* 公平锁策略
*/
public class FairLockStrategy extends LockStrategy {

@Override
public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
return redissonClient.getFairLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0));
}
}
  • 联锁
1
2
3
4
5
6
7
8
9
10
11
java复制代码/**
* 联锁策略
*/
public class MultipleLockStrategy extends LockStrategy {

@Override
public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
RLock[] locks = getRLocks(keys, parameterNames, args, keyConstant);
return new RedissonMultiLock(locks);
}
}
  • 红锁
1
2
3
4
5
6
7
8
9
10
11
java复制代码/**
* 红锁策略
*/
public class RedLockStrategy extends LockStrategy {

@Override
public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
RLock[] locks = getRLocks(keys, parameterNames, args, keyConstant);
return new RedissonRedLock(locks);
}
}
  • 读锁
1
2
3
4
5
6
7
8
9
10
11
java复制代码/**
* 读锁策略
*/
public class ReadLockStrategy extends LockStrategy {

@Override
public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
RReadWriteLock rwLock = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0));
return rwLock.readLock();
}
}
  • 写锁
1
2
3
4
5
6
7
8
9
10
11
java复制代码/**
* 写锁策略
*/
public class WriteLockStrategy extends LockStrategy {

@Override
public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
RReadWriteLock rwLock = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0));
return rwLock.writeLock();
}
}

最后提供一个策略工厂初始化锁策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码/**
* 锁的策略工厂
*/
@Service
public class LockStrategyFactory {

private LockStrategyFactory() {
}

private static final Map<LockModel, LockStrategy> STRATEGIES = new HashMap<>(6);

static {
STRATEGIES.put(LockModel.FAIR, new FairLockStrategy());
STRATEGIES.put(LockModel.REENTRANT, new ReentrantLockStrategy());
STRATEGIES.put(LockModel.RED_LOCK, new RedLockStrategy());
STRATEGIES.put(LockModel.READ, new ReadLockStrategy());
STRATEGIES.put(LockModel.WRITE, new WriteLockStrategy());
STRATEGIES.put(LockModel.MULTIPLE, new MultipleLockStrategy());
}

public RLock createLock(LockModel lockModel, String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
return STRATEGIES.get(lockModel).createLock(keys, parameterNames, args, keyConstant, redissonClient);
}
}

8、使用方式

1
2
3
4
5
6
java复制代码    @Lock(keys = "#query.channel") // 支持spel
@ApiOperation("分页列表")
@GetMapping
public ApiPageResult list(VendorProjectItemQuery query, Pagination pagination) {
return ApiPageResult.success(pagination, vendorProjectItemService.list(query, pagination), vendorProjectItemService.count(query));
}

大功告成,顺利吃鸡hatching_chick)hatched_chick)baby_chick

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

漫画 媳妇,去帮我放水泡个澡~

发表于 2021-11-01

这是**苏南**的 第 32 篇 原创漫画推送

其实,人到中年,不管男人还是女人

肩上都担负很多的责任和义务

自己的能力却时常匹配不上 于是心生各种焦虑

因此,我们要学会与自己和解

调整好自己的情绪、心态

学会取舍

才能面对一个又一个不期而至的挑战.

本期漫画 情节纯属虚构 如有雷同,纯属巧合.

编后

网上曾有个段子调侃说:“不要大声责骂年轻人,他们会立刻辞职的,但是你可以往死里骂那些中年人,尤其是有车有房有娃的那些”。这话虽然难听,但是回归到现实中的职场,却是大部分人的真实写照。

不过说到压力,年轻人的压力真的比中年人的压力小吗?

其实答案是否定的

但是他们唯一的区别就是,年轻人可以选择逃避,但是中年人不可以逃避,必须要直面。

中年人不单没钱,担子导致他说话的底气都少了一些,老子不干了这种话一般都是年轻人说的,一人吃饱全家不饿,当然可以随时炒老板.

不过也不能拿年轻当本钱,年轻的时候抓紧时间学习的那份努力,才是本钱.

因为现在的你还很年轻,是大学生或职场新人,但是一不留神,你马上就变成那个被各种压力压到不能动弹的中年人了.

都说成年人的世界里,没有“容易”二字,人到中年更是如此,家庭、工作两边要尽力去做好,哪边出了岔子都不行,生活早已不由你自己掌控….

张爱玲说:人到中年,一睁眼周围都是靠自己的人,浑身每一个零件都不是自己的,那是用来换房子,换父母医疗费,换儿女前程的。唯独没有自己的!—— 摘自 《半生缘》

莫里老人曾说:“人生最重要的是学会如何施爱于人,并去接受爱.” —— 摘自 《相约星期二》

中年,是每个人的必经之路,我们无须逃避,也无须过度迷茫纱、焦虑,应该欣然接受,人到中年,担子重了,也代表着你在乎、亦在乎你的人也多了. 你给于他们关爱的同时,也在接受他们回馈你的爱.

仅以此篇,致敬那些在迷雾中继续坚定不已前行的中年人( 包括自己 ),路虽远,行则将至。事虽难,做则必成。我们只管向前走,也许走着走着花就开了。也许在未来的某天,回首曾经的中年,我们会感慨道:“看,那曾经可爱的,手忙脚乱,一地鸡毛的中年”.

参考资料

文中部分素材来源网络,如有侵权,请联系删除

声明:本公众号原创漫画内容均为架空构思,人物和故事情节均为虚构推演,不针对现实世界中的任何人和事,请勿对号入座

本文系 “画漫画的程序员 ” 原创

转载请先微信联系苏南 (su-south)授权,并标明出处!

撰稿:马克杨

插画 / 排版:苏南、表面

更多精彩技术漫画,尽在画漫画的程序员

  • 漫画 | 悄悄努力,偷偷拔尖,一举惊艳所有人!
  • 漫画 | 如果面试时大家都说真话…
  • 漫画 | 老板,医生说我胃不好!!
  • 漫画 | 为什么月薪过万的互联网人脱单这么难?
  • 漫画 | 老板,我想申请加薪~
  • 漫画 | 害,曾经我也是一个棱角分明的人啊…
  • 漫画 | 辞职前与老板的最后一次谈话有哪些禁忌?
  • 漫画 | 没有一个程序员能拒绝这样的女朋友
  • 漫画 | 一个NB互联网项目的上线过程…
  • 漫画 | 为什么祖传代码被称为「屎山」?
  • 漫画 | 如何委婉且优雅的提出离职,并喜提N+1~
  • 漫画 | Java语言的诞生!
  • 漫画 | 从四大名著之一看产品经理和程序员的关系!
  • 漫画 | 公司测试因提Bug不规范,锒铛入狱~
  • 程序员联名把产品经理告上县衙,并列了8大罪状(下)
  • 漫画 | 产品经理的八大罪状(上)
  • 漫画 | 北上广深打工人月薪五万回老家的“注意事项”
  • 漫画 | 我穿越回十年前,暴打了自己~
  • 漫画 | 阿姨,我不想努力了~
  • 漫画 | 卧槽,我把面试官整崩溃了~
  • 漫画 | P7砖家:对不起,我要跑路了
  • 漫画 | 产品经理频繁更改需求,我没忍住把他给砍了!
  • 漫画 | 人到中年,一地鸡毛
  • 漫画 | 带你领略前端发展史的江湖恩怨情仇

更多精彩,欢迎关注我们 -> 画漫画的程序员

本文首发于公众号:画漫画的程序员

链接:mp.weixin.qq.com/s/aYYhYXC60…

转载请联系微信:su-south 授权

用漫画解读前端技术,执笔演绎程序人生,愿吾手中笔,能博君(卿)一笑
Github地址:更多有趣的程序员漫画github.com/meibin08/co…欢迎 Star、watch

苏南的 第 **26** 篇 原创漫画推送
创作不易,觉得不错,请点个「 赞 」给我点动力,感谢~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

低代码报表,JimuReport积木报表 v140版本发

发表于 2021-11-01
项目介绍

积木报表,一款免费的可视化Web报表工具,像搭建积木一样在线拖拽设计!功能涵盖,数据报表、打印设计、图表报表、大屏设计等!
秉承“简单、易用、专业”的产品理念,极大的降低报表开发难度、缩短开发周期、节省成本、解决各类报表难题,完全免费的!

当前版本:v1.4.0 | 2021-11-01

集成依赖
1
2
3
4
5
xml复制代码<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>
#升级日志
重点新功能
  • 支持分组合计计数统计
  • 支持特殊字符分组
  • 支持表达式compute计算
  • js增强支持设置下拉框默认值
  • 时间默认值支持计算yyyy-MM格式
  • 字典支持系统变量设置
  • 支持表达式数据集小写
  • 升级minidao,支持自定义数据源
  • 预览导出百分比统一
  • 导出excel斜线表头支持颜色值为英文
  • 导出excel默认样式加上边框
  • 修复横向有3级分组时模版计算的bug
  • 修复Quickstart版本,图表默认乱码问题
  • 修复行号函数#{t_index+1} 导出excel报错
  • 修复数据量大时打印浏览器崩溃问题
  • 修复数值计算问题double显示E,int求和展示成负数
  • 修复分页导出excel问题
Issues处理
  • 使用Quickstart版本,图表默认乱码问题issues/#584
  • 数据带有括号时出错issues/#491
  • jimuReport由特殊字符导致组合分组失效问题issues/#518
  • groupRight分组,无数据时显示的字段名issues/I4BNXB
  • 纵向小计结果显示有问题issues/I4D9U8
  • 横向分组支持特殊字符issues/I48Y2U
  • compute() 计算失效issues/#535
  • 升级1.4.0-beta后对mysql json处理不支持了issues/#582
  • 升级到1.4.0版本sum合计还是不行issues/#581
  • 横向分组表头超过三层时,数据显示为空白issues/#562
  • 百分比数据导出有问题,多除以了100 issues/I4EZPQ
  • 导出报表配置sql问题issues/I4DB4L
  • 条件下拉框默认值可否默认第一条issues/I4DJXF
  • 导出excel,数据为空时报错issues/#587
  • 导出excel错误issues/#588
  • 使用行号函数#{t_index+1} 导出excel报错issues/I4DYT4
  • 小数位数设置为0,导出excel显示为两位小数issues/I4E9M4
  • 导出excel,数据为空时报错issues/I4DIFR
  • 主数据源为Oracle数据字典点击查询,报语法错误issues/I4DCXA
#代码下载
  • github.com/zhangdaisco…
  • gitee.com/jeecg/JimuR…
#技术文档
  • 体验官网: jimureport.com
  • 快速集成文档 :report.jeecg.com/2078875
  • 技术文档: report.jeecg.com
  • QQ群:212391162

为什么选择 JimuReport?

永久免费,支持各种复杂报表,并且傻瓜式在线设计,非常的智能,低代码时代,这个是你的首选!

  • 采用SpringBoot的脚手架项目,都可以快速集成
  • Web 版设计器,类似于excel操作风格,通过拖拽完成报表设计
  • 通过SQL、API等方式,将数据源与模板绑定。同时支持表达式,自动计算合计等功能,使计算工作量大大降低
  • 开发效率很高,傻瓜式在线报表设计,一分钟设计一个报表,又简单又强大
  • 支持 ECharts,目前支持28种图表,在线拖拽设计,支持SQL和API两种数据源
  • 支持分组、交叉,合计、表达式等复杂报表
  • 支持打印设计(支持套打、背景打印等)可设置打印边距、方向、页眉页脚等参数 一键快速打印 同时可实现发票套打,不动产证等精准、无缝打印
  • 大屏设计器支持几十种图表样式,可自由拼接、组合,设计炫酷大屏
  • 可设计各种类型的单据、大屏,如出入库单、销售单、财务报表、合同、监控大屏、旅游数据大屏等
#系统截图
  • 报表设计器(专业一流 数据可视化,解决各类报表难题)
  • 报表设计器(完全在线设计,简单易用)

  • 打印设计(支持套打、背景打印)

  • 数据报表(支持分组、交叉,合计等复杂报表)

  • 图形报表(目前支持28种图表)
  • 数据报表斑马线

#功能清单
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
css复制代码├─报表设计器
│ ├─数据源
│ │ ├─支持多种数据源,如Oracle,MySQL,SQLServer,PostgreSQL等主流的数据库
│ │ ├─支持SQL编写页面智能化,可以看到数据源下面的表清单和字段清单
│ │ ├─支持参数
│ │ ├─支持单数据源和多数数据源设置
│ │ ├─支持Nosql数据源Redis,MongoDB
│ │ ├─支持存储过程
│ ├─单元格格式
│ │ ├─边框
│ │ ├─字体大小
│ │ ├─字体颜色
│ │ ├─背景色
│ │ ├─字体加粗
│ │ ├─支持水平和垂直的分散对齐
│ │ ├─支持文字自动换行设置
│ │ ├─图片设置为图片背景
│ │ ├─支持无线行和无限列
│ │ ├─支持设计器内冻结窗口
│ │ ├─支持对单元格内容或格式的复制、粘贴和删除等功能
│ │ ├─等等
│ ├─报表元素
│ │ ├─文本类型:直接写文本;支持数值类型的文本设置小数位数
│ │ ├─图片类型:支持上传一张图表;支持图片动态生成
│ │ ├─图表类型
│ │ ├─函数类型
│ │ └─支持求和
│ │ └─平均值
│ │ └─最大值
│ │ └─最小值
│ ├─背景
│ │ ├─背景颜色设置
│ │ ├─背景图片设置
│ │ ├─背景透明度设置
│ │ ├─背景大小设置
│ ├─数据字典
│ ├─报表打印
│ │ ├─自定义打印
│ │ └─医药笺、逮捕令、介绍信等自定义样式设计打印
│ │ ├─简单数据打印
│ │ └─出入库单、销售表打印
│ │ └─带参数打印
│ │ └─分页打印
│ │ ├─套打
│ │ └─不动产证书打印
│ │ └─发票打印
│ ├─数据报表
│ │ ├─分组数据报表
│ │ └─横向数据分组
│ │ └─纵向数据分组
│ │ └─多级循环表头分组
│ │ └─横向分组小计
│ │ └─纵向分组小计
│ │ └─分版
│ │ └─分栏
│ │ └─动态合并格
│ │ └─自定义分页条数
│ │ └─合计
│ │ ├─交叉报表
│ │ ├─明细表
│ │ ├─带条件查询报表
│ │ ├─表达式报表
│ │ ├─带二维码/条形码报表
│ │ ├─多表头复杂报表
│ │ ├─主子报表
│ │ ├─预警报表
│ │ ├─数据钻取报表
│ ├─图形报表
│ │ ├─柱形图
│ │ ├─堆叠柱形图
│ │ ├─折线图
│ │ ├─饼图
│ │ ├─动态轮播图
│ │ ├─折柱图
│ │ ├─散点图
│ │ ├─漏斗图
│ │ ├─雷达图
│ │ ├─象形图
│ │ ├─地图
│ │ ├─仪盘表
│ │ ├─关系图
│ │ ├─图表背景
│ │ ├─图表动态刷新
│ │ ├─图表数据字典
│ ├─参数
│ │ ├─参数配置
│ │ ├─参数管理
│ ├─导入导出
│ │ ├─支持导入Excel
│ │ ├─支持导出Excel、pdf;支持导出excel、pdf带参数
│ ├─打印设置
│ │ ├─打印区域设置
│ │ ├─打印机设置
│ │ ├─预览
│ │ ├─打印页码设置
├─大屏设计器
│ ├─系统功能
│ │ ├─静态数据源和动态数据源设置
│ │ ├─基础功能
│ │ └─支持拖拽设计
│ │ └─支持增、删、改、查大屏
│ │ └─支持复制大屏数据和样式
│ │ └─支持大屏预览、分享
│ │ └─支持系统自动保存数据,同时支持手动恢复数据
│ │ └─支持设置大屏密码
│ │ └─支持对组件图层的删除、组合、上移、下移、置顶、置底等
│ │ ├─背景设置
│ │ └─大屏的宽度和高度设置
│ │ └─大屏简介设置
│ │ └─背景颜色、背景图片设置
│ │ └─封面图设置
│ │ └─缩放比例设置
│ │ └─环境地址设置
│ │ └─水印设置
│ │ ├─地图设置
│ │ └─添加地图
│ │ └─地图数据隔离
│ ├─图表
│ │ ├─柱形图
│ │ ├─折线图
│ │ ├─折柱图
│ │ ├─饼图
│ │ ├─象形图
│ │ ├─雷达图
│ │ ├─散点图
│ │ ├─漏斗图
│ │ ├─文本框
│ │ ├─跑马灯
│ │ ├─超链接
│ │ ├─实时时间
│ │ ├─地图
│ │ ├─全国物流地图
│ │ ├─地理坐标地图
│ │ ├─城市派件地图
│ │ ├─图片
│ │ ├─图片框
│ │ ├─轮播图
│ │ ├─滑动组件
│ │ ├─iframe
│ │ ├─video
│ │ ├─翻牌器
│ │ ├─环形图
│ │ ├─进度条
│ │ ├─仪盘表
│ │ ├─字浮云
│ │ ├─表格
│ │ ├─选项卡
│ │ ├─万能组件
└─其他模块
└─更多功能开发中。。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…443444445…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%