0%

第一章

使用stream流形式递归

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
@GetMapping("/getUser")
public HttpResponses<List<User>> getUser(){
long startTime=System.currentTimeMillis(); //获取开始时间
List<User> list = this.userService.query().eq(User::getStatus,StatusEnum.NORMAL)
.eq(User::getDirty,StatusEnum.NORMAL).list();
//用map写法
/*List<User> listUser = list.stream().filter(user -> user.getLevelId().equals("0")).map(user -> {
user.setChildList(getChildrens(user,list));
return user;
}).collect(Collectors.toList());*/
//用peek写法
List<User> listUser = list.stream().filter(user -> user.getLevelId().equals("0"))
.peek(user -> user.setChildList(getChildrens(user,list))).collect(Collectors.toList());
long endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(endTime-startTime)+"ms");
return success(listUser);
}
获取子类数据
public List<User> getChildrens(User user,List<User> userList){
List<User> childrens = userList.stream().filter(u -> Objects.equals(u.getLevelId(),user.getId())).map(
u -> {
u.setChildList(getChildrens(u,userList));
return u;
}
).collect(Collectors.toList());
return childrens;
}

前端nginx部署

前端部署确认服务器是否有nginx 如果没有,安装一个,建立离线下载,因为生产服务器都是无网或者内网,所以只能离线下载,具体下载可以参考https://blog.csdn.net/weixin_36754290/article/details/126541006

将前端包通过跳板机上传到指定目录,建议放在home目录下的项目名包下

img

这里我就放在这个包下

前端包如果是压缩包的形式需要先进行解压 tar 包解压命令

1
tar -zxvf 文件名

zip 解压命令

1
unzip 文件名

进入nginx目录 一般放在usr/local目录下

进入conf 目录下

这里时配置前端的地方

可以再nginx.conf编辑 也可以新建一个.conf文档

这里我选择新建一个,方便后期快速定位

1
vi xxx.conf

把这个配置文件复制进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {

listen 80;
server_name localhost;
root /home/xxx/dist;//前端包的绝对路径
index index.html index.htm;
#处理vue-router路径Start
#如果找不到路径则跳转到@router变量中寻找,找到了就默认进入index.html
location / {
try_files $uri $uri/ /index.html last;
index index.html index.htm;
}
#处理访问时不能访问到接口的问题
location /sdb_platform{ // 配置接口地址
#后端接口地址
proxy_pass http://后端服务器id:端口;
add_header Content-Type "text/plain;charset=utf-8";
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST';
}
}

确保内容无丢失,然后保存退出

重启nginx

1
cd  /usr/local/nginx/sbin
1
./nginx -s reload  //如果出现无法启动说明你没有启动nginx 你需要启动一下 每次改完nginx的conf文件都需要重新启动   启动命令 ./nginx

然后去输入你配置的地址看是否可以访问

nginx实现防盗链

使用Nginx服务器的Rewrite功能实现防盗链。

Nginx中有一个指令 valid_referers. 该指令可以用来获取 Referer 头域中的值,并且根据该值的情况给 Nginx全局变量 invalidreferer赋值。如果Referer头域中没有符合validreferers指令的值的话,invalidreferer赋值。如果Referer头域中没有符合validreferers指令的值的话,invalid_referer变量将会赋值为

valid_referers 指令基本语法如下:

1
valid_referers none | blocked | server_names | string

none: 检测Referer头域不存在的情况。

blocked: 检测Referer头域的值被防火墙或者代理服务器删除或伪装的情况。那么在这种情况下,该头域的值不以”http://“ 或 “https://” 开头。

server_names: 设置一个或多个URL,检测Referer头域的值是否是URL中的某个。

因此我们有了 valid_referers指令和$invalid_referer变量的话,我们就可以通过 Rewrite功能来实现防盗链。

下面我们介绍两种方案:第一:根据请求资源的类型。第二:根据请求目录

1. 根据请求文件类型实现防盗链配置实列如下:

配置如下

1
2
3
4
5
6
7
8
9
10
11
server {
listen 8080;
server_name xxx.abc.com
location ~* ^.+\.(gif|jpg|png|swf|flv|rar|zip)$ {
valid_referers none blocked www.xxx.com www.yyy.com *.baidu.com *.tabobao.com;
if ($invalid_referer) {
rewrite ^/ http://www.xxx.com/images/forbidden.png;
}
}
}

2. 根据请求目录实现防盗链的配置实列如下:
1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 8080;
server_name xxx.abc.com
location /file/ {
root /server/file/;
valid_referers none blocked www.xxx.com www.yyy.com *.baidu.com *.tabobao.com;
if ($invalid_referer) {
rewrite ^/ http://www.xxx.com/images/forbidden.png;
}
}
}

离线安装docker

首先,linux安装docker的环境,需要centos的版本最好达到7及以上

首先通过命令查看操作系统版本

在 CentOS 7安装docker要求系统为64位、系统内核版本为 3.10 以上

1
2
lsb_release -a
uname -r

一般情况下,我是使用离线安装模式,因为到时实际生产环境中,使用的网络可能是局域网,并不能直接访问互联网链接,下载 docker-18.03.1-ce.tgz 版本

下载地址:https://download.docker.com/linux/static/stable/x86_64/

1
2
3
4
5
解压命令(这里可以下载你实际使用到的版本)
上传到geelyapp目录下/geelyapp
tar -zxvf docker-18.03.1-ce.tgz
解压之后的文件复制到 /usr/bin/ 目录下
cp docker/* /usr/bin/

在/etc/systemd/system/目录下新增docker.service文件

1
vim  /etc/systemd/system/docker.service

注意:其中的ExecStart=/usr/bin/dockerd –selinux-enabled=false –insecure-registry=127.0.0.1 。有的可能会要求粘贴上面的代码,–insecure-registry=127.0.0.1表示的是加入允许不安全链接,需要去掉

(注意此处启动之后没有问题)复制完之后,检查一下是否完整

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
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target

[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd --selinux-enabled=false
ExecReload=/bin/kill -s HUP $MAINPID
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
# Uncomment TasksMax if your systemd version supports it.
# Only systemd 226 and above support this version.
#TasksMax=infinity
TimeoutStartSec=0
# set delegate yes so that systemd does not reset the cgroups of docker containers
Delegate=yes
# kill only the docker process, not all processes in the cgroup
KillMode=process
# restart the docker process if it exits prematurely
Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s

[Install]
WantedBy=multi-user.target

启动docker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#给docker.service文件添加执行权限
chmod +x /etc/systemd/system/docker.service

#重新加载配置文件(每次有修改docker.service文件时都要重新加载下)
systemctl daemon-reload

#启动docker
systemctl start docker

# 设置开机启动
systemctl enable docker.service

#查看docker服务状态
systemctl status docker

influxdb时区异常解决

我们使用influxdb时候,可能会遇到一些特殊报错,比如时期报错如下图

img

对于这种报错,是因为安装influxdb服务器上对于时区的规则没有定义,我们只需要下载一个插件就可完美解决

tzdata

命令如下

1
yum install tzdata

下载以后输入y

就可解决时区报错

记得要重启项目,重新加载influxdb的配置

彩云天气接入实时天气

需要去官网申请token和付费限制

1
https://api.caiyunapp.com/v2.6/token/经纬度/daily?dailysteps=3

然后调用解析返回的json文件

websocket搭建

引入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
 <dependencies>
<!-- WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- </plugin>-->
<!-- </plugins>-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.1</version>
<configuration>
<mainClass>com.star.qiaosi.StartQiaosiWebsocketApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

编写配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
// 在此处设置bufferSize
Integer size = 512 * 1024 ;
container.setMaxTextMessageBufferSize(size);
container.setMaxBinaryMessageBufferSize(size);
container.setMaxSessionIdleTimeout(15 * 60000L);
return container;
}

}

编写服务类 这个我加了websocket的心跳机制 如果业务上不要可以不加 需要前端配合定时发送心跳信息

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
package com.star.qiaosi.server;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

/**
* @author Weilong.Cheng
* @description: webSocket 服务端
* @date 2023/5/11 16:00
*/
@Component
@ServerEndpoint("/star-qiaosi/websocket")
@Slf4j
public class WebSocketServer implements WebSocketMessageBrokerConfigurer {

private Session session;
private static final String HEART_REQUEST="ping";
private static final String HEART_RESPONSE="pong";

/* 存放websocket的集合 */
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();

/* 存放session */
//public static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<String, Session>();

public static CopyOnWriteArrayList<Session> sessionList = new CopyOnWriteArrayList();

/*
*默认情况下,Spring Boot 对 WebSockets 不设置数据上限。
* WebSockets 的数据上限通常取决于服务器和客户端的配置以及网络带宽
* websocket设置数据上线实例 50MB
* 需要本类实现websocket的配置类 implements WebSocketMessageBrokerConfigurer
* */
// @Override
// public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
// registration.setMessageSizeLimit(5 * 1024 * 1024);
// }

@OnOpen
public void onOpen(Session session) {
this.session = session;
sessionList.add(session);
log.info("websocket连接成功!用户:{},当前共 {} 位用户在线", session.getId(), sessionList.size());
// if (!StringUtils.isBlank(userId)) {
// sessionMap.put(userId, session);
// webSocketSet.add(this);
// log.info("websocket连接成功!用户id:{}", userId);
// }
}

@OnClose
public void onClose() {
//log.info("{}web", this.session.getBasicRemote());
webSocketSet.remove(this); //从set中删除
sessionList.remove(this.session); //从List中删除
log.info("用户:{} 登出,当前共 {} 位用户在线", this.session.getId(), sessionList.size());
}

@OnMessage
public void onWebSocketText(String message) {
if (HEART_REQUEST.equals(message)) {
try {
session.getBasicRemote().sendText(HEART_RESPONSE);
log.info("Sent heartbeat response");
} catch (IOException e) {
e.printStackTrace();
}
} else {
for (Session session : sessionList) {
sendMessage(session, message);
}
log.info("Sent heartbeat response." + message);
}
}

/**
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误:{}", error.getMessage());
}

// public void sendMessage(String uuid, String message) {
// log.info("指定客户端发消息:{} MSG:", uuid, message);
// Session session = sessionMap.get(uuid);
// if (session != null) {
// try {
// session.getBasicRemote().sendText(message);
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }

public synchronized void sendMessage(Session session, String msg) {
try {
session.getBasicRemote().sendText(msg);
//session.getAsyncRemote().sendText(msg);
} catch (Exception e) {
e.printStackTrace();
}
}

}

stream 流使用

map方法

map方法用于根据自定义的规则对stream流中的数据做一对一的映射

1
2
3
//获取所有员工的姓名
List<String> enames = employees.stream().map(employee -> employee.getEname()).collect(Collectors.toList());
enames.stream().forEach(System.out::println);

mapToInt/mapToLong/mapToDouble方法

这几个方法主要用来对stream流中的元素产生单个的统计结果

1
2
3
 //获取所有员工的薪水总和
int totalSalary = employees.stream().mapToInt(employee -> employee.getSalary()).sum();
System.out.println("薪水总和:" + totalSalary);

执行结果:

1
薪水总和:7200

filter方法

filter方法用于根据设置的条件对stream流中的数据做过滤操作

1
2
3
//获取薪水超过1500的员工
List<Employee> filterEmp = employees.stream().filter(employee -> employee.getSalary()>1500).collect(Collectors.toList());
filterEmp.stream().forEach(System.out::println);

执行结果:

1
2
Employee{empno=7499, ename='ALLEN', salary=1600, deptno=30}
Employee{empno=7782, ename='CLARK', salary=2450, deptno=10}

sorted方法

sorted方法用于对流中的元素进行排序

1
2
3
//按员工的薪水由低到高排序
List<Employee> sortedEmp = employees.stream().sorted(Comparator.comparing(Employee::getSalary)).collect(Collectors.toList());
sortedEmp.stream().forEach(System.out::println);

执行结果

1
2
3
4
5
Employee{empno=7369, ename='SMITH', salary=800, deptno=20}
Employee{empno=7876, ename='ADAMS', salary=1100, deptno=20}
Employee{empno=7521, ename='WARD', salary=1250, deptno=30}
Employee{empno=7499, ename='ALLEN', salary=1600, deptno=30}
Employee{empno=7782, ename='CLARK', salary=2450, deptno=10}

Collectors类

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串

1
2
3
4
5
6
7
8
9
10
11
//按员工所属部门号进行分类
Map<Integer, List<Employee>> map = employees.stream().collect(Collectors.groupingBy(employee -> employee.getDeptno()));
for(Map.Entry<Integer, List<Employee>> entry : map.entrySet()) {
System.out.println("key: " + entry.getKey() + " value:" + entry.getValue());
}

System.out.println();

//获取员工姓名,用","进行拼接
String enameString = employees.stream().map(employee -> employee.getEname()).collect(Collectors.joining(","));
System.out.println(enameString);

Mysql的坑

 在MySQL 5.6及以后的版本上,优化器在遇到order by limit语句的时候,做了一个优化,即使用了priority queue。使用 priority queue 的目的,是在不能使用索引有序性的时候,如果要排序,并且使用了limitn,那么只需要在排序的过程中,保留n条记录即可,这样虽然不能解决所有记录都需要排序的开销,但是只需要 sort buffer少量的内存就可以完成排序。

  之所以5.6出现了第二页数据重复的问题,是因为 priority queue使用了堆排序的排序方法,而堆排序是一个不稳定的排序方法,也就是相同的值可能排序出来的结果和读出来的数据顺序不一致。5.5 没有这个优化,所以也就不会出现这个问题。

  快速排序和堆排序是不稳定的排序算法,也就是对于重复值是不能保证顺序的。而直接利用索引的话其返回数据是稳定的,因为索引的B+树叶子结点的顺序是唯一且一定的,快速排序适合大数据量排序,堆排序在少量排序上有优势。

解决方式两种

1 ,如果你们公司业务允许这样的情况,因为数据是在的,只不过是展示的时候多了一条,那就不处理
2,可以通过联合条件,比如通过时间和id去升序降序查

Mysql可以通过过滤字段查数据

首先有个这样的需求,根据解决人id去查数据,另外你们公司的数据库是这种扯淡设计

1
2
3
4
5
事件id   事件名称   解决人id
1 xxx 1,2,3,4
2 xxx 1,3,4
3 xxx 1,2,4
4 xxx 3

这种情况下我要你根据解决人去查数据 比如 前端传过来的是2 这样就需要得出事件id是1,3的数据

我们首先想的是不是就是得出所有数据,通过split(”,”),然后去equals 过滤掉不符合的数据

这种方法也可以做,但是不太优雅,我们需要进行很多if 不符合开发规范

其实mysql有自带的这种情况的函数

1
find_in_set()

这时候我们的mysql语句就可以写成

1
SELECT * FROM `表名` where FIND_IN_SET(49,解决人id)

这样就可以解决这种问题

minio服务器搭建

安全漏洞解决办法

1,sql 注入
通过#{}解决传参
2,不安全的http方法

1
2
3
if ($request_method !~* GET|POST|HEAD) {
return 403;
}

3,xss
不能输入js代码
4,csrf

5,垂直越权
6,水平越权
7,任意文件上传
····1.文件大小的合理限制 (通用5M大小限制,具体可根据业务需求设置)
····2.白名单检查文件扩展名
····3.确保文件名不包含任何可能被解释为目录或遍历序列 ( ../) 的子字符串
····4.重命名上传的文件以避免可能导致现有文件被覆盖的冲突
····5.隐藏上传文件路径
····6.病毒扫描 (影响性能,根据客户需要设置)
····7.上传文件的存储目录禁用执行权限
····8.在完全验证之前不要将文件上传到服务器的永久文件系统.
····9.内容检测要求:
A. 文件类型根据具体业务需求设置白名单
例子:人员头像(限制上传图片格式)、考勤表单(pdf或者图片格式)
B. 文件类型建议不要有txt文件上传,除业务需求例外。
C. 文件上传的内容和文件上传的扩展名一致。

···········通用白名单文件类型:
···········图片类型: jpg、jpeg、bmp、png、gif
···········文件类型: pdf、doc、docx、xls、xlsx、xlsm,xlt、xltx、ppt、pptx
···········文本类型: txt(特例情况使用,尽量不要设置该白名单)
8,弱密码
密码复杂度颗粒修改
9,
10,暴露nginx版本信息

1
2
3
在nginx 的conf目录中 ,编辑 nginx.conf文件

在http节点中 添加 server_tokens off;

linux开放指定端口

1
firewall-cmd ``--zone=public --add-port=1935/tcp --permanent
  • –zone #作用域
  • –add-port=1935/tcp #添加端口,格式为:端口/通讯协议
  • –permanent #永久生效,没有此参数重启后失效

kafka监听消息

1
2
3
4
5
6
7
8
@KafkaListener(id = "监听器id",topicPattern = "正则表达式匹配队列")
public void receiveMsg(ConsumerRecord<?,?> consumerRecord){
//业务数据为json字符串
String value = (String) consumerRecord.value();
//类型转换
ExceptionEventPO o = JSONObject.parseObject(value, ExceptionEventPO.class);
//其他业务
}

@KafkaListener(id=”监听器id”,groupId=”消费组名”,topicPattern=”较适用于正则表达式匹配多个队列”,topics={“队列名1”,”队列名2”})

topicPattern属性 为队列正则表达式用法,与topics属性相斥。

docker部署离线镜像包

分布式锁具备的条件

互斥性:任意时刻,只能有一个客户端持有锁

锁超时释放:持有锁超时,可以释放,防止死锁

可重入性:一个线程获取了锁之后,可以再次对其请求加锁

高可用、高性能:加锁和解锁开销要尽可能低,同时保证高可用

安全性:锁只能被持有该锁的服务(或应用)释放。

容错性:在持有锁的服务崩溃时,锁仍能得到释放,避免死锁。

分布式锁实现方案

分布式锁都是通过第三方组件来实现的,目前比较流行的分布式锁的解决方案有:

1、数据库,通过数据库可以实现分布式锁,但是在高并发的情况下对数据库压力较大,所以很少使用。
2、Redis,借助Redis也可以实现分布式锁,而且Redis的Java客户端种类很多,使用的方法也不尽相同。
3、Zookeeper,Zookeeper也可以实现分布式锁,同样Zookeeper也存在多个Java客户端,使用方法也不相同

数据库、zookeeper 参考https://blog.csdn.net/poizxc2014/article/details/123963250

Redis实现分布式锁

1)基本方案:Redis提供了setXX指令来实现分布式锁

1
2
3
4
5
SETNX

格式: setnx key value
将key 的值设为value ,当且仅当key不存在。
若给定的 key已经存在,则SETNX不做任何动作。
1
2
3
String REDIS_LOCAK="xxx";
String value =UUID.randomUUID().toString().replace("-","");
template.opsForValue().setIfAbsent(REDIS_LOCK,value)

可以给锁设置一个超时时间,到时自动释放锁(锁的过期时间大于业务执行时间)

不然会产生死锁

1
2
template.opsForValue().setIfAbsent(REDIS_LOCK,value)
template.expire(REDIS_LOCK,timeout:10,TimeUnit.SECONDS);

分步操作 需要保证原子性

1
template.opsForValue().setIfAbsent(REDIS_LOCK,value,10L,TimeUnit.SECONDS);

2,redission

引入依赖

1
2
3
4
5
<dependency>
<groupId>org.redission</groupId>
<artifactId>redission</artifactId>
<version>3.6.5</version>
</dependency>

在在主启动类中加入如下配置

1
2
3
4
5
6
7
8
@Bean
public Redisson redisson(){
//此为单机模式
Config config=new Config();
config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
// config.setLockWatchdogTimeout(1000); //设置分布式锁watch dog超时时间
return (Redisson) Redisson.create(config);
}
1
2
Rlock redissonLock=redission.getLock(REDIS_LOCK);
redissonLock.unlock();//解锁

Redis与zookeeper分布式锁对比

redis集群是AP,zookeeper集群是CP,redis在集群架构上性能很高,zookeeper在数据一致性上做的更好。

redis往主节点写成功key,马上告诉客户端写成功,收到半数的返回结果就可以执行代码逻辑。所以redis采用抢占式方式进行锁的获取,需要不断的在用户态进行CAS尝试获取锁,对CPU占用率高。

zookeeper在主节点中写好key,会把key同步给所有从节点,并且接受从节点是否同步成功返回。当主节点接收到半数以上的同步成功的结果,就会返回客户端可以执行业务逻辑了。

Zookeeper不会出现主从架构锁丢失问题,主节点挂了,zookeeper的ZAB选举机制一定会把同步成功的从节点变为主节点。

如果追求正确,就选zk集群,如果允许一点出错,追求性能,那选redis

对于redis分布式锁的使用,在企业中是非常常见的,绝大多数情况不会出现极端情况。

oracle安装

(5条消息) Oracle完整安装详解_oracle安装_皓慕的博客-CSDN博客

Mysql分库分表查询

OSS个人的密码

1
2
3
4
5
6
oss:
host: xxx
bucketName:
endpoint: xxx
accessKeyId: xxx
accessKeySecret: xxx

阿里云OSS图片上传

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
package com.star.qiaosi.constant;


import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
* OSS配置类
*
* @author An
* @description TODO
* @date 2022/6/22 1:06
*/
// 当项目启动,Spring 接口,Spring加载之后执行接口中的方法
@Component
public class ConstantPropertiesUtils implements InitializingBean {
// 读取配置文件内容
@Value("${oss.endpoint}")
private String endpoint;

@Value("${oss.accessKeyId}")
private String keyId;

@Value("${oss.accessKeySecret}")
private String keySecret;

@Value("${oss.bucketName}")
private String bucketName;

// 定义公开的静态常量
public static String END_POINT;
public static String ACCESS_KEY_ID;
public static String ACCESS_KEY_SECRET;
public static String BUCKET_NAME;

@Override
public void afterPropertiesSet() throws Exception {
END_POINT = endpoint;
ACCESS_KEY_ID = keyId;
ACCESS_KEY_SECRET = keySecret;
BUCKET_NAME = bucketName;
}

}



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
package com.star.qiaosi.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.ObjectMetadata;
import com.star.qiaosi.constant.ConstantPropertiesUtils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.util.Date;

/**
* 文件上传工具类
*/
@Data
@Component
@ConfigurationProperties(prefix = "oss")
public class OssUtils {

private String host;

private String bucketName;

private String endpoint;

private String accessKeyId;

private String accessKeySecret;

/**
* 上传文件
* @param file
* @return
*/
public Object upload(MultipartFile file) {
// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
String endpoint = ConstantPropertiesUtils.END_POINT;
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = ConstantPropertiesUtils.ACCESS_KEY_ID;
String accessKeySecret = ConstantPropertiesUtils.ACCESS_KEY_SECRET;
// 填写Bucket名称,例如examplebucket。
String bucketName = ConstantPropertiesUtils.BUCKET_NAME;

// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上传文件流
try {
// 获取文件的名称
String fileName = file.getOriginalFilename();
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(getcontentType(fileName.substring(fileName.lastIndexOf("."))));
// 调用oss的方法实现长传
// 第一个参数 bucketName
// 第二个参数 上传到oss的文件路径和文件名称
ossClient.putObject(bucketName, fileName, new ByteArrayInputStream(file.getBytes()),objectMetadata);
// 关闭OSSClient。
ossClient.shutdown();
// 把上传的文件路径返回 (手动拼接)
// 这里设置图片有效时间 我设置了30年
Date expiration = new Date(System.currentTimeMillis() + 946080000 * 1000);
String url = ossClient.generatePresignedUrl(bucketName, fileName, expiration).toString();
return url;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 删除图片
* @param fileUrl
* @return
*/
public boolean deleteImages(String fileUrl) {
// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
String endpoint = ConstantPropertiesUtils.END_POINT;
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = ConstantPropertiesUtils.ACCESS_KEY_ID;
String accessKeySecret = ConstantPropertiesUtils.ACCESS_KEY_SECRET;
// 填写Bucket名称,例如examplebucket。
String bucketName = ConstantPropertiesUtils.BUCKET_NAME;
//创建OSSClient实例
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
String imgFile = fileUrl.replace("https://bucket-ans.oss-cn-hangzhou.aliyuncs.com/", "");
String fileName = imgFile.substring(0, imgFile.indexOf("?"));

// 根据BucketName,objectName删除文件
ossClient.deleteObject(bucketName, fileName);
ossClient.shutdown();
return true;
}

/**
* 预图片
* @param FilenameExtension
* @return
*/
public static String getcontentType(String FilenameExtension) {
if (FilenameExtension.equalsIgnoreCase(".bmp")) {
return "image/bmp";
}
if (FilenameExtension.equalsIgnoreCase(".gif")) {
return "image/gif";
}
if (FilenameExtension.equalsIgnoreCase(".jpeg") ||
FilenameExtension.equalsIgnoreCase(".jpg") ||
FilenameExtension.equalsIgnoreCase(".png")) {
return "image/jpg";
}
if (FilenameExtension.equalsIgnoreCase(".html")) {
return "text/html";
}
if (FilenameExtension.equalsIgnoreCase(".txt")) {
return "text/plain";
}
if (FilenameExtension.equalsIgnoreCase(".vsd")) {
return "application/vnd.visio";
}
if (FilenameExtension.equalsIgnoreCase(".pptx") ||
FilenameExtension.equalsIgnoreCase(".ppt")) {
return "application/vnd.ms-powerpoint";
}
if (FilenameExtension.equalsIgnoreCase(".docx") ||
FilenameExtension.equalsIgnoreCase(".doc")) {
return "application/msword";
}
if (FilenameExtension.equalsIgnoreCase(".xml")) {
return "text/xml";
}
return "image/jpg";
}

}

OBS资源服务器上传

.yml文件

1
2
3
4
5
obs:
ak:
sk:
endPoint:
bucketName:

pom依赖

1
2
3
4
5
<dependency>
<groupId>com.huaweicloud</groupId>
<artifactId>esdk-obs-java-bundle</artifactId>
<version>3.21.8</version>
</dependency>

工具类配置

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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
package com.star.qiaosi.utils;


import cn.hutool.core.io.FileUtil;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.*;

import com.star.qiaosi.config.OBSProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
* @author xxx
* @createTime 2021/12/6 16:31
* @description 华为OBS工具类
*/
@Slf4j
@Component
public class HuaweiOBSUtil {

@Autowired
private OBSProperties obsProperties;
/**
* 上传File类型文件
*
* @param file
* @return
*/
public String uploadFile(File file) {
return getUploadFileUrl(file);
}
public File transferToFile(MultipartFile multipartFile) {
//选择用缓冲区来实现这个转换即使用java 创建的临时文件 使用 MultipartFile.transferto()方法 。
File file = null;
try {
String originalFilename = multipartFile.getOriginalFilename();
String[] filename = originalFilename.split("\\.");
file = File.createTempFile(filename[0], filename[1]); //注意下面的 特别注意!!!
multipartFile.transferTo(file);
file.deleteOnExit();
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
/**
* 上传MultipartFile类型文件
*
* @param
* @return
*/


private String getUploadFileUrl(File file) {
//检查文件是否为null
if (file!=null) {
String fileName = FileUtil.getName(file);
log.info("上传图片:" + fileName);
/*log.info("ak:" + ak);
log.info("sk:" + sk);
log.info("endPoint:" + endPoint);*/
ObsClient obsClient = new ObsClient(obsProperties.getAk(), obsProperties.getSk(), obsProperties.getEndPoint());
try {
//判断桶是否存在,不存在则创建
if (!obsClient.headBucket(obsProperties.getBucketName())) {
obsClient.createBucket(obsProperties.getBucketName());
}
PutObjectRequest request = new PutObjectRequest();
request.setBucketName(obsProperties.getBucketName());
request.setObjectKey(fileName);
request.setFile(file);
request.setAcl(AccessControlList.REST_CANNED_PUBLIC_READ);
PutObjectResult result = obsClient.putObject(request);
String url = result.getObjectUrl();
log.info("图片路径:" + url);
return url;
} catch (Exception e) {
log.error("图片上传错误:{}", e);
throw new RuntimeException("图片上传失败");
}/* finally {
删除本地临时文件
HuaweiOBSUtil.deleteTempFile(file);
}*/
}
return null;
}


/**
* 上传图片自定义code
*
* @param ak
* @param sk
* @param endPoint
* @param file
* @return
*/
public String uploadFileByCode(String ak, String sk, String endPoint, String bucket, File file) {
//String pathname = objectName;
try {
String fileName = FileUtil.getName(file);
log.info("上传图片:" + fileName);
ObsClient obsClient = new ObsClient(ak, sk, endPoint);
//判断桶是否存在,不存在则创建
if (!obsClient.headBucket(bucket)) {
obsClient.createBucket(bucket);
}
PutObjectRequest request = new PutObjectRequest();
request.setBucketName(bucket);
request.setObjectKey(fileName);
request.setFile(file);
request.setAcl(AccessControlList.REST_CANNED_PUBLIC_READ);
PutObjectResult result = obsClient.putObject(request);
String url = result.getObjectUrl();
log.info("文件名称:"+fileName+"图片路径:" + url);
return url;
} catch (Exception e) {
log.error("图片上传错误:{}", e);
} /*finally {
HuaweiOBSUtil.deleteTempFile(file);
}*/
return null;
}

/**
* 删除本地临时文件
*
* @param file
*/
public void deleteTempFile(File file) {
if (file != null) {
File del = new File(file.toURI());
del.delete();
}
}


/**
* 批量n天删除之前的文件
*
* @param ak
* @param sk
* @param endPoint
* @param bucket
* @param requireHours
*/
public void batchDeleteForHoursago(String ak, String sk, String endPoint, String bucket, int requireHours) {
ObsClient obsClient = new ObsClient(ak, sk, endPoint);
long currentTime = new Date().getTime();
try {
ListObjectsRequest listRequest = new ListObjectsRequest(bucket);
listRequest.setMaxKeys(1000); // 每次至多返回1000个对象
ObjectListing listResult;
Date lastModified;
long hourMillisecond = 1000 * 3600 * 1;
// 分页查询
do {
List<KeyAndVersion> toDelete = new ArrayList<>();
listResult = obsClient.listObjects(listRequest);
for (ObsObject obsObject : listResult.getObjects()) {
lastModified = obsObject.getMetadata().getLastModified();
long diffs = (currentTime - lastModified.getTime()) / hourMillisecond; // 当前时间减去文件修改时间
if (diffs > requireHours
&& (obsObject.getObjectKey().endsWith(".ts") || obsObject.getObjectKey().endsWith(".mp4"))) {
log.info("文件距现在{}小时,对象更改日期:{},文件对象:{}", diffs, lastModified, obsObject.getObjectKey());
toDelete.add(new KeyAndVersion(obsObject.getObjectKey()));
}
}
// 设置下次列举的起始位置
listRequest.setMarker(listResult.getNextMarker());

//批量删除文件
log.info("待删除的OBS对象数量:{}", toDelete.size());
if (!CollectionUtils.isEmpty(toDelete)) {
DeleteObjectsRequest deleteRequest = new DeleteObjectsRequest(bucket);
deleteRequest.setQuiet(true); // 设置为quiet模式,只返回删除失败的对象
deleteRequest.setKeyAndVersions(toDelete.toArray(new KeyAndVersion[toDelete.size()]));
DeleteObjectsResult deleteResult = obsClient.deleteObjects(deleteRequest);
if (!CollectionUtils.isEmpty(deleteResult.getErrorResults())) {
log.error("删除失败的OBS对象数量:{}", deleteResult.getErrorResults().size());
}
}
} while (listResult.isTruncated());

} catch (Exception e) {
log.error("华为OBS批量删除异常", e);
} finally {
try {
obsClient.close();
} catch (IOException e) {
log.error("华为OBS关闭客户端失败", e);
}
}
}

/**
* 删除单个对象
*
* @param ak
* @param sk
* @param endPoint
* @param bucket
* @param objectKey
* @return
*/
public boolean deleteFile(String ak, String sk, String endPoint, String bucket, String objectKey) {
ObsClient obsClient = new ObsClient(ak, sk, endPoint);
DeleteObjectResult deleteObjectResult = obsClient.deleteObject(bucket, objectKey);
boolean deleteMarker = deleteObjectResult.isDeleteMarker();
try {
obsClient.close();
} catch (IOException e) {
log.error("华为OBS关闭客户端失败", e);
}
return deleteMarker;
}

/**
* 查询桶内已使用空间大小
*
* @param ak
* @param sk
* @param endPoint
* @param bucket
* @return 单位字节
*/
public long getBucketUseSize(String ak, String sk, String endPoint, String bucket) {
ObsClient obsClient = new ObsClient(ak, sk, endPoint);
BucketStorageInfo storageInfo = obsClient.getBucketStorageInfo(bucket);
log.info("{} 桶内对象数:{} 已使用的空间大小B:{} GB:{}", bucket, storageInfo.getObjectNumber(), storageInfo.getSize(), storageInfo.getSize() / 1024 / 1024 / 1024);
try {
obsClient.close();
} catch (IOException e) {
log.error("华为OBS关闭客户端失败", e);
}
return storageInfo.getSize();
}


public String readFileContent(String fileName) {
File file = new File(fileName);
BufferedReader reader = null;
StringBuffer sbf = new StringBuffer();
try {
reader = new BufferedReader(new FileReader(file));
String tempStr;
while ((tempStr = reader.readLine()) != null) {
sbf.append(tempStr);
}
reader.close();
return sbf.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
return sbf.toString();
}

/**
* 根据下载地址url获取文件名称
* @param url
* @throws IOException
*/
public String getFilenameByUrl(String url){
String fileName= null;
try {
//url编码处理,中文名称会变成百分号编码
String decode = URLDecoder.decode(url, "utf-8");
fileName = decode.substring(decode.lastIndexOf("/")+1);
log.info("fileName :" + fileName);
} catch (UnsupportedEncodingException e) {
log.error("getFilenameByUrl() called with exception => 【url = {}】", url,e);
e.printStackTrace();
}
return fileName;
}
}



映射注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.star.qiaosi.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "obs")
public class OBSProperties {
private String endPoint;
private String ak;
private String sk;
private String bucketName;
}

Mysql字典表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE TABLE `sys_dict_item` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`dict_type_code` varchar(255) NOT NULL COMMENT '字典类型编码',
`dict_code` varchar(255) NOT NULL COMMENT '数据编码',
`dict_value` varchar(255) DEFAULT NULL COMMENT '值',
`dict_name` varchar(255) NOT NULL COMMENT '字典名称',
`dict_desc` varchar(255) DEFAULT NULL COMMENT '描述',
`sort` int(5) DEFAULT NULL COMMENT '排序',
`status` enum('0','1') NOT NULL DEFAULT '1' COMMENT '0 禁用;1 启用',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
`create_by_user_id` int(11) DEFAULT NULL COMMENT '创建人id',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(50) DEFAULT NULL COMMENT '修改人',
`update_by_user_id` int(11) DEFAULT NULL COMMENT '修改人id',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_dict_code` (`dict_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统字典条目表';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE `sys_dict_type` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`dict_type_code` varchar(255) NOT NULL COMMENT '数据类型编码',
`dict_type_name` varchar(255) NOT NULL COMMENT '数据类型名称',
`dict_desc` varchar(255) DEFAULT NULL COMMENT '描述',
`sort` int(5) DEFAULT '1' COMMENT '排序',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
`create_by_user_id` int(11) DEFAULT NULL COMMENT '创建人id',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(50) DEFAULT NULL COMMENT '修改人',
`update_by_user_id` int(11) DEFAULT NULL COMMENT '修改人id',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_dict_type_code` (`dict_type_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统字典类型表';

Springboot整合secruity

(4条消息) SpringBoot结合SpringSecurity动态权限认证设置_幸运小男神的博客-CSDN博客

src/main/java/com/vipbbo/security/springboot/controller/LoginController.java · /w/security-spring-boot - 码云 - 开源中国 (gitee.com)

索引下推 覆盖索引

索引下推(index condition pushdown )简称ICP,在Mysql5.6的版本上推出,用于优化查询。

在不使用ICP的情况下,在使用非主键索引(又叫普通索引或者二级索引)进行查询时,存储引擎通过索引检索到数据,然后返回给MySQL服务器,服务器然后判断数据是否符合条件 。

在使用ICP的情况下,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器

代码生成器

引入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
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>

<!-- 代码自动生成器依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.0.5</version>
</dependency>

<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

编写yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 9099
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# MySql
url: jdbc:mysql://localhost:3306/xxx?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
#url: jdbc:mysql://127.0.0.1:3306/xxx?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
username: xxxx
password: xxxx
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
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
package com.example.test.generator;


import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Scanner;

public class CodeGenerator {

public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}


public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();

// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");//设置代码生成路径
gc.setFileOverride(true);//是否覆盖以前文件
gc.setOpen(false);//是否打开生成目录
gc.setAuthor("Cheng");//设置项目作者名称
gc.setIdType(IdType.AUTO);//设置主键策略
gc.setBaseResultMap(true);//生成基本ResultMap
gc.setBaseColumnList(true);//生成基本ColumnList
gc.setServiceName("%sService");//去掉服务默认前缀
gc.setDateType(DateType.ONLY_DATE);//设置时间类型
mpg.setGlobalConfig(gc);

// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/xxx?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("xxx");
dsc.setPassword("xxx");
mpg.setDataSource(dsc);

// 包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.example.test");
pc.setMapper("mapper");
pc.setXml("mapper.xml");
pc.setEntity("entity");
pc.setService("service");
pc.setServiceImpl("service.impl");
pc.setController("controller");
mpg.setPackageInfo(pc);

// 策略配置
StrategyConfig sc = new StrategyConfig();
sc.setNaming(NamingStrategy.underline_to_camel);
sc.setColumnNaming(NamingStrategy.underline_to_camel);
sc.setEntityLombokModel(true);//自动lombok
sc.setRestControllerStyle(true);
sc.setControllerMappingHyphenStyle(true);

sc.setLogicDeleteFieldName("deleted");//设置逻辑删除

//设置自动填充配置
TableFill gmt_create = new TableFill("create_time", FieldFill.INSERT);
TableFill gmt_modified = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(gmt_create);
tableFills.add(gmt_modified);
sc.setTableFillList(tableFills);

//乐观锁
sc.setVersionFieldName("version");
sc.setRestControllerStyle(true);//驼峰命名


// sc.setTablePrefix("tbl_"); 设置表名前缀
sc.setInclude(scanner("表名,多个英文逗号分割").split(","));
mpg.setStrategy(sc);

// 生成代码
mpg.execute();
}

}

springboot整合flyway

导入pom依赖

1
2
3
4
5
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>5.2.1</version>
</dependency>

修改yml文件 当作参考

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
flyway配置详解
flyway.baseline-description对执行迁移时基准版本的描述.
flyway.baseline-on-migrate当迁移时发现目标schema非空,而且带有没有元数据的表时,是否自动执
行基准迁移,默认false.
flyway.baseline-version开始执行基准迁移时对现有的schema的版本打标签,默认值为1.
flyway.check-location检查迁移脚本的位置是否存在,默认false.
flyway.clean-on-validation-error当发现校验错误时是否自动调用clean,默认false.
flyway.enabled是否开启flywary,默认true.
flyway.encoding设置迁移时的编码,默认UTF-8.
flyway.ignore-failed-future-migration当读取元数据表时是否忽略错误的迁移,默认false.
flyway.init-sqls当初始化好连接时要执行的SQL.
flyway.locations迁移脚本的位置,默认db/migration.
flyway.out-of-order是否允许无序的迁移,默认false.
flyway.password目标数据库的密码.
flyway.placeholder-prefix设置每个placeholder的前缀,默认${.
flyway.placeholder-replacementplaceholders是否要被替换,默认true.
flyway.placeholder-suffix设置每个placeholder的后缀,默认}.
flyway.placeholders.[placeholder name]设置placeholder的value
flyway.schemas设定需要flywary迁移的schema,大小写敏感,默认为连接默认的schema.
flyway.sql-migration-prefix迁移文件的前缀,默认为V.
flyway.sql-migration-separator迁移脚本的文件名分隔符,默认__
flyway.sql-migration-suffix迁移脚本的后缀,默认为.sql
flyway.tableflyway使用的元数据表名,默认为schema_version
flyway.target迁移时使用的目标版本,默认为latest version
flyway.url迁移时使用的JDBC URL,如果没有指定的话,将使用配置的主数据源
flyway.user迁移数据库的用户名
flyway.validate-on-migrate迁移时是否校验,默认为true.

参考yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
flyway:
# 是否启用flyway
enabled: true
# 编码格式,默认UTF-8
encoding: UTF-8
# 迁移sql脚本文件存放路径,默认db/migration
locations: classpath:db/migration
# 迁移sql脚本文件名称的前缀,默认V
sql-migration-prefix: V
# 迁移sql脚本文件名称的分隔符,默认2个下划线__
sql-migration-separator: __
# 迁移sql脚本文件名称的后缀
sql-migration-suffixes: .sql
# 迁移时是否进行校验,默认true
validate-on-migrate: true
# 当迁移发现数据库非空且存在没有元数据的表时,自动执行基准迁移,新建schema_version表
baseline-on-migrate: true

在resource下新建包 报名db.migration

sql文件名称

1
V1.0__create_user_table.sql //V+版本号(版本号的数字间以”.“或”_“分隔开)+双下划线(用来分隔版本号和描述)+文件描述+后缀名

pom引入管理工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<build>
<plugins>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>5.2.4</version>
<configuration>
<url>jdbc:mysql://localhost:3306/flyway-demo?characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai
</url>
<user>root</user>
<password>root</password>
<driver>com.mysql.cj.jdbc.Driver</driver>
</configuration>
</plugin>
</plugins>
</build>
原创技术分享,您的支持将鼓励我继续创作