Files
Scoreboard/server/passwd.go
T
AskaEth 2d207fd654 feat: 赛事项目切换 + 记住上次打开的赛事(修「导入赛事包后没数据」)
根因:服务端启动只认 -project(缺省 default),Console 从不传;导入只是把包解到
event_projs/<名字>,没有任何切换入口 → 导入的赛事永远打不开,页面看到的是空的 default。

- 服务端 -project 缺省改为空串;解析顺序:显式 flag(并写回记忆)> <dataDir>/active_project.txt
  > default;backup/passwd 口径一致;启动日志打印生效项目与来源
- GET /api/projects(admin)列出各赛事(是否当前/是否有 event.db/大小/更新时间)
- POST /api/projects/activate(admin):校验后原子写记忆文件 → 审计 → 返回
  {ok,name,restarting:true} → 触发优雅关闭;Console 对非用户发起的退出会自动拉起,
  重启后即打开新赛事(无需改 Console)。本来就是当前赛事则 restarting:false 不重启
- 管理端「导出」页新增赛事项目列表与「切换为当前赛事」;导入成功后提示「已导入为 X,
  但还不是当前赛事」并给「立即切换并重启服务端」;切换后轮询 /api/health 等它回来再刷新
- 文档:使用手册(导入≠切换、生效四步)、常见问题(导入后看不到数据)

自检:服务端 12 包全绿;前端 vue-tsc + build、node --test 66/66;console 14 文件通过。
2026-09-10 09:47:06 +08:00

79 lines
2.6 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"git.yeij.top/AskaEth/Scoreboard/server/internal/auth"
"git.yeij.top/AskaEth/Scoreboard/server/internal/config"
"git.yeij.top/AskaEth/Scoreboard/server/internal/store"
)
// runPasswd 实现 `scoreboard passwd` 子命令:重置用户密码并作废其全部会话。
// 初始密码是首次启动随机生成的,只能用日志找回;忘了就用这个命令重置。
func runPasswd(args []string) error {
fs := flag.NewFlagSet("passwd", flag.ExitOnError)
configPath := fs.String("config", "config.yaml", "配置文件路径")
dataDir := fs.String("data", "", "数据目录;非空时覆盖配置文件里的 dataDir")
// passwd 不涉及赛事项目,参数只为与 server / backup 的命令行口径一致(解析顺序、校验规则相同)。
project := fs.String("project", "", "赛事项目名;留空则用记住的上一次赛事,再没有就用 default")
username := fs.String("user", "admin", "用户名")
newUsername := fs.String("new-username", "", "同时把用户名改成这个(留空则不改)")
password := fs.String("password", "", "新密码;留空则随机生成并打印")
if err := fs.Parse(args); err != nil {
return err
}
cfg, err := config.Load(*configPath)
if err != nil {
return err
}
// Console 拉起服务端时用 -data 把数据指到安装目录之外;这里必须用同一份,
// 否则改的是安装目录下那个空库里的账号。
cfg.ApplyDataDir(*dataDir)
// 解析一次只为与另外两个命令同名同规则;passwd 不需要赛事项目名,解析结果不参与后续逻辑。
if _, _, err := resolveProjectName(cfg.DataDir, *project); err != nil {
return err
}
st, err := store.Open(cfg.DataDir)
if err != nil {
return err
}
defer func() { _ = st.Close() }()
if err := st.Migrate(); err != nil {
return err
}
pwd := *password
generated := pwd == ""
if generated {
pwd, err = auth.RandomPassword(16)
if err != nil {
return err
}
}
svc := auth.New(st.DB(), auth.Options{})
if *newUsername != "" && *newUsername != *username {
if err := svc.SetUsername(context.Background(), *username, *newUsername); err != nil {
return err
}
fmt.Printf("已将用户名 %s 改为 %s\n", *username, *newUsername)
*username = *newUsername
}
if err := svc.SetPassword(context.Background(), *username, pwd, generated); err != nil {
return err
}
if generated {
fmt.Printf("已重置用户 %s 的密码:%s\n请登录后立即修改(已要求下次登录强制改密)。\n", *username, pwd)
} else {
fmt.Printf("已重置用户 %s 的密码。\n", *username)
}
return nil
}