从0到1搭建web系统(一)

前言

虽然说从事技术开发已经有一段时日了,对一个系统的搭建及部署也有所了解,但确实没有动手实践过。借着这次机会将整个流程走了一遍,因此准备写一篇文章记录这一路踩过的各种坑,并分享给需要的人。本文将梳理一个后台管理系统(Java+SpringBoot+React+antd+nginx+mysql)是如何从开发到部署至云服务器的全流程。

服务端开发

服务端采用SpringBoot+DDD分层架构+mysql作为技术栈,DDD分层架构如下:
image
模型构建具体的就不讲了,回头会有专门的文章去分析,主要讲讲如果要在云服务器部署并被前端访问到,要做哪些配置。

允许跨域请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author shinan.chen
* @since 2021/12/23
*/
@Configuration
public class BeanConfig {
/**
* 允许跨域请求
* @return
*/
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
return bean;
}
}

集成lombok和mapstruct

集成lombok和mapstruct则需要在父项目中配置两者的编译顺序,否则在修改实体的属性时不会重新生成mapstruct的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source> <!-- depending on your project -->
<target>1.8</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
</plugin>

简易登录鉴权功能

通过JwtUtil生成token,并在登录后的每个接口中携带这个token来做鉴权校验。

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
/**
* Jwt工具类
*
* @author shinan.chen
* @since 2021/12/18
*/
public class JwtUtil {
private static final String SIGN = "da5c4a56112326@564#546c1!?SCasd";
public static final String KEY = "loginInfo";
/**
* 生成token
*
* @param map
* @return
*/
public static String createToken(Map<String, String> map) {
//token的保质期
Calendar instance = Calendar.getInstance();
instance.add(Calendar.HOUR, 2);
//创建jwt
JWTCreator.Builder builder = JWT.create();
//头部使用默认
//添加payload
map.forEach(builder::withClaim);
//添加过期时间和签名
return builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(SIGN));
}

/**
* 验证token
*
* @param token token
* @return
*/
public static DecodedJWT verifyToken(String token) {
return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 登录方法
**/
public AccountLoginDTO login(AccountLoginParam login) {
Account account = accountRepository.findByUsername(login.getUsername());
if (account == null || !account.getPassword().getValue().equals(login.getPassword())) {
return new AccountLoginDTO(false, "用户名或密码不正确", null);
}
account.setLastLoginTime(new Date());
accountRepository.save(account);
Map<String, String> map = new HashMap<>(2);
map.put(JwtUtil.KEY, JSON.toJSONString(new AccountLoginInfo(account.getId(), account.getUsername().getValue(), account.getRole().getValue())));
String token = JwtUtil.createToken(map);
return new AccountLoginDTO(true, null, token);
}
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
/**
* Interceptor拦截
*
* @author shinan.chen
* @since 2021/12/30
*/
@Component
@Slf4j
public class AuthorityInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String authorizationHeader = request.getHeader("Authorization");
if (!StringUtils.isEmpty(authorizationHeader)) {
if (authorizationHeader.startsWith("Bearer ")) {
try {
String token = authorizationHeader.substring(7);
DecodedJWT jwt = JwtUtil.verifyToken(token);
String loginInfoJson = jwt.getClaim(JwtUtil.KEY).asString();
AccountLoginInfo loginInfo = JSONObject.parseObject(loginInfoJson,AccountLoginInfo.class);
ThreadLocalContext.localLoginInfo.set(loginInfo);
return true;
} catch (Exception e) {
log.error(ErrorUtil.printStackTrace(e));
}
}
}
response.setStatus(401);
return false;
}
}

/**
* @author shinan.chen
* @since 2021/12/30
*/
@Configuration
public class InterceptorAdapterConfig extends WebMvcConfigurerAdapter {
@Autowired
private AuthorityInterceptor authorityInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自己的拦截器并设置拦截的请求路径
registry.addInterceptor(authorityInterceptor).addPathPatterns("/api/**")
.excludePathPatterns("/api/account/login/**");
}
}

通过maven打包项目,并启动jar

通过maven打SpringBoot的包的话需要指定启动类,这部分配置需要写在start包下的pom中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--spring boot打包的话需要指定一个唯一的入口类-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.hd.HdApplication</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

maven打包的话可以在父工程下执行命令:mvn clean package,打完包之后只需要用start包下生成的jar包即为可运行的包

前端开发

前端采用react+antd作为技术栈,笔者对前端的技术掌握不多,而我们做的也只是一个简单的增删改查系统,因此我在giuhub上找到了一个包含了基本功能的react后台管理系统解决方案,这个react框架帮助我快速搭建了前端页面。这里我会记录一些react用法以及踩过的坑,希望能帮助那些后端写前端的同学快速上手。

安装前端开发环境

1
2
3
4
5
6
7
8
9
# 安装yarn
npm install -g yarn
# 克隆项目
git clone https://github.com/yezihaohao/react-admin.git
cd react-admin
# 安装项目的依赖
yarn install
# 运行项目
yarn start

react组件的基本写法

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
import React, { useEffect, useState } from 'react';
import { Row, Col, Card, Space, DatePicker, Drawer, Form, Input, Select, Radio, message, Modal, FormInstance, Pagination } from 'antd';
import { Table, Button } from 'antd';
import BreadcrumbCustom from '../../components/widget/BreadcrumbCustom';
import { DownOutlined, ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
import {IAccount, listAccounts, createAccount, updateAccount, deleteAccount, pageAccounts} from '../../service/api/account';
import moment from 'moment';
const { confirm } = Modal;

const AccountView = (props: any) => {
const [accounts, setAccounts] = useState<IAccount[]>();
const [accountDetail, setAccountDetail] = useState<IAccount>();
const [accountVisible, setAccountVisible] = useState(false);
const [pageNum, setPageNum] = useState(1);
const [pageTotal, setPageTotal] = useState(1);
const [pageSize, setPageSize] = useState(10);
const formRef = React.createRef<FormInstance>();

const columns = [
{
title: '用户名',
dataIndex: 'username',
key: 'username',
},
{
title: '上次登录时间',
dataIndex: 'lastLoginTime',
key: 'lastLoginTime',
render(val:Date){
return val?moment(val).format('YYYY-MM-DD HH:mm'):<>&mdash;</>
},
},
{
title: '操作',
key: 'action',
render: (text: any, record: any) => (
<Space size="middle">
<a onClick={()=>{
console.log("record:"+JSON.stringify(record));
setAccountDetail(record);
setAccountVisible(true);
}}>编辑 </a>
<a onClick={e=>{onDelete(e,record)}}>删除 </a>
</Space>
),
}
];

useEffect(() => {
loadData();
}, [pageNum, pageSize]);

useEffect(() => {
if(accountDetail){
formRef.current!.setFieldsValue(accountDetail);
}
}, [accountDetail]);

const loadData=()=>{
pageAccounts(pageNum,pageSize).then((info: any) => {
setAccounts(info.data.list);
setPageTotal(info.data.total);
console.log("info:"+JSON.stringify(info.data));
});
}

const onFinish = (values: any) => {
console.log("values:"+values);
if(accountDetail){
updateAccount({
...values,
id: accountDetail.id
}).then((info: any) => {
console.log("info:"+JSON.stringify(info));
message.success("更新成功");
setAccountVisible(false);
setAccountDetail(undefined);
loadData();
});
}else{
createAccount(values).then((info: any) => {
console.log("info:"+JSON.stringify(info));
message.success("创建成功");
setAccountVisible(false);
setAccountDetail(undefined);
loadData();
});
}
};

const onDelete = (e:any,item:IAccount)=>{
e.stopPropagation();
confirm({
title: '提示',
icon: <ExclamationCircleOutlined />,
content: '确定要删除该用户吗?删除后无法恢复',
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk() {
deleteAccount(item.id).then((info: any) => {
console.log("info:"+JSON.stringify(info));
message.success("删除成功");
loadData();
});
},
onCancel() {
},
});
}

const onCancel = ()=>{
setAccountDetail(undefined);
setAccountVisible(false);
}

const onPageChange=(pageNum:number,pageSize:number)=>{
console.log("pageNum:"+pageNum);
console.log("pageSize:"+pageSize);
setPageNum(pageNum);
setPageSize(pageSize);
// setPageSize(info.data.pageSize);
}

return (
<div className="gutter-example">
<BreadcrumbCustom breads={['用户管理']} />
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="用户管理" bordered={false} extra={
<Button type="primary" onClick={()=>{
setAccountDetail(undefined);
setAccountVisible(true);
if(formRef.current){
formRef.current!.resetFields();
}
}} icon={<PlusOutlined />}>
新增
</Button>
}>
<Table dataSource={accounts} columns={columns} pagination={false}/>
<div style={{display:"flex", justifyContent:"flex-end", paddingTop:"10px"}}>
<Pagination
defaultCurrent={pageNum}
total={pageTotal}
pageSize = {pageSize}
hideOnSinglePage={false}
className="pagination"
onChange={onPageChange}
showSizeChanger
/>
</div>
</Card>
</div>
</Col>
</Row>
<Drawer
title={accountDetail?"编辑用户":"新增用户"}
visible={accountVisible}
width={720}
onClose={onCancel}
bodyStyle={{ paddingBottom: 80 }}
>
<Form onFinish={onFinish} ref={formRef} layout="vertical" hideRequiredMark>
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input placeholder="请输入用户名" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="password"
label="密码"
rules={[{ required: true, message: '请输入密码' }]}
>
<Input placeholder="请输入密码" />
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={24}>
<Form.Item
name="role"
label="角色"
rules={[{ required: true, message: '请选择角色' }]}
>
<Radio.Group >
<Radio value="admin">管理员</Radio>
</Radio.Group>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={24}>
<Form.Item>
<Button type="primary" htmlType="submit" >保存</Button>
<Button htmlType="button" onClick={onCancel}>取消</Button>
</Form.Item>
</Col>
</Row>
</Form>
</Drawer>
</div>
);
};

export default AccountView;

前端请求的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Created by shinan.chen on 2021/12/25.
*/
import { getCommon, postCommon } from '../tools';
import * as config from '../config';

export interface IAccount {
id: number,
username: string,
password: string,
role: string,
lastLoginTime: Date
}

export const listAccounts = () =>
getCommon({
url: `${config.SERVICE_URL}/account/list_all`,
});

export const createAccount = (data: IAccount) =>
postCommon({
url: `${config.SERVICE_URL}/account/create`,
data
});

请求包装工具类

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
/**
* 公用get请求
* @param url 接口地址
* @param msg 接口异常提示
* @param headers 接口所需header配置
*/
export const getCommon = ({ url, msg = '接口异常' }: IFRequestParam) => {
const token = localStorage.getItem("token");
let Authorization = '';
if (token) Authorization = `Bearer ${token}`;
return axios
.get(url, {
headers: { 'Authorization': Authorization }
})
.then((res) => {
// console.log("res:" + JSON.stringify(res));
if (res.data.success) {
return res.data;
} else {
message.error("接口调用异常");
}
})
.catch((err) => {
if (err && err.response && err.response.status === 401) {
// token失效跳转到登录页面
localStorage.removeItem("token");
window.location.replace("#/login");
message.error("登录失效,请重新登录");
} else {
message.error("网络错误");
}
}
)
};

/**
* 公用post请求
* @param url 接口地址
* @param data 接口参数
* @param msg 接口异常提示
* @param headers 接口所需header配置
*/
export const postCommon = ({ url, data, msg = '接口异常' }: IFRequestParam) => {
const token = localStorage.getItem("token");
let Authrization = '';
if (token) Authrization = `Bearer ${token}`;
return axios
.post(url, data, {
headers: { 'Authorization': Authrization }
})
.then((res) => {
if (res.data.success) {
return res.data;
} else {
message.error("接口调用异常");
}
})
.catch((err) => {
if (err && err.response && err.response.status === 401) {
// token失效跳转到登录页面
localStorage.removeItem("token");
window.location.replace("#/login");
message.error("登录失效,请重新登录");
} else {
message.error("网络错误");
}
})
};

react组件中的外传里和里传外

外传里和里传外

1
2
3
4
<CourseDefinitionList
visible={courseDefinitionVisible}
setVisible={setCourseDefinitionVisible}
/>
1
2
3
<CourseDefinitionList
onFinish={onFinish}
/>

安装echarts-for-react

1
2
3
npm install --save echarts-for-react
# 如果需要使用echarts的一些特殊方法需要安装
npm install --save echarts

云服务器部署

云服务器部署的工具包括jdk+docker+nginx+mysql,这里目前只用docker部署nginx,后续优化的过程中会把后端服务也用docker启动。

安装JDK

1
2
3
4
5
6
# 执行以下命令,查看yum源中JDK版本
yum list java*
# 执行以下命令,使用yum安装JDK1.8
yum -y install java-1.8.0-openjdk*
# 执行以下命令,查看是否安装成功
java -version

openJDK安装好后的目录位于:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.55.x86_64

安装docker

1
2
3
4
5
6
7
8
9
# 安装Docker的依赖库
yum install -y yum-utils device-mapper-persistent-data lvm2
# 添加Docker CE的软件源信息
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 安装Docker CE
yum makecache fast
yum -y install docker-ce
# 启动Docker服务
systemctl start docker

Docker的默认官方远程仓库是hub.docker.com,由于网络原因,下载一个Docker官方镜像可能会需要很长的时间,甚至下载失败。为此,阿里云容器镜像服务ACR提供了官方的镜像站点,从而加速官方镜像的下载。下面介绍如何使用阿里云镜像仓库。

image

1
2
3
4
5
6
7
8
9
10
# 配置Docker的自定义镜像仓库地址。请将下面命令中的镜像仓库地址https://kqh8****.mirror.aliyuncs.com替换为阿里云为您提供的专属镜像加速地址。
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://kqh8****.mirror.aliyuncs.com"]
}
EOF
# 重新加载服务配置文件
systemctl daemon-reload
# 重启Docker服务
systemctl restart docker

安装MySQL数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 执行以下命令,下载并安装MySQL官方的Yum Repository
wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm
yum -y install mysql57-community-release-el7-10.noarch.rpm
yum -y install mysql-community-server
# 执行以下命令,启动 MySQL 数据库
systemctl start mysqld.service
# 执行以下命令,查看MySQL初始密码
grep "password" /var/log/mysqld.log
# 执行以下命令,登录数据库
mysql -uroot -p
# 执行以下命令,修改MySQL默认密码
set global validate_password_policy=0; #修改密码安全策略为低(只校验密码长度,至少8位)。
ALTER USER 'root'@'localhost' IDENTIFIED BY '12345678';
# 执行以下命令,授予root用户远程管理权限
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '12345678';

通过docker启动nginx

先建几个配置文件,用来覆盖镜像容器中的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看Docker镜像仓库中Nginx的可用版本
docker search nginx
# 拉取最新版的Nginx镜像
docker pull nginx:latest
# 查看本地镜像
docker images
# 启动镜像
docker run --name nginx-test # 容器命名
-p 8080:80 # 端口进行映射,将本地8080端口映射到容器内部的80端口
-v /root/nginx/html:/usr/share/nginx/html
-v /root/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
-v /root/nginx/logs:/var/log/nginx
-v /root/nginx/conf.d:/etc/nginx/conf.d
-v /root/front1231:/etc/nginx/front1231 # 将前端代码复制进到容器里
-d nginx:latest # 设置容器在后台一直运行

/root/nginx/html配置:

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>系统时间</title>
</head>
<body>
<div id="datetime">
<script>
setInterval("document.getElementById('datetime').innerHTML=new Date().toLocaleString();", 1000);
</script>
</div>
</body>

/root/nginx/conf/nginx.conf配置:

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
user nginx;
worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;


events {
worker_connections 1024;
}


http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

# 开启gzip压缩
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
gzip_types text/plain application/json application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;
gzip_vary on;

include /etc/nginx/conf.d/*.conf;
}

/root/nginx/conf.d配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
server_name localhost;

location / {
root /etc/nginx/front1231;
index index.html index.htm;
expires 1d;
autoindex on;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

docker容器中安装vim

1
2
3
4
apt-get install vim
# 若提示 Unable to locate package vim,则
apt-get update
apt-get install vim

后端代码部署至服务器并可用的流程

1
2
3
4
5
6
7
8
9
# 在父项目下构建jar包
mvn clean package
# 将jar包拷贝至云服务器
scp -rp /Users/xxx/Downloads/hd-start.jar root@xxx.xxx.xxx.xxx:/root
# 停掉原来的java进程
ps -a|grep java
kill -9 xxx
# 通过后台运行的方式启动jar包,并指定配置文件
java -jar hd-start.jar --spring.profiles.active=prod >temp.text &

前端代码部署至服务器并可用的流程

1
2
3
4
5
6
7
8
# 先删除云服务器中的front1231
rm -rf front1231
# 将前端构建包拷贝至云服务器
scp -rp /Users/xxx/Downloads/build root@xxx.xxx.xxx.xxx:/root/front1231
# 重启nginx
docker ps
docker stop xxx
docker start xxx

云服务器开放的安全组的端口规则

  • 8080:nginx端口
  • 8081:后端服务端口
  • 3306:mysql端口供远程访问数据库

image

踩坑记录

java -jar 找不到或无法加载主类

问题描述:springboot + maven 打包成jar包后,使用java jar命令启动jar包时,报错:找不到或无法加载主类

解决办法:需要在pom文件中使用相应的springboot maven 打包插件,并且指定相应的启动类,即mainClass。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.hd.HdApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

云服务器上的mysql无法远程连接

问题描述:远程服务器安装了mysql服务并且启动成功,在本地可以进行自由的访问和更新等操作,而远程通过工具连接该mysql数据库会报Host ‘xxx.xxx.xxx.xxx’(本地ip) is not allowed to connect to this MySQL server 的错误

解决办法:
1、配置mysql可远程登录

1
2
3
4
5
6
7
# 通过命令登录数据库
mysql -uroot -p
# 切换到mysql
use mysql
select host from user where user = ‘root’
# 修改root用户的允许登录host为所有
update user set host = ‘%’ where user = ‘root’

2、云服务器开放安全组的端口

image

echarts版本升级之后报错

问题描述:react使用的echarts版本从4.x.x升级到5.x.x之后启动项目报错:’echarts’ does not contain a default export (imported as ‘echarts’).

解决办法:把import方式 改成:var echarts = require(‘echarts’)

Visual Studio Code的TypeScript语法校验失效

问题描述:在升级npm各种包之后,打开vs code发现右下角频繁报错:“The typescript language service has been aborted 5 times since it was started. The service will not be restarted.”/“TypeScript 语言服务在其启动后已中止 5 次。将不会重启该服务。

解决办法:打开vs code的设置页面,找到setting.json文件,并在文件中加上"typescript.tsdk": "node_modules/typescript/lib"即可重新生效TypeScript语法校验;

image

在云服务器中通过jar包方式连接mysql连不上

问题描述:在云服务器中通过jar包方式连接mysql连不上,报错:javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate),但是通过命令可以连上

解决办法:网上找了很多文章,有的说tomcat依赖包冲突,有的说这个文件要改\jre\lib\security\java.security,经过测试都没有用。多次失败后我发现可能和连接 mysql 有关系,最后在application.properties中的数据库连接配置添加useSSL=false即可连通,整体连接配置如下:

1
spring.datasource.url=jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&useSSL=false

vi字符串检索(查找)命令

问题描述:如何在vi的某个文本中查找内容

解决办法:/ 命令(输入/+string,回车后即可定位查找)

cat & grep 在文本内查找关键词

问题描述:如何在linux系统下对某个文件内容查找关键词

解决办法:

1
2
3
4
# 显示关键词所在行
cat test.txt | grep keyword
# 显示关键词所在行,及其前后各3行
cat test.txt | grep keyword -C 3

前端资源访问慢的问题

问题描述:通过nginx访问前端资源加载慢

解决办法:在nginx中配置缓存

1、配置gzip压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#前面有很多,不关注多余的
http {
#前面有很多,不关注多余的

#开启gzip压缩
gzip on;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明
gzip_comp_level 9;
# 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
gzip_types text/plain application/json application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;
# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;

#后面有很多,不关注多余的
}

2、配置cache缓存

1
2
3
4
5
6
location /api {
proxy_pass http://192.168.0.0:80;
#expires作用于http, server, location。 可以控制HTTP应答中的“Expires”和“Cache-Control”的头标(起到控制页面缓存的作用)。
#如果expires为-1s,即永远过期,则reponse header为Cache-Control: no-cache, 当expires为正值,则header为Cache-Control: max-age = #
expires 1d;
}

antd在yarn start后报错

问题描述:升级antd4后运行构建命令报错less问题

image

1
2
3
  /* stylelint-disable-next-line function-calc-no-invalid */
padding-right: calc(@drawer-header-close-padding - var(--scroll-bar));
^

解决办法:是因为less和less-loader的版本不兼容问题,参考https://github.com/ant-design/ant-design/issues/23125,执行如下命令

1
2
# 要删除旧的npm包,再用cmd以管理员权限重新安装一次
rm -rf ./node_modules && npm install

参考文献