Files
Academic-Affairs-System/README.md
T
biss 034f1eacb8 第二阶段优化已完成,重点是提高后台任务吞吐并降低 Outbox 数据库开销。
RabbitMQ 默认从单一回调通道提升为三类任务独立并行,跨类型并发能力由 1 提升到 3;每类还能独立配置 1–16 个消费者。[RabbitMqBackgroundJobs.cs (line 143)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/BackgroundJobs/RabbitMqBackgroundJobs.cs:143)
InMemory 开发模式同步改为按任务类型隔离队列,避免某类长任务堵塞其他任务。
租约恢复检查从“每发布一条执行一次”改为默认每 60 秒维护一次。
启动恢复改为数据库 NOT EXISTS 查询,不再把全部历史 Outbox 加载进内存。[BackgroundJobOutboxPublisher.cs (line 9)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/BackgroundJobs/BackgroundJobOutboxPublisher.cs:9)
完成消息默认保留 14 天,之后按每批 500 条清理,并增加对应组合索引。
/health/messaging 现在返回各状态积压量、过期租约和最老任务等待时间。[BackgroundJobMonitoringService.cs (line 16)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/BackgroundJobs/BackgroundJobMonitoringService.cs:16)
新增 Jiaowu.BackgroundJobs 运行时指标,覆盖发布量、处理量、发布耗时、处理耗时和清理量。[BackgroundJobTelemetry.cs (line 9)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/BackgroundJobs/BackgroundJobTelemetry.cs:9)
配置、Compose 和调优建议已更新。[README.md (line 257)](E:/jiaowu/README.md:257)
2026-07-26 21:05:18 +08:00

477 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 明序教务管理系统
面向普通高校的教务管理系统。后端使用 ASP.NET Core 10、EF Core 10,前端使用 Vue 3、TypeScript 和 Element Plus。
官方电子凭证支持学生自助申请和教务代签,由服务端生成成绩单与学籍状态证明 PDF,并提供唯一凭证编号、二维码公开验真、下载记录、失效和重签;生产部署应通过 `OfficialDocuments__PublicBaseUrl` 配置二维码使用的最终 HTTPS 根地址。
当前已实现系统登录与角色权限、基础数据、用户管理、教师档案、学生档案、课程库、培养方案、教学任务、排课课表、学生选课、成绩管理、考试考场、学籍异动、毕业审核、学位授予、毕业离校和首页统计。人员及课程列表支持组合筛选、服务端分页和完整增删改查;培养方案支持课程模块、专业年级版本、复制新版本、发布与旧版本归档,已发布版本可继续维护名称、学分说明和课程结构,适用专业、入学年级及版本号保持锁定;教学任务支持学期课程开设、多教师、合班、容量校验、发布与结课,公共课由校级教务负责、专业必修/专业选修/实践课下放课程所属学院管理,并支持教师按学期申报授课科目、学院审核授课资格、公共课按若干行政班合并教学班,以及在审核通过的教师池中随机均衡分配后批量生成草稿;排课支持学期作息维护、单双周与周次节次、课程可用时间、校区/教学楼/指定教室约束、不占用教室课程、教室容量、教师/行政班/教室冲突校验、自动生成、手工微调和版本化发布;教师和学生可启用个人教学日历订阅,将固定课程、考试/监考、补考及灵活课程提醒同步到支持 iCalendar 的客户端,并可重置或停用订阅地址;选课支持批次时间窗、投放范围、容量与学分上限、重复课程与课表冲突校验、退课截止时间、满员候补、顺位查询、退课后资格复核与自动递补,以及正式名单和候补队列管理;成绩管理支持分项比例、批量录入、特殊考试状态、自动总评与绩点、教师提交、学院审核、校级发布和学生成绩单;考试管理支持考试计划、场次、考场容量、监考教师、考生名单以及考场/监考/学生时间冲突校验;学籍异动支持休学、复学、退学申请,辅导员、学院、学校三级顺序审核,学生撤回,以及最终审批后自动同步学籍状态;毕业审核按入学年级匹配已发布培养方案,以正式成绩计算总学分、必修通过和未解决不及格课程,支持学院范围查看、人工复核、校级锁定发布和学生结果查询;学位授予以已发布毕业资格为来源,按正式成绩加权平均绩点生成规则结论,支持学院人工复核、校级发布锁定和学生结果查询;毕业离校支持自定义事项与责任部门,按校级、学院、辅导员角色分工办理,强制数据范围校验,学生进度查询,以及必办事项全部完成后的批次锁定。
课程库支持下载标准模板后批量导入 `.xlsx`,按课程编码新增或更新,并在整批校验失败时不写入任何课程;授课资格既支持教师申报后审核,也支持学院在本院教师范围内直接分配;学生可在“我的培养方案”中查看本人适用的已发布方案,并按已完成、在读、重修中、未通过、待完成和未修读状态核对课程与学分进度。
权限采用后端强制校验的角色与数据范围模型。多角色账号按 `All > College > Class > Self` 取最高数据范围:校级角色可访问全校数据,院系管理员限定本学院,辅导员通过稳定的账号 ID 绑定所带行政班,教师和学生限定本人及当前教学关系;前端菜单和路由限制仅作为交互辅助,不替代 API 授权。
人员档案与登录账号分开维护。新增或 Excel 导入学生、教师档案时不会自动创建账号,也不会在修改档案时同步账号。学生首次使用时可以在登录页进入“自助激活”,填写姓名、学号、学院、专业、年级和行政班;全部匹配在籍档案后自行设置密码,系统才创建 Identity 登录账号并关联学生角色。`AspNetUsers` 作为 ASP.NET Core Identity 的内部安全存储,负责密码哈希、登录锁定、角色和令牌。
## 本地开发:热更新模式
本地开发固定使用 SQLite。首次启动会自动创建空的
`src/Jiaowu.Api/data/jiaowu-dev.sqlite`,只初始化系统角色和课程分类,不再写入
演示组织、人员、课程、业务记录或内置测试账号。
```powershell
$env:SeedAdmin__UserName = 'admin'
$env:SeedAdmin__Password = '请替换为本机开发密码'
$env:SeedAdmin__DisplayName = '系统管理员'
dotnet run --project src/Jiaowu.Api
```
另开一个终端:
```powershell
Set-Location web
npm install
npm run dev
```
访问 `http://localhost:5173`。首次创建管理员后可以清除三个
`SeedAdmin__*` 环境变量;后续账号和基础数据均通过管理界面维护。
## 本地开发:单服务模式
不需要前端热更新时,可以先把 Vue 编译进 API 的 `wwwroot`
```powershell
npm --prefix web run build
dotnet run --project src/Jiaowu.Api
```
访问 `http://localhost:5255``/api` 和静态页面由同一个 ASP.NET Core 服务提供,`/base-data` 等前端路由刷新时也会回退到 `index.html`
## MySQL 8.4 生产部署
非 Development 环境只允许使用 MySQL。构建发布包与数据库配置相互独立:
`dotnet publish` 不需要数据库连接串、JWT 密钥或生产环境变量,也不会把这些配置写入发布包。
它会自动执行 `npm ci``npm run build`,并将 Vue 静态文件放入发布目录的
`wwwroot`
```powershell
dotnet publish src/Jiaowu.Api -c Release -o .artifacts/publish
```
如需在特殊流水线中跳过自动前端构建,可传入 `-p:BuildFrontendOnPublish=false`
将发布包复制到目标服务器后,把发布包中的 `.env.example` 复制为 `.env`,并填写
真实配置。应用会在启动时自动读取**可执行文件所在目录**的 `.env`
```powershell
$copyParams = @{
LiteralPath = '.artifacts\publish\.env.example'
Destination = '.artifacts\publish\.env'
}
Copy-Item @copyParams
```
`.env` 使用 `KEY=VALUE` 格式,允许空行、以 `#` 开头的注释、可选的 `export` 前缀,
以及单引号或双引号值。双引号值支持 `\n``\r``\t``\\``\"`;不执行变量
替换或命令。真实进程环境变量的优先级高于 `.env`,因此 Windows 服务、Docker、
Kubernetes 或密钥管理系统仍可覆盖文件中的值。
如需把配置文件放到其他位置,通过 `JIAOWU_ENV_FILE` 指定绝对路径;相对路径按进程
当前工作目录解析。显式指定但文件不存在、行格式错误或引号没有闭合时,应用会拒绝
启动。不要把真实 `.env` 提交到仓库或打进发布包;Linux/macOS 建议设置权限
`chmod 600 .env`Windows 应通过 ACL 只允许服务账号和管理员读取。连接串中的证书
路径必须是运行服务器上的实际路径。
### Linux systemd 服务
仓库提供 [`deploy/systemd/jiaowu.service`](deploy/systemd/jiaowu.service),适用于
使用 systemd 的 Linux 发行版。示例假定自包含发布包位于 `/opt/jiaowu`,服务使用
无登录权限的 `jiaowu` 账号。程序会自动读取 `/opt/jiaowu/.env`,因此服务单元没有再
配置 `EnvironmentFile=`
以 Debian/Ubuntu 为例,先创建账号并设置文件权限:
```bash
sudo useradd --system \
--home-dir /opt/jiaowu \
--shell /usr/sbin/nologin \
jiaowu
sudo chown -R root:jiaowu /opt/jiaowu
sudo chmod 0750 /opt/jiaowu
sudo chmod 0750 /opt/jiaowu/Jiaowu.Api
sudo chmod 0640 /opt/jiaowu/.env
```
如果账号已存在,`useradd` 会报错,可以跳过该命令。RHEL 系发行版的 `nologin` 通常
位于 `/sbin/nologin`,请按服务器实际路径调整。如果连接串使用私有 CA,还要确保
`jiaowu` 组对 `SslCa` 指向的证书文件具有读取权限。首次启动前,先停止旧实例,并以
服务账号执行迁移;仅专用空演示库需要执行第二条灌数命令:
```bash
sudo -u jiaowu /opt/jiaowu/Jiaowu.Api --migrate-only
sudo -u jiaowu /opt/jiaowu/Jiaowu.Api \
--seed-demo-data \
--confirm-production-demo-data
```
演示数据成功后,从 `/opt/jiaowu/.env` 删除临时的 `SeedAdmin__*` 配置。随后安装并
验证服务单元:
```bash
sudo install \
--owner=root \
--group=root \
--mode=0644 \
deploy/systemd/jiaowu.service \
/etc/systemd/system/jiaowu.service
sudo systemd-analyze verify /etc/systemd/system/jiaowu.service
sudo systemctl daemon-reload
sudo systemctl enable --now jiaowu.service
```
查看状态、实时日志和数据库就绪探针:
```bash
sudo systemctl status jiaowu.service --no-pager
sudo journalctl --unit=jiaowu.service --follow
curl --fail http://127.0.0.1:8080/health/ready
```
更新程序时应先 `sudo systemctl stop jiaowu.service`,替换发布文件并执行
`--migrate-only`,确认迁移成功后再运行 `sudo systemctl start jiaowu.service`
服务以失败重启策略运行,但不会在正常退出或管理员主动停止后自行拉起。
数据库应明确使用 `utf8mb4`;MySQL 8.4 的默认排序规则为
`utf8mb4_0900_ai_ci`。新建数据库时可执行:
```sql
CREATE DATABASE `jiaowu`
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
```
首次部署或版本升级时,从 `.env` 复制一份不纳入版本控制的 `.env.migrate`,只将
连接串改成具备 DDL 权限的迁移账号,然后单独执行迁移:
```powershell
$env:JIAOWU_ENV_FILE = (Resolve-Path -LiteralPath '.artifacts\publish\.env.migrate').Path
& '.artifacts\publish\Jiaowu.Api.exe' --migrate-only
```
迁移成功后删除 `.env.migrate`,清除 `JIAOWU_ENV_FILE`,应用便会读取发布目录中的
`.env`;其中应配置仅具备应用所需 DML 权限的运行账号:
```powershell
Remove-Item -LiteralPath 'Env:JIAOWU_ENV_FILE'
& '.artifacts\publish\Jiaowu.Api.exe'
```
`Database:ApplyMigrationsOnStartup` 默认关闭。普通启动会检查待执行迁移并在架构落后时
直接失败,避免多实例同时执行 DDL。只有明确接受启动期 DDL 风险的单实例部署才应将
`Database__ApplyMigrationsOnStartup` 设为 `true`
生产环境不会创建默认管理员。首次部署可以临时配置
`SeedAdmin__UserName``SeedAdmin__Password``SeedAdmin__DisplayName`
账号创建后立即移除这些配置。
### 独立的生产演示环境
如需验证 Production 配置和 MySQL 8.4 部署链路,请新建专用的空数据库(例如
`jiaowu_demo`),不要向准备承载真实业务的数据库插入演示数据。先按前述步骤执行
`--migrate-only`,停止该环境的应用实例,再从 `.env.example` 复制并编辑
`.env.demo`,配置演示数据库、运行账号和临时管理员:
```powershell
$env:JIAOWU_ENV_FILE = (Resolve-Path -LiteralPath '.artifacts\publish\.env.demo').Path
& '.artifacts\publish\Jiaowu.Api.exe' --seed-demo-data --confirm-production-demo-data
Remove-Item -LiteralPath 'Env:JIAOWU_ENV_FILE'
```
该命令仅允许在非 Development 环境运行,且必须同时提供确认参数。它会再次检查迁移
状态,只在没有校区、学院、专业、班级、师生、课程、学期、教室和教学任务等业务数据
的空库中写入;检测到任何已有业务数据都会直接终止。写入完成后命令退出,不会启动
Web 服务,也不会在以后启动时自动补写或重复写入。
演示数据包含校区、学期、教学楼与教室、16 个学院/教学单位、52 个专业、104 个行政班、
128 名教师、3,640 名学生、148 门课程和授课资格,不包含内置通用密码或学生/教师登录
账号。演示数据 DML 在一个事务中提交,失败会回滚;迁移 DDL 必须保持为前置独立步骤,
因为 MySQL 8.4 的 DDL 会触发隐式提交。参见
[MySQL 8.4 事务说明](https://dev.mysql.com/doc/refman/8.4/en/commit.html)和
[隐式提交语句清单](https://dev.mysql.com/doc/refman/8.4/en/implicit-commit.html)。
生产数据库使用 MySQL 专用 EF Core 迁移。部署前先恢复仓库工具并检查迁移:
```powershell
dotnet tool restore
dotnet ef migrations list --no-connect --project src/Jiaowu.Api --startup-project src/Jiaowu.Api
```
不要使用当前提供程序生成的 `dotnet ef migrations script --idempotent` 作为 MySQL
部署脚本;其条件块不是 MySQL 8.4 可直接执行的语法。应使用上述
`--migrate-only` 入口,或在明确知道目标迁移状态时生成非幂等脚本并先做备份。
MySQL 的 DDL 会隐式提交,迁移不能依赖外层事务整体回滚。
SQLite 只用于本地开发:新库通过 `EnsureCreated` 建立,已有开发库通过轻量、版本化的
本地升级脚本补齐结构,不需要手动删除数据文件。SQLite 文件不能用于生产。
服务探针:
- `/health/live`:只检查进程存活。
- `/health``/health/ready`:实际检查数据库连接,失败时返回 HTTP 503。
- `/health/cache`:检查可选 Redis;未配置 Redis 时返回 `disabled`Redis
故障不会影响数据库就绪探针。
- `/health/messaging`:检查后台任务传输;单机内存队列返回 `memory`,启用
RabbitMQ 时实际检查代理连接,同时返回各 Outbox 状态的积压数、过期租约数和
最老未完成任务的等待时间。
### 查询缓存与 Redis
应用使用 HybridCache 统一管理进程内一级缓存和可选 Redis 二级缓存。目前缓存范围为
学生激活/基础数据选项、匿名可访问的已发布课表、仪表盘以及统计分析摘要。统计缓存键
包含有效数据范围、学院和规范化筛选条件,避免跨学院复用;统计 Excel 导出仍实时查询。
选课容量、成绩写入、考勤、审批、通知未读数、权限和后台任务状态仍直接以 MySQL 为准。
不配置 `ConnectionStrings__Redis` 时,开发和单机部署仍使用进程内缓存,不要求安装
Redis。生产环境使用 Redis 时,通过环境变量配置连接串,例如:
```text
ConnectionStrings__Redis=redis.internal:6380,user=jiaowu,password=REPLACE_ME,ssl=true,abortConnect=false
```
仪表盘和统计摘要默认在 Redis 中缓存 3 分钟、进程内缓存 30 秒,可分别通过
`Cache__AnalyticsExpirationMinutes``Cache__AnalyticsLocalExpirationSeconds`
调整。该类汇总采用短 TTL 控制数据新鲜度,不要求每个业务写入点同步清理缓存。
Redis 只作为可丢弃的查询缓存。连接失败时应用回源数据库,普通启动和
`/health/ready` 不依赖 Redis;可以单独检查 `/health/cache`。缓存键自动包含运行环境,
同一 Redis 可以安全承载 Development、Staging 和 Production,但生产环境仍建议使用
独立实例、私有网络、ACL 和 TLS。
`compose.example.yml` 包含不暴露宿主机端口的 Redis 服务,限制为 256 MB 并使用
`allkeys-lfu` 淘汰策略,不启用持久化。`compose.app.example.yml` 不创建 Redis
如需连接外部 Redis,在 `.env` 中配置上述连接串即可。
### 后台任务与 RabbitMQ
自动排课、课表发布和补考自动生成使用数据库 Outbox 保存任务消息。创建业务任务与
Outbox 消息在同一次 MySQL 提交中完成,后台发布器再将消息投递给任务 Worker;重复
投递通过 Outbox 处理租约和唯一任务键抑制。任务状态表仍是前端查询进度与错误信息的
唯一来源。
开发和单机部署默认使用有界进程内队列,不需要 RabbitMQ:
```text
BackgroundJobs__Transport=InMemory
```
多实例生产部署应切换为 RabbitMQ,并配置独立账号、虚拟主机和 TLS:
```text
BackgroundJobs__Transport=RabbitMq
BackgroundJobs__AutomaticScheduleConcurrency=1
BackgroundJobs__SchedulePublishConcurrency=1
BackgroundJobs__MakeupExamAutoConcurrency=1
RabbitMq__HostName=rabbitmq.example.edu.cn
RabbitMq__Port=5671
RabbitMq__UserName=jiaowu
RabbitMq__Password=REPLACE_WITH_A_STRONG_PASSWORD
RabbitMq__VirtualHost=/jiaowu
RabbitMq__UseTls=true
RabbitMq__TlsServerName=rabbitmq.example.edu.cn
```
RabbitMQ 传输使用持久消息、发布确认、手动消费确认、每种任务独立队列和死信队列。
默认创建 Quorum Queue,重任务的消费者预取数为 1。三类任务各自至少有一个消费者,
因此不同类型的任务不会再互相阻塞;单类任务的并发度可独立设置为 1-16。自动排课
通常最消耗 CPU,建议先保持为 1,再根据 CPU、数据库连接池和任务等待时间逐级调到
2 或 3;不要只提高预取数。
Outbox 租约恢复改为按维护周期执行,避免积压发布时每条消息都额外扫描数据库;已完成
消息默认保留 14 天并按每批 500 条清理,可使用 `CompletedRetentionDays`
`MaintenanceIntervalSeconds``CleanupBatchSize` 调整。应用暴露
`Jiaowu.BackgroundJobs` Meter,其中包含发布量、处理量、发布耗时、处理耗时和清理量,
可接入现有 OpenTelemetry/运行时指标采集器。MySQL 或 RabbitMQ 暂时不可用时,未完成
消息会根据 Outbox 状态和租约继续补投。迁移服务应先应用
`BackgroundJobOutbox` 数据库迁移,再启动应用实例。
## 跨平台发布与 Docker
`.gitea/workflows/publish.yml` 只在推送 `v*` 标签或手动运行时执行,普通分支 push
不会触发耗时发布。默认生成以下自包含程序包,目标服务器无需另装 .NET:
- Windows x64`.zip`
- Linux x64、Linux ARM64`.tar.gz`
- `SHA256SUMS`:所有压缩包的 SHA-256 校验值
手动运行时启用 `include_extended_platforms`,还会生成 Windows ARM64、macOS x64
和 macOS ARM64。Windows 使用 `Jiaowu.Api.exe` 启动,Linux/macOS 使用
`./Jiaowu.Api`;各压缩包都包含 `.env.example`
工作流同时使用 Buildx 构建 `linux/amd64``linux/arm64` 镜像并推送至 Gitea
Container Registry
```text
git.biss.click/biss/academic-affairs-system
```
仓库的 Actions 权限必须允许内置 `GITEA_TOKEN` 写入 Packages 和 Releases。版本标签
会创建 Gitea Release;手动运行只保留工作流产物并推送
`manual-<run-number>``sha-<commit>` 镜像标签。
### 简单 Compose:连接外部 MySQL
已有独立 MySQL 8.4 时,使用
[`compose.app.example.yml`](compose.app.example.yml)。该文件只有一个 `app` 服务,
不会创建 MySQL 容器或数据库卷。先将 `.env.example` 复制为 `.env`,填写外部数据库、
JWT、域名和跨域配置:
```powershell
Copy-Item -LiteralPath '.env.example' -Destination '.env'
```
`.env` 中的 MySQL 主机必须是**容器可以访问的地址**,不能把宿主机数据库写成
`localhost`。Docker Desktop 可按实际环境使用 `host.docker.internal`;远程数据库应
填写其 DNS 名称。使用私有 CA 时,把证书放到 `certs/mysql-ca.pem`,并取消 Compose
文件底部的只读挂载配置注释。
本地构建、迁移和启动:
```powershell
$composeFile = 'compose.app.example.yml'
$buildArgs = @('--file', $composeFile, 'build', 'app')
& docker compose @buildArgs
if ($LASTEXITCODE -ne 0) { throw 'Docker 镜像构建失败。' }
$migrateArgs = @('--file', $composeFile, 'run', '--rm', 'app', '--migrate-only')
& docker compose @migrateArgs
if ($LASTEXITCODE -ne 0) { throw '数据库迁移失败。' }
$upArgs = @('--file', $composeFile, 'up', '--detach', 'app')
& docker compose @upArgs
if ($LASTEXITCODE -ne 0) { throw '应用启动失败。' }
```
Linux/macOS 使用相同的 Compose 文件:
```bash
cp .env.example .env
docker compose -f compose.app.example.yml build app
docker compose -f compose.app.example.yml run --rm app --migrate-only
docker compose -f compose.app.example.yml up -d app
```
如使用 Gitea 已发布镜像,可在 `.env` 末尾添加
`JIAOWU_IMAGE=git.biss.click/biss/academic-affairs-system:1.0.0`,然后跳过 `build`
宿主机端口可通过 `JIAOWU_PORT` 调整,默认是 8080。
只有连接到专用空演示数据库时,才执行:
```powershell
$demoArgs = @(
'--file', 'compose.app.example.yml'
'run', '--rm', 'app'
'--seed-demo-data', '--confirm-production-demo-data'
)
& docker compose @demoArgs
if ($LASTEXITCODE -ne 0) { throw '演示数据写入失败。' }
```
该命令不会启动长期运行的 Web 容器。演示数据写入成功后删除 `.env` 中临时使用的
`SeedAdmin__*` 配置,再执行正常的 `up --detach app`
### 一体化 ComposeMySQL 8.4 演示环境
仓库提供跨 Windows、Linux 和 macOS Docker Desktop 使用的
[`compose.example.yml`](compose.example.yml)。它包含 MySQL 8.4、一次性数据库迁移、
Web 服务和一个默认关闭的演示数据工具服务。数据库数据保存在命名卷中,MySQL 端口不
暴露到宿主机;容器日志默认轮转为 3 个 10 MB 文件。
先复制并编辑配置。PowerShell
```powershell
Copy-Item -LiteralPath '.env.docker.example' -Destination '.env.docker'
```
Linux/macOS
```bash
cp .env.docker.example .env.docker
```
`.env.docker` 中所有密码和 JWT 空值填成独立随机值;`JWT_KEY` 至少使用 64 个
随机十六进制字符。该文件已被
`.gitignore` 排除,不要提交;Compose 通过 `--env-file` 在运行时插值并把应用需要的
配置传入容器,镜像构建过程不需要也不会得到这些值。
如需在当前机器构建镜像,保留 `JIAOWU_IMAGE=jiaowu:local` 并执行:
```powershell
$composeArgs = @(
'--file', 'compose.example.yml'
'--env-file', '.env.docker'
'build', 'app'
)
& docker compose @composeArgs
if ($LASTEXITCODE -ne 0) { throw 'Docker 镜像构建失败。' }
```
也可以把 `JIAOWU_IMAGE` 改成工作流发布的明确版本标签,例如
`git.biss.click/biss/academic-affairs-system:1.0.0`。正常启动不会写入演示数据:
```powershell
$composeArgs = @(
'--file', 'compose.example.yml'
'--env-file', '.env.docker'
'up', '--detach', 'app'
)
& docker compose @composeArgs
if ($LASTEXITCODE -ne 0) { throw 'Compose 启动失败。' }
```
Compose 会先等待 MySQL 健康,再执行 `migrate`;只有迁移成功才启动 `app`。首次需要
演示数据时,应在**尚未启动 app 的全新空库**上单独执行:
```powershell
$composeArgs = @(
'--file', 'compose.example.yml'
'--env-file', '.env.docker'
'run', '--rm', 'demo-data'
)
& docker compose @composeArgs
if ($LASTEXITCODE -ne 0) { throw '演示数据写入失败。' }
```
`demo-data` 使用 `tools` profile,因此普通 `docker compose up` 不会执行它;显式指定
该服务时 Compose 会自动启用其 profile,并启动 MySQL、执行迁移后再写入数据。应用自身
还会强制检查确认参数和空业务库,检测到已有业务数据会拒绝写入。成功后,从
`.env.docker` 删除三个 `SEED_ADMIN_*` 配置,再启动 `app`
Linux/macOS 的 Compose 参数完全相同,可直接运行:
```bash
docker compose -f compose.example.yml --env-file .env.docker build app
docker compose -f compose.example.yml --env-file .env.docker run --rm demo-data
docker compose -f compose.example.yml --env-file .env.docker up -d app
docker compose -f compose.example.yml --env-file .env.docker logs --follow app
```
示例中应用与 MySQL 位于 Compose 私有网络,所以内部连接使用
`SslMode=Disabled`,MySQL 未映射宿主机端口。这适合独立演示环境;正式生产连接外部
MySQL 时仍应使用前文的 `SslMode=VerifyFull` 和 CA,并拆分具备 DDL 权限的迁移账号与
仅具备业务 DML 权限的应用账号。官方 MySQL 镜像通过 `MYSQL_USER` 创建的账号会获得
示例数据库的全部权限,因此这个一体化 Compose 不能替代生产环境的最小权限设计。
Docker 镜像不包含 `.env`、数据库密码或 JWT 密钥。应用容器以非 root 用户运行并监听
8080 端口;生产环境仍应由反向代理负责 HTTPS、访问日志和请求大小限制。
## 验证
```powershell
dotnet test Jiaowu.slnx
npm --prefix web run build
```