mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
582 字
2 分钟
board-xcpcio榜单部署
2026-03-02
统计加载中...

介绍#

xcpcio
/
board
Waiting for api.github.com...
00K
0K
0K
Waiting...

最近在为自己学校的比赛部署一个 xcpcio board 以用于保存历史比赛数据,但是目前没看到任何详细的部署文档,只有 hub docker上的 xcpcio/xcpcio images

经过 1145秒 阅读dockerfile 后,发现这个images实际上是一个构建镜像,用于将 XCPCIO 源码编译成静态文件,并根据环境变量注入配置

也就是说我们只需要拉取镜像下来运行一次,获得构建的结果即可,随后通过 nginx 反向代理即可

/app/xcpcio/packages/apps/board/scripts/../dist/index.html
/app/xcpcio/packages/apps/board/scripts/../dist/sw.js
Exported /app/xcpcio/docker/../packages/apps/board/dist to /app/export

构建#

我这边由于学校内网结构比较复杂,宿舍网段是 172.17.0.0/16,而docker默认创建的也是172.17.0.0/16,所以就导致出现了宿舍无法访问机房机器

docker-compose#

在某个空白文件夹中创建 docker-compose.yml 文件即可,填入以下内容:

docker-compose.yml
services:
xcpcio-builder:
image: xcpcio/xcpcio:0.84.0
container_name: xcpcio-builder
environment:
APP: board
BOARD_DATA_HOST: "/data" # 这里
BOARD_DATA_REGION: "CN"
BOARD_DEFAULT_LANG: "zh"
BOARD_REFETCH_INTERVAL: "5000" # 榜单内拉取服务器信息频率(ms毫秒)
volumes:
- ./export:/app/export
command: primary

如果你学校和我一样是 172.17.0.0/16 则使用下面的yml文件

docker-compose.yml
services:
xcpcio-builder:
image: xcpcio/xcpcio:0.84.0
container_name: xcpcio-builder
environment:
APP: board
BOARD_DATA_HOST: "/data" #
BOARD_DATA_REGION: "CN"
BOARD_DEFAULT_LANG: "zh"
BOARD_REFETCH_INTERVAL: "5000" # 榜单内拉取服务器信息频率(ms毫秒)
volumes:
- ./export:/app/export
networks:
xcpcio-net:
ipv4_address: 172.29.0.3
command: primary
networks:
xcpcio-net:
driver: bridge
ipam:
config:
- subnet: 172.29.0.0/16
gateway: 172.29.0.1

在控制台运行 docker-compose up -d 即可在 docker-compose.yml 同一文件夹下看到 export的文件夹

docker#

这里如果你是172.17.0.0/16网段,我还是建议你使用 docker-compose.yml的方法

build.sh
# 创建输出目录
mkdir -p ./export
# 运行构建
docker run --rm \
-e APP=board \
-e BOARD_DATA_HOST="/data" \
-e BOARD_DATA_REGION="CN" \
-e BOARD_DEFAULT_LANG="zh" \
-e BOARD_REFETCH_INTERVAL="5000" \
-v $(pwd)/export:/app/export \
xcpcio/xcpcio:0.84.0 primary
# 查看生成的文件
ls -la ./export/

运行结果如图

部署上线#

这里只介绍反向代理怎么写。

我们使用 nginx 创建一个反向代理,这里的 proxy_pass 指向的是我们校内的数据服务器

location /data {
proxy_pass http://127.0.0.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
proxy_ssl_server_name off;
proxy_ssl_name $proxy_host;
}

数据服务器#

xcpcio
/
board-data
Waiting for api.github.com...
00K
0K
0K
Waiting...

xcpcio 是通过python自带的http server 来实现的数据服务器。

python \
-m http.server \
$@ \
8080

我们的自定义数据服务器必须要提供索引文件,服务器的结构如下

board-data:.
├─data
│ └─index
│ └─contest_list.json

其中 contest_list.json 为索引文件,用于显示主页目录条目的,其中包括每个比赛的数据路径

比赛路径必须是其中之一(你也可以自己去魔改代码)

const contestTypes = [
"camp",
"icpc",
"ccpc",
"provincial-contest",
];
const isNotFound = !contestTypes.some(c => route.fullPath.startsWith(`/${c}`));

contest_list.json格式#

从代码定义的类型可以知道

export function createContestIndexList(contestListJSON: any): ContestIndexList {
const contestIndexList = [] as ContestIndexList;
const dfs = (contestList: any) => {
if (Object.prototype.hasOwnProperty.call(contestList, "config")) {
contestIndexList.push(createContestIndex(contestList));
} else {
for (const k in contestList) {
dfs(contestList[k]);
}
}
};
dfs(contestListJSON);
contestIndexList.sort((a: ContestIndex, b: ContestIndex) => {
if (a.contest.startTime.isBefore(b.contest.startTime)) {
return 1;
}
if (a.contest.startTime.isAfter(b.contest.startTime)) {
return -1;
}
if (a.contest.endTime.isBefore(b.contest.endTime)) {
return 1;
}
if (a.contest.endTime.isAfter(b.contest.endTime)) {
return -1;
}
if (a.contest.name < b.contest.name) {
return 1;
}
if (a.contest.name > b.contest.name) {
return -1;
}
return 0;
});
return contestIndexList;
}

我们则可以得到一个比较简单的可用配置(实际下面的是复制contest.json里的)

{
"icpc": {
"50th": {
"xian": {
"config": {
"contest_name": "第 50 届 ICPC 国际大学生程序设计竞赛西安站",
"start_time": 1760835600000,
"end_time": 1760853600000,
"frozen_time": 3600000,
"logo": {
"preset": "ICPC"
}
},
"board_link": "/icpc/50th/xian"
}
},
},
"ccpc": {
"10th": {
"zhengzhou": {
"config": {
"contest_name": "第 11 届 CCPC 中国大学生程序设计竞赛郑州站 - 正式赛",
"start_time": 1763859600000,
"end_time": 1763877600000,
"frozen_time": 3600,
"logo": {
"preset": "CCPC"
}
},
"board_link": "/ccpc/11th/zhengzhou"
}
}
}
}

我们可用从json数据得到 board_link , 这个则是homepage点击跳转的链接,获取对应比赛也会依据这个path获得对应的

  • team.json
  • run.json
  • config.json

上面的具体格式,docs中亦有记载 link

我们的任务就是将学校oj的数据转化为符合规范的json数据即可。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

board-xcpcio榜单部署
https://blog.cutebaka.cloud/posts/xcpcio/
作者
0x0AB8
发布于
2026-03-02
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时