Files
competition-website/app/controller/adminUser.js
qubiaobiao 459f1adf6d feat(admin): R176-R180 公示表与新闻撤回 + 管理端多管理员权限(C 层)+ 交卷冻结
- R176 成绩公示表引擎(lib/publicitySheet.js + lib/assets/publicity-template.xlsx 官方模板基底)
  与指导教师回填(lib/teacherBackfill.js);赛项名称/编号改为配置项
- R177 新闻「发布 ↔ 撤回」互斥:撤回只置 is_draft=1,不删除数据
- R178 C 层 RBAC:lib/adminPermissions.js(13 权限点 / 70 条路由规则,唯一 SoT)
  + middleware/auth.js 三段判定(默认拒绝 · is_super 单点 · token_version)
  + controller/service/adminUser.js + lib/auditLog.js + model/adminAuditLog.js
- R178 B1 交卷冻结:service.exam.assertNotSubmitted(bizCode 4006 单一判据源)
- R178 B④ 提交口径拆分:formalSubmitted / submitted / answeredCount 三字段
- R178 成绩总览补小组编号:resolveTeamLabels(与选手管理同源,含兜底与三态)
- R181 生产迁移配套:admins +7 列、admin_audit_logs(scripts/migrate_r178_admin_rbac.py)
2026-10-20 11:06:29 +08:00

119 lines
3.9 KiB
JavaScript
Raw 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.
'use strict';
/**
* 管理员账号管理控制器(R178 · C 层)
*
* 统一错误约定(与项目其它控制器一致):
* - 业务错误 = HTTP 200 + `code !== 0`(helper.error 不改 HTTP 状态)
* - service 通过 `bizCode` 表达期望错误码,这里原样翻译
* ⛔ 不要把业务错误包成 500(前端会当成系统故障,用户看到「服务器错误」)
*/
const Controller = require('egg').Controller;
const adminPermissions = require('../lib/adminPermissions');
class AdminUserController extends Controller {
/** 把 service 抛出的 bizCode 翻译成业务响应;非业务异常交给上层 catch */
_fail(err, fallbackMessage) {
const { ctx } = this;
if (err && err.bizCode) {
ctx.body = ctx.helper.error(err.bizCode, err.bizMessage || err.message);
return;
}
ctx.logger.error(`${fallbackMessage}:`, err);
ctx.body = ctx.helper.error(500, fallbackMessage);
}
/**
* 当前登录管理员的权限信息(自助,无需业务权限)
* GET /api/admin/me
*
* 前端用途:① 渲染侧边菜单;② 权限被他人改动后,下次进页面能感知并提示。
* 顺带返回权限点目录(catalog),避免前端硬编码一份权限清单(那是第二真相源)。
*/
async me() {
const { ctx } = this;
const admin = ctx.state.currentAdmin;
if (!admin) {
ctx.body = ctx.helper.error(401, '未登录或登录状态已失效');
return;
}
ctx.body = ctx.helper.success({
id: admin.id,
username: admin.username,
isSuper: Number(admin.is_super) === 1,
permissions: admin.permissions || [],
catalog: adminPermissions.PERMISSIONS,
}, '获取当前管理员信息成功');
}
/** GET /api/admin/admin-users */
async list() {
const { ctx, service } = this;
try {
const list = await service.adminUser.list();
ctx.body = ctx.helper.success({ list, total: list.length }, '获取管理员列表成功');
} catch (err) {
this._fail(err, '获取管理员列表失败');
}
}
/** POST /api/admin/admin-users */
async create() {
const { ctx, service } = this;
try {
const result = await service.adminUser.create(ctx.request.body || {});
ctx.body = ctx.helper.success(result, '管理员账号已创建');
} catch (err) {
this._fail(err, '创建管理员失败');
}
}
/** PUT /api/admin/admin-users/:id/permissions */
async updatePermissions() {
const { ctx, service } = this;
try {
const result = await service.adminUser.updatePermissions(ctx.params.id, ctx.request.body || {});
ctx.body = ctx.helper.success(result, '权限已更新');
} catch (err) {
this._fail(err, '更新权限失败');
}
}
/** PUT /api/admin/admin-users/:id/password */
async resetPassword() {
const { ctx, service } = this;
try {
const result = await service.adminUser.resetPassword(ctx.params.id, ctx.request.body || {});
ctx.body = ctx.helper.success(result, '口令已重置,该账号需重新登录');
} catch (err) {
this._fail(err, '重置口令失败');
}
}
/** PUT /api/admin/admin-users/:id/status */
async setStatus() {
const { ctx, service } = this;
try {
const result = await service.adminUser.setStatus(ctx.params.id, ctx.request.body || {});
const msg = Number(result.status) === 1 ? '账号已启用' : '账号已禁用,其在线会话已失效';
ctx.body = ctx.helper.success(result, msg);
} catch (err) {
this._fail(err, '修改账号状态失败');
}
}
/** DELETE /api/admin/admin-users/:id */
async remove() {
const { ctx, service } = this;
try {
const result = await service.adminUser.remove(ctx.params.id);
ctx.body = ctx.helper.success(result, '管理员账号已删除');
} catch (err) {
this._fail(err, '删除管理员失败');
}
}
}
module.exports = AdminUserController;