feat(M5): 前端完善 + 部署 — Vue3 全量页面、仪表盘统计与我的审计接口、systemd/sudoers/迁移脚本、部署文档
This commit is contained in:
@@ -77,10 +77,11 @@ func (s *UserService) GetByID(ctx context.Context, id uint) (*model.User, error)
|
||||
|
||||
// UserFilter 用户列表筛选条件。
|
||||
type UserFilter struct {
|
||||
Status string // active / disabled / expired,空为全部
|
||||
Supervisor string // 挂靠老师模糊匹配
|
||||
Page int
|
||||
PageSize int
|
||||
Status string // active / disabled / expired,空为全部
|
||||
Supervisor string // 挂靠老师模糊匹配
|
||||
ExpiresBefore *time.Time // 仅返回 expire_at 早于该时刻的用户(仪表盘"近期待过期"用)
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// List 分页查询用户(admin)。
|
||||
@@ -92,6 +93,9 @@ func (s *UserService) List(ctx context.Context, f UserFilter) ([]model.User, int
|
||||
if f.Supervisor != "" {
|
||||
q = q.Where("supervisor LIKE ?", "%"+f.Supervisor+"%")
|
||||
}
|
||||
if f.ExpiresBefore != nil {
|
||||
q = q.Where("expire_at IS NOT NULL AND expire_at < ?", f.ExpiresBefore)
|
||||
}
|
||||
var total int64
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
@@ -113,6 +117,56 @@ func (s *UserService) List(ctx context.Context, f UserFilter) ([]model.User, int
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
// UserStats 仪表盘统计(PLAN §8):各状态用户数、近 30 天待过期用户、待审批申请数。
|
||||
type UserStats struct {
|
||||
Total int64 `json:"total"`
|
||||
Active int64 `json:"active"`
|
||||
Disabled int64 `json:"disabled"`
|
||||
Expired int64 `json:"expired"`
|
||||
ExpiringSoon []model.User `json:"expiring_soon"` // 30 天内到期且仍 active
|
||||
PendingApprovals int64 `json:"pending_approvals"`
|
||||
}
|
||||
|
||||
// Stats 汇总仪表盘统计。
|
||||
func (s *UserService) Stats(ctx context.Context) (*UserStats, error) {
|
||||
st := &UserStats{}
|
||||
q := s.db.WithContext(ctx).Model(&model.User{})
|
||||
if err := q.Count(&st.Total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byStatus := map[string]int64{}
|
||||
var rows []struct {
|
||||
Status string
|
||||
C int64
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Model(&model.User{}).Select("status, count(*) as c").Group("status").Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range rows {
|
||||
byStatus[r.Status] = r.C
|
||||
}
|
||||
st.Active = byStatus[model.UserStatusActive]
|
||||
st.Disabled = byStatus[model.UserStatusDisabled]
|
||||
st.Expired = byStatus[model.UserStatusExpired]
|
||||
|
||||
// 近 30 天待过期(active 且 expire_at 在 30 天内)
|
||||
window := time.Now().Add(30 * 24 * time.Hour)
|
||||
var soon []model.User
|
||||
if err := s.db.WithContext(ctx).Model(&model.User{}).
|
||||
Where("status = ? AND expire_at IS NOT NULL AND expire_at < ?", model.UserStatusActive, window).
|
||||
Order("expire_at ASC").Limit(10).Find(&soon).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st.ExpiringSoon = soon
|
||||
|
||||
var pending int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.Approval{}).Where("status = ?", model.StatusPending).Count(&pending).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st.PendingApprovals = pending
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// Create 创建外部用户:DB 记录 + 系统账号(useradd + passwd -l)。
|
||||
// username 不含前缀;ttl 为有效期时长,0 表示用配置默认(90 天)。
|
||||
// 系统建号失败时回滚 DB 记录,保证两侧一致。
|
||||
|
||||
Reference in New Issue
Block a user