Compare commits
@@ -0,0 +1,21 @@
|
||||
.git
|
||||
.agents
|
||||
.codex
|
||||
node_modules
|
||||
tests
|
||||
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.docker.example
|
||||
|
||||
data/*
|
||||
!data/.gitkeep
|
||||
|
||||
*.log
|
||||
npm-debug.log*
|
||||
Dockerfile*
|
||||
compose*.yml
|
||||
compose*.yaml
|
||||
README.md
|
||||
LICENSE
|
||||
@@ -0,0 +1,22 @@
|
||||
# 首次启动前请复制为 .env.docker,并替换下面两个值。
|
||||
# TOTP_ENCRYPTION_KEY 必须至少 32 个字符,部署后不得随意更换。
|
||||
TOTP_ENCRYPTION_KEY=replace-with-a-random-secret-of-at-least-32-characters
|
||||
|
||||
INITIAL_ADMIN_USERNAME=admin
|
||||
INITIAL_ADMIN_PASSWORD=replace-with-a-strong-admin-password
|
||||
INITIAL_ADMIN_DISPLAY_NAME=系统管理员
|
||||
|
||||
# 下列公开站点信息可按需修改。
|
||||
PUBLIC_SITE_NAME=海州市教育考试中心
|
||||
PUBLIC_SITE_CODE=HZ-EDU-032
|
||||
PUBLIC_SITE_PHONE=0518-8602 3158
|
||||
PUBLIC_SITE_ADDRESS=江苏省连云港市海州区文教路 18 号
|
||||
PUBLIC_SITE_EMAIL=
|
||||
PUBLIC_SITE_HERO_EYEBROW=HAIZHOU EXAMINATION SERVICE
|
||||
PUBLIC_SITE_HERO_TITLE=一个报名号,
|
||||
PUBLIC_SITE_HERO_HIGHLIGHT=贯穿每一次考试。
|
||||
PUBLIC_SITE_HERO_DESCRIPTION=使用学校下发的报名号登录,完成密码更新和个人信息核验后,即可办理所有考试事项。
|
||||
PUBLIC_SITE_FOOTER_NOTICE=本平台展示数据仅用于系统演示
|
||||
|
||||
# 可选 Redis 缓存;容器外的 Redis 不应填写 127.0.0.1。
|
||||
# REDIS_URL=redis://redis-host:6379/0
|
||||
@@ -0,0 +1,64 @@
|
||||
name: Build and publish Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Test, build and publish
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: https://github.com/actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: https://github.com/docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: https://github.com/docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: https://github.com/docker/login-action@v3
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Generate image tags and labels
|
||||
id: metadata
|
||||
uses: https://github.com/docker/metadata-action@v5
|
||||
with:
|
||||
images: docker.io/${{ vars.DOCKERHUB_IMAGE }}
|
||||
flavor: latest=false
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ gitea.ref == 'refs/heads/master' }}
|
||||
type=sha,format=short,prefix=sha-
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Build and push image
|
||||
uses: https://github.com/docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
||||
cache-from: type=registry,ref=docker.io/${{ vars.DOCKERHUB_IMAGE }}:buildcache
|
||||
cache-to: type=registry,ref=docker.io/${{ vars.DOCKERHUB_IMAGE }}:buildcache,mode=max
|
||||
@@ -5,4 +5,5 @@ data/*.sqlite-shm
|
||||
data/*.sqlite-wal
|
||||
node_modules/
|
||||
.env
|
||||
.env.docker
|
||||
*.log
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
FROM node:22-bookworm-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev --ignore-scripts \
|
||||
&& npm cache clean --force
|
||||
|
||||
COPY --chown=node:node . .
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
HOST=0.0.0.0 \
|
||||
PORT=4173 \
|
||||
DATABASE_CLIENT=sqlite \
|
||||
SQLITE_PATH=/app/data/exam.sqlite
|
||||
|
||||
EXPOSE 4173
|
||||
VOLUME ["/app/data"]
|
||||
|
||||
USER node
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD ["node", "-e", "fetch('http://127.0.0.1:' + (process.env.PORT || '4173') + '/api/public/home').then(response => { if (!response.ok) process.exit(1); }).catch(() => process.exit(1));"]
|
||||
|
||||
CMD ["node", "server.mjs"]
|
||||
@@ -94,6 +94,59 @@ npm start
|
||||
|
||||
本地开发无需额外配置,首次运行会自动创建 `data/exam.sqlite` 和完整关系型数据库结构,但不会导入学校、考生、考试或报名测试数据。首次建库只写入系统基础配置和一个超级管理员;账号、密码和显示名可通过 `INITIAL_ADMIN_USERNAME`、`INITIAL_ADMIN_PASSWORD`、`INITIAL_ADMIN_DISPLAY_NAME` 设置。当前数据库结构版本为 v17;v16 数据库会自动增加 TOTP 字段,低于 v15 的开发库会提示重建。
|
||||
|
||||
### Docker
|
||||
|
||||
项目根目录包含生产镜像和 Docker Compose 配置。默认使用 SQLite,数据库保存在命名卷 `exam-information-data` 中,因此重建容器不会丢失数据。
|
||||
|
||||
先创建容器环境文件,并将其中的 TOTP 主密钥和初始管理员密码替换为安全随机值:
|
||||
|
||||
```powershell
|
||||
Copy-Item .env.docker.example .env.docker
|
||||
```
|
||||
|
||||
然后构建并启动:
|
||||
|
||||
```powershell
|
||||
docker compose up --build --detach
|
||||
```
|
||||
|
||||
启动完成后访问 <http://127.0.0.1:4173>。查看状态和日志可运行:
|
||||
|
||||
```powershell
|
||||
docker compose ps
|
||||
docker compose logs --follow app
|
||||
```
|
||||
|
||||
停止服务使用 `docker compose down`;该命令会保留数据库卷。只有明确需要删除全部 SQLite 数据时才使用 `docker compose down --volumes`。
|
||||
|
||||
也可以只构建镜像:
|
||||
|
||||
```powershell
|
||||
docker build --tag hengzhun-exam-system:local .
|
||||
```
|
||||
|
||||
镜像默认监听 `0.0.0.0:4173`,以非 root 用户运行,并通过 `/api/public/home` 执行健康检查。需要连接 MySQL 或 Redis 时,用运行环境变量覆盖 `DATABASE_CLIENT`、`DATABASE_URL`/`MYSQL_*`、`REDIS_URL`;此时 SQLite 数据卷可以移除。
|
||||
|
||||
#### Gitea Actions 自动发布到 Docker Hub
|
||||
|
||||
工作流位于 `.gitea/workflows/docker-publish.yml`。它会先安装依赖并运行测试,然后构建 `linux/amd64`、`linux/arm64` 双架构镜像并推送到 Docker Hub。
|
||||
|
||||
使用前需要完成以下配置:
|
||||
|
||||
1. 在 Docker Hub 创建目标仓库,并创建具有该仓库 Read & Write 权限的访问令牌。
|
||||
2. 在 Gitea 仓库的 Actions Variables 中添加 `DOCKERHUB_IMAGE`,值为不带 registry 和 tag 的完整镜像名,例如 `yourname/exam-information-system`。
|
||||
3. 在 Gitea 仓库的 Actions Secrets 中添加 `DOCKERHUB_USERNAME` 和 `DOCKERHUB_TOKEN`。前者填写 Docker Hub 用户名,后者填写访问令牌,不要填写账户密码。
|
||||
4. 确保 Gitea Actions 已启用,并且 `ubuntu-latest` Runner 能访问 Docker daemon、GitHub 和 Docker Hub。双架构构建还需要 Runner 允许 QEMU 注册步骤运行。
|
||||
|
||||
推送到 `master` 后会发布 `latest` 和 `sha-<短提交号>`;推送形如 `v1.2.3` 的 Git 标签后会发布 `1.2.3`、`1.2` 和对应的提交标签。例如:
|
||||
|
||||
```powershell
|
||||
git tag v1.2.3
|
||||
git push origin v1.2.3
|
||||
```
|
||||
|
||||
工作流也支持从 Gitea Actions 页面手动运行。构建缓存保存为同一 Docker Hub 仓库中的 `buildcache` 标签,以加快后续构建。
|
||||
|
||||
需要清空并重建空业务库时运行 `npm run reset-db`。需要测试数据时再手动运行 `npm run seed-test-data`;导入脚本会读取项目根目录的 `.env`,并根据 `DATABASE_CLIENT` 选择 SQLite 或 MySQL。它会生成 4 所学校、360 名批量考生及 360 条不同状态的报名数据,并明确不生成考场编排计划和准考证。省市区县下拉数据位于 `src/data/china-regions.mjs`,当前版本为国家地名信息库截至 2025-12-31 的三级快照,并补入和康县(653228)与和安县(653229);从新版 CSV 更新时可运行 `node scripts/build-regions.mjs <CSV路径> src/data/china-regions.mjs`。
|
||||
|
||||
## 数据库配置
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
image: hengzhun-exam-system:local
|
||||
init: true
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env.docker
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
HOST: 0.0.0.0
|
||||
PORT: 4173
|
||||
DATABASE_CLIENT: sqlite
|
||||
SQLITE_PATH: /app/data/exam.sqlite
|
||||
ports:
|
||||
- "4173:4173"
|
||||
volumes:
|
||||
- exam-information-data:/app/data
|
||||
|
||||
volumes:
|
||||
exam-information-data:
|
||||
Reference in New Issue
Block a user