Linux部署

CentOs8安装docker出现冲突。

解决方法

出现以错误,根据提示在命令结尾加上–allowerasing或–nobest后再次执行即可

1
2
yum install -y docker-ce --nobest
yum install -y docker-ce --allowerasing

启动docker并设置开机自启

1
systemctl start docker && systemctl enable docker

查看Docker是否安装成功

1
docker version

安装docker-compose

安装docker-compose

方法一:

docker-compose版本选择

1
2
3
4
curl -L https://github.com/docker/compose/releases/download/1.26.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose
123

查看docker-compose版本

1
2
docker-compose version
1

方法二:

1、安装python-pip

1
2
3
4
yum -y install epel-release
dnf install -y python3
dnf install python3-paramiko
123

2、安装docker-compose

1
pip3 install docker-compose

Redis安装

Docker搜索redis镜像

命令:docker search <镜像名称>

1
2
docker search redis
1

在这里插入图片描述

可以看到有很多redis的镜像,此处因没有指定版本,所以下载的就是默认的最新版本 。redis latest.

Docker拉取镜像

命令::docker pull <镜像名称>:<版本号>

1
2
docker pull redis
1

在这里插入图片描述

Docker挂载配置文件

接下来就是要将redis 的配置文件进行挂载,以配置文件方式启动redis 容器。(挂载:即将宿主的文件和容器内部目录相关联,相互绑定,在宿主机内修改文件的话也随之修改容器内部文件)

1)、挂载 redis 的配置文件

2)、挂载 redis 的持久化文件(为了数据的持久化)。

本人的配置文件是放在

1
2
liunx` 下redis.conf文件位置: `/home/redis/myredis/redis.conf
liunx` 下redis的data文件位置 : `/home/redis/myredis/data

位置可以自己随便选择哈

在这里插入图片描述

mkdir -p /home/redis/myredis 命令 是不存在就直接创建/home/redis/myredis 文件夹

在这里插入图片描述

myredis.conf 是我手动上传的。 (文件在文末,redis.conf 的标准文件在redis官网也可以找到)

在这里插入图片描述

启动redis 容器

1
2
docker run --restart=always --log-opt max-size=100m --log-opt max-file=2 -p 6479:6379 --name myredis -v /home/redis/myredis/myredis.conf:/etc/redis/redis.conf -v /home/redis/myredis/data:/data -d redis redis-server /etc/redis/redis.conf  --appendonly yes  --requirepass 181393
1
  1. --restart=always 总是开机启动
  2. --log是日志方面的
  3. -p 6379:6379 将6379端口挂载出去
  4. --name 给这个容器取一个名字
  5. -v 数据卷挂载
    - /home/redis/myredis/myredis.conf:/etc/redis/redis.conf 这里是将 liunx 路径下的myredis.conf 和redis下的redis.conf 挂载在一起。
    - /home/redis/myredis/data:/data 这个同上
  6. -d redis 表示后台启动redis
  7. redis-server /etc/redis/redis.conf 以配置文件启动redis,加载容器内的conf文件,最终找到的是挂载的目录 /etc/redis/redis.conf就是liunx下的/home/redis/myredis/myredis.conf
  8. –appendonly yes 开启redis 持久化
  9. –requirepass 000415 设置密码 (如果你是通过docker 容器内部连接的话,就随意,可设可不设。但是如果想向外开放的话,一定要设置,我被搞过,可以看这篇文章“阿里云服务器中毒‘Kirito666’经历”)
  10. 成功界面

在这里插入图片描述

测试

通过docker ps指令查看启动状态

1
2
docker ps -a |grep myredis # 通过docker ps指令查看启动状态,是否成功.
1

在这里插入图片描述

查看容器运行日志

命令:docker logs –since 30m <容器名>

此处 --since 30m 是查看此容器30分钟之内的日志情况。

1
2
docker logs --since 30m myredis
1

在这里插入图片描述

容器内部连接进行测试

进入容器

命令:docker exec -it <容器名> /bin/bash

此处跟着的 redis-cli 是直接将命令输在上面了。

1
2
docker exec -it myredis redis-cli
1

进入之后,我直接输入查看命令:

在这里插入图片描述
error是没有权限验证。(因为设置了密码的。)

验证密码:

1
2
auth 密码
1

在这里插入图片描述

查看当前redis有没有设置密码:(得验证通过了才能输入的)

1
2
config get requirepass
1

在这里插入图片描述

配置文件

myredis.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1 ::1
#bind 127.0.0.1

protected-mode no
port 6379
tcp-backlog 511
requirepass 000415
timeout 0
tcp-keepalive 300
daemonize no
supervised no
pidfile /var/run/redis_6379.pid
loglevel notice
logfile ""
databases 30
always-show-logo yes
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ./
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-disable-tcp-nodelay no
replica-priority 100
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
appendonly yes
appendfilename "appendonly.aof"
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
slowlog-max-len 128
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes

nacos安装

1
2
3
4
5
6
7
mkdir -p /home/txy/cloud/docker/nacos/data/
mkdir -p /home/txy/cloud/docker/nacos/logs/
mkdir -p /home/txy/cloud/docker/nacos/init.d/

vim /usr/local/docker/nacos/init.d/custom.properties

docker run -dit --name mynacos -p 8948:8848 --privileged=true -e JVM_XMS=256m -e JVM_XMX=256m -e MODE=standalone -e PREFER_HOST_MODE=hostname -v /usr/local/docker/nacos/logs:/home/nacos/logs -v /usr/local/docker/nacos/data:/home/nacos/data -v /usr/local/docker/nacos/init.d/custom.properties:/home/nacos/init.d/custom.properties nacos/nacos-server

custom.properties内容

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
server.contextPath=/nacos
server.servlet.contextPath=/nacos
server.port=8848 // 端口号

spring.datasource.platform=mysql

// mysql连接
db.num=1
db.url.0=jdbc:mysql://192.168.136.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=root


nacos.cmdb.dumpTaskInterval=3600
nacos.cmdb.eventTaskInterval=10
nacos.cmdb.labelTaskInterval=300
nacos.cmdb.loadDataAtStart=false

management.metrics.export.elastic.enabled=false

management.metrics.export.influx.enabled=false


server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i


nacos.security.ignore.urls=/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/**
nacos.naming.distro.taskDispatchThreadCount=1
nacos.naming.distro.taskDispatchPeriod=200
nacos.naming.distro.batchSyncKeyCount=1000
nacos.naming.distro.initDataRatio=0.9
nacos.naming.distro.syncRetryDelay=5000
nacos.naming.data.warmup=true
nacos.naming.expireInstance=true

Docker界面管理工具

1
2
docker run -d  -p 9000:9000 --name portainer --restart=always --privileged=true -v /home/txy/cloud/docker/dockerGUI/docker.sock:/var/run/docker.sock -v /home/txy/cloud/docker/dockerGUI/portainer_data:/data portainer/portainer

本地部署

问题一

1
2
java: java.lang.ExceptionInInitializerError
com.sun.tools.javac.code.TypeTags

方案一:在对应的依赖下加上版本号

方案二:lombok的版本太低,更换高版本即可

问题二

1
java: 程序包javax.validation不存在

起因

SpringBoot 2.3.0版本之后就没有引入validation对应的包

解决方法

导入Spring Boot Starter Validation,附上Maven Repository的链接

根据自己项目所使用的版本号导入

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Docker中的nacos本地连接不上。

  1. 查看Linux中的端口是否开放。
  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
1. 根据容器名称查询容器ID并删除
# 第一种写法
docker stop `docker ps -a| grep oes-live-manage | awk '{print $1}' `
docker rm `docker ps -a| grep oes-live-manage | awk '{print $1}' `

# 第二种写法
docker stop `docker ps -aq --filter name=oes-live-manage`
docker rm `docker ps -aq --filter name=oes-live-manage`


2. 根据镜像名称查询容器ID并删除
# 第一种写法
docker stop `docker ps -a| grep ygsama/test-project:1.0.2 | awk '{print $1}' `
docker rm `docker ps -a| grep ygsama/test-project:1.0.2 | awk '{print $1}' `

# 第二种写法--filter,reference 后跟镜像名称
docker stop `docker ps -aq --filter oes-live-manage:1.0.2`
docker rm `docker ps -aq --filter oes-live-manage:1.0.2`

3. 根据镜像名称查询镜像ID并删除
docker images -q --filter reference=oes-live-manage*:*
docker image rm `docker images -q --filter reference=oes-live-manage*:*`


注意:grep后面填写匹配的数据

BUG

前端启动

npm : 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

后端启动

Error creating bean with name 'scopedTarget.configVO'

出现原因:nacos中的中文编码有问题,将乱码更改即可。

[http-nio-8088-exec-1] ERROR o.a.c.c.C.[.[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: com/sun/jna/platform/win32/VersionHelpers] with root cause

数据库

tag_classification表

列名 实体属性类型 备注
id Long primary key
name String 标签分类名称
oj String 标签分类所属oj
gmt_modifie datetime 修改时间
gmt_create datetime 创建时间
rank int 标签分类优先级 越小越高

tag表:

列名 实体属性类型 备注
id Long primary key
name String 标签名字
color String 标签颜色
oj String 标签所属oj
gid ilong 外键 group id
tcid long 外键 tag_classification id
gmt_modifie datetime 修改时间
gmt_create datetime 创建时间
rank int 标签分类优先级 越小越高

group表:

列名 实体属性类型 备注
id long primary id
avatar String 头像地址
name String 团队名称
short_name String 团队简称,创建题目时题目前缀
brief String 团队简介
description String 团队介绍
owner String 团队拥有者用户名
uid String 外键 团队拥有者id
auth int 0为Public,1为Protected,2为Private
visible Boolean 是否可见
status Boolean 是否封禁
code String 邀请码
gmt_create datetime 创建时间
gmt_modified datetime 修改时间

group_member表:

列名 实体属性类型 备注
id long primary key
gid long 外键 group_id
uid String 外键 user_id
auth int 1未审批,2拒绝,3普通成员,4团队管理员,5团队拥有者
reason String 申请理由
gmt_create datetime 创建时间
gmt_modified datetime 修改时间

后端代码

top.hcode.hoj

config

shiroConfig

开启Shiro注解相关知识:

AOP:(53条消息) 细说Spring——AOP详解(AOP概览)_spring aop看哪个_Jivan2233的博客-CSDN博客

shiro权限管理:Shiro权限管理框架(一):Shiro的基本使用 - 知乎 (zhihu.com)

判题服务

image-20230411204123601

后端接受判题请求:

  • 在线自测
  • 提交测评
  1. 进入JudgeController,判断是否拥有权限进行判题。按照路径进入方法。
  2. 在Service层调用JudgeManager的判题方法。
  3. JudgeManager中对用户提交进行条件判断,判断是否多次频繁提交
  4. 进入JudgeDispatcher将题目数据加入判题等待队列
  5. 处理队列中的判题任务
1
2
3
// 优先处理比赛的提交任务
// 其次处理普通提交的提交任务
// 最后处理在线调试的任务
  1. 进入Dispatcher,向判断机发送判题请求,获取判题的结果返回。
  2. 以上过程中一但抛出异常则表明判题失败或提交结果不正确

前端

开启Vue.js devtools调试工具

Vue前端使用的是https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.11/vue.min.js体积较小的压缩版,应该改为开发环境版https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.11/vue.js 前往vue.config.js文件修改

路由

image-20230426171025367

HOJ标志

点击之后在中文与英文之间切换。

1
2
3
4
// 调用方法
changeWebLanguage() {
this.$store.commit('changeWebLanguage', {language: this.webLanguage == 'zh-CN' ? 'en-US' : 'zh-CN'});
},

首页

跳转至首页。

路由:/home

1
2
3
4
5
6
{
path: '/',
redirect: '/home',
component: Home,
meta: {title: 'Home'}
},

首页初始化时的操作:

  1. 获取首页轮播图:URL:/api/home-carousel
  2. 获取用户最近14天的通过的题目榜单:/api/get-recent-contest
  3. 获取用户最近一周的提交统计:/api/get-recent-seven-ac-rank
  4. 获取最新的10个题目:/api/get-recent-updated-problem

题目

跳转至题目列表页。

路由: /problem

1
2
3
4
5
6
{
path: '/problem',
name: 'ProblemList',
component: ProblemLIst,
meta: {title: 'Problem'}
},

image-20230427182045837

条件搜索

训练

跳转至训练列表页。

路由:/training

1
2
3
4
5
6
{
name: 'TrainingFullProblemDetails',
path: '/training/:trainingID/problem/:problemID/full-screen',
component: Problem,
meta: {title: 'Training Problem Details', fullScreenSource: 'training'}
},

比赛

跳转至比赛列表

路由:/contest

1
2
3
4
5
6
{
name: 'ContestFullProblemDetails',
path: '/contest/:contestID/problem/:problemID/full-screen',
component: Problem,
meta: {title: 'Contest Problem Details', fullScreenSource: 'contest'}
},

测评

跳转至网页的用户测评页。

路由:/status

1
2
3
4
5
6
{
path: '/status',
name: 'SubmissionList',
component: SubmissionList,
meta: {title: 'Status'}
},

排名

排名分为ACM排名和OI排名。

路由分别为:/acm-rank,/oi-rank

1
2
3
4
5
6
7
8
9
10
11
12
{
path: '/acm-rank',
name: 'ACM Rank',
component: ACMRank,
meta: {title: 'ACM Rank'}
},
{
path: '/oi-rank',
name: 'OI Rank',
component: OIRank,
meta: {title: 'OI Rank'}
},

讨论

路由:/discussion

1

团队

路由:/group

1
2
3
4
5
6
{
path: '/discussion',
name: 'AllDiscussion',
meta: {title: 'Discussion', access: 'discussion'},
component: DiscussionList
},

关于

关于拥有简介和开发者。

路由分别为:/introduction,/developer

1
2
3
4
5
6
7
8
9
10
{
path: '/introduction',
meta: {title: 'Introduction'},
component: Introduction,
},
{
path: '/developer',
meta: {title: 'Developer'},
component: Developer,
},

未登录时

登录

绑定事件:

1
2
3
4
5
6
handleBtnClick(mode) {
this.changeModalStatus({
mode,
visible: true,
});
},

注册

绑定事件:

1
2
3
4
5
6
handleBtnClick(mode) {
this.changeModalStatus({
mode,
visible: true,
});
},

登录注册

用户点击登录触发按钮,执行hanleBtnClick(‘Login’).

image-20230418223438202

调用共享数据中的changeModalStatus方法

image-20230418223749423

在NavBar.vue组件页面的最后,由于mode的值被修改为Login,modalVisible修改为true。调用Login组件。

1
<component :is="modalStatus.mode" v-if="modalVisible"></component>

输入账号密码之后判断登录失败的次数决定登录是否需要进行人机验证。

1
2
3
4
5
6
7
8
9
watch: {
loginFailNum(newVal, oldVal) {
if (newVal >= 5) {
this.needVerify = true;
} else {
this.needVerify = false;
}
},
},

调用Login方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
api.login(formData).then(
(res) => {
this.btnLoginLoading = false;
this.changeModalStatus({visible: false});
const jwt = res.headers['authorization'];
this.$store.commit('changeUserToken', jwt);
this.$store.dispatch('setUserInfo', res.data.data);
this.$store.dispatch('incrLoginFailNum', true);
mMessage.success(this.$i18n.t('m.Welcome_Back'));
},
(_) => {
this.$store.dispatch('incrLoginFailNum', false);
this.btnLoginLoading = false;
}
);

进入axios全局请求拦截器:

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
axios.interceptors.request.use(
config => {

// NProgress.start();
// 每次发送请求之前判断vuex中是否存在token
// 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况
// 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
const token = localStorage.getItem('token')
if (config.url != '/api/login' && config.url != '/api/admin/login') {
token && (config.headers.Authorization = token);
}
let type = config.url.split("/")[2];
if (type === 'admin') { // 携带请求区别是否为admin
config.headers['Url-Type'] = type
} else {
config.headers['Url-Type'] = 'general'
}

return config;
},
error => {
// NProgress.done();
mMessage.error(error.response.data.msg);
if (!isMobile) {
Vue.prototype.$notify.error({
title: i18n.t('m.Error'),
message: error.response.data.msg,
duration: 5000,
offset: 50
});
}
return Promise.error(error);
})

发送ajax请求获取登录结果,进入全局响应拦截

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
/ 响应拦截器
axios.interceptors.response.use(
response => {
// NProgress.done();
if (response.headers['refresh-token']) { // token续约!
store.commit('changeUserToken', response.headers['authorization'])
}
if (response.data.status === 200 || response.data.status == undefined) {
return Promise.resolve(response);
} else {
mMessage.error(response.data.msg);
if (!isMobile) {
Vue.prototype.$notify.error({
title: i18n.t('m.Error'),
message: response.data.msg,
duration: 5000,
offset: 50
});
}
return Promise.reject(response);
}

},
// 服务器状态码不是200的情况
error => {
// NProgress.done();
if (error.response) {
if (error.response.headers['refresh-token']) { // token续约!!
store.commit('changeUserToken', error.response.headers['authorization'])
}
if (error.response.data instanceof Blob) { // 如果是文件操作的返回,由后续进行处理
return Promise.resolve(error.response);
}
switch (error.response.status) {
// 401: 未登录 token过期
// 未登录则跳转登录页面,并携带当前页面的路径
// 在登录成功后返回当前页面,这一步需要在登录页操作。
case 401:
if (error.response.data.msg) {
mMessage.error(error.response.data.msg);
if (!isMobile) {
Vue.prototype.$notify.error({
title: i18n.t('m.Error'),
message: error.response.data.msg,
duration: 5000,
offset: 50
});
}
}
if (error.response.config.headers['Url-Type'] === 'admin') {
router.push("/admin/login")
} else {
store.commit('changeModalStatus', {mode: 'Login', visible: true});
}
store.commit('clearUserInfoAndToken');
break;
// 403
// 无权限访问或操作的请求
case 403:
if (error.response.data.msg) {
mMessage.error(error.response.data.msg);
if (!isMobile) {
Vue.prototype.$notify.error({
title: i18n.t('m.Error'),
message: error.response.data.msg,
duration: 5000,
offset: 50
});
}
}
let isAdminApi = error.response.config.url.startsWith('/api/admin');
store.dispatch('refreshUserAuthInfo').then((res) => {
if (isAdminApi) {
router.push("/admin")
}
})
break;
// 404请求不存在
case 404:
mMessage.error(i18n.t('m.Query_error_unable_to_find_the_resource_to_request'));
break;
// 其他错误,直接抛出错误提示
default:
if (error.response.data) {
if (error.response.data.msg) {
mMessage.error(error.response.data.msg);
if (!isMobile) {
Vue.prototype.$notify.error({
title: i18n.t('m.Error'),
message: error.response.data.msg,
duration: 5000,
offset: 50
});
}
} else {
mMessage.error(i18n.t('m.Server_error_please_refresh_again'));
}
}
break;
}
return Promise.reject(error);
} else { //处理断网或请求超时,请求没响应
if (error.code == 'ECONNABORTED' || error.message.includes('timeout')) {
mMessage.error(i18n.t('m.Request_timed_out_please_try_again_later'));
} else {
mMessage.error(i18n.t('m.Network_error_abnormal_link_with_server_please_try_again_later'));
}
return Promise.reject(error);
}
}
);

支持的远程评测平台

为各个远程评测平台的链接。