feat(M0): 工程骨架 — Go 后端 + Vue3 前端脚手架
- Go 工程:cmd/usernode CLI(serve/migrate/admin create/reset-password/user otp)
+ internal/{config,model,service,system,auth,cron,api,router,server,pkg,webui}
- 配置:TOML + 环境变量覆盖(USERNODE_<SEC>_<FIELD>),config.example.toml
- 数据:GORM 双驱动(SQLite/MySQL)8 表模型,migrate 子命令可跑通
- 系统层:system.Manager 接口(useradd/userdel/passwd 白名单,dev dry-run)
- HTTP:Gin 路由骨架(/api/v1 + 501 占位),healthz,slog 结构化日志,优雅启停
- 前端:Vite + Vue3 + TS + Element Plus 最小可运行(web/),go:embed 打通
- 构建:deploy/Containerfile 多阶段单二进制镜像,web/Containerfile.dev 前端 dev
镜像,Makefile(build/test/dev/web-build/image);go.mod 锁定 go 1.22
- 验证:go build/vet/test 通过;podman 镜像构建运行 healthz+embed 通过
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"ws_usernode/internal/model"
|
||||
"ws_usernode/internal/pkg"
|
||||
"ws_usernode/internal/system"
|
||||
)
|
||||
|
||||
// 用户服务错误。
|
||||
var (
|
||||
ErrUserNotFound = errors.New("service: 用户不存在")
|
||||
ErrUserExists = errors.New("service: 用户名已存在")
|
||||
)
|
||||
|
||||
// UserService 外部用户生命周期服务。
|
||||
// M0 提供查询与创建骨架;建号(useradd)、禁用/延期等系统操作 M1 接入 system.Manager。
|
||||
type UserService struct {
|
||||
db *gorm.DB
|
||||
sys system.Manager
|
||||
}
|
||||
|
||||
// NewUserService 创建用户服务。
|
||||
func NewUserService(db *gorm.DB, sys system.Manager) *UserService {
|
||||
return &UserService{db: db, sys: sys}
|
||||
}
|
||||
|
||||
// GetByUsername 按用户名查询外部用户(含或不含 ext_ 前缀均可)。
|
||||
func (s *UserService) GetByUsername(ctx context.Context, username string) (*model.User, error) {
|
||||
if !strings.HasPrefix(username, "ext_") {
|
||||
username = "ext_" + username
|
||||
}
|
||||
var u model.User
|
||||
if err := s.db.WithContext(ctx).First(&u, "username = ?", username).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// Create 创建外部用户记录并调用系统层建号。M0 阶段系统层为 dry-run。
|
||||
// username 为不含前缀的申请名,内部加 ext_ 前缀。
|
||||
func (s *UserService) Create(ctx context.Context, username, email, supervisor, purpose string, ttlSeconds int64) (*model.User, error) {
|
||||
if err := pkg.ValidateUserName(username); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := pkg.ValidateEmail(email); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
full := "ext_" + username
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.User{}).Where("username = ?", full).Count(&count).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil, ErrUserExists
|
||||
}
|
||||
u := &model.User{
|
||||
Username: full,
|
||||
Email: email,
|
||||
Supervisor: supervisor,
|
||||
Purpose: purpose,
|
||||
Status: model.UserStatusActive,
|
||||
Shell: "/bin/sh",
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Create(u).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 系统账号创建(dry-run / 真实),失败时回滚 DB 记录
|
||||
if err := s.sys.CreateUser(ctx, system.Account{Username: full}); err != nil {
|
||||
_ = s.db.WithContext(ctx).Delete(u).Error
|
||||
return nil, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
Reference in New Issue
Block a user