package data import ( "context" "errors" "fmt" "strings" "time" "kra/internal/biz" "gorm.io/gorm" ) type dictionaryPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` Name string Type string `gorm:"uniqueIndex"` Status bool Desc string ParentID *uint } func (dictionaryPO) TableName() string { return "sys_dictionaries" } type dictionaryDetailPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` Label string Value string Extend string Status bool Sort int DictionaryID uint `gorm:"column:sys_dictionary_id;index"` ParentID *uint Level int Path string } func (dictionaryDetailPO) TableName() string { return "sys_dictionary_details" } type parameterPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` Name string Key string `gorm:"uniqueIndex"` Value string Desc string } func (parameterPO) TableName() string { return "sys_params" } type apiTokenPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` UserID uint AuthorityID uint Token string `gorm:"type:text;uniqueIndex"` Status bool ExpiresAt time.Time Remark string } func (apiTokenPO) TableName() string { return "sys_api_tokens" } type securityConfigPO struct { ID uint `gorm:"primaryKey"` CaptchaOpen int CaptchaTimeout int KeyLong int ImgWidth int ImgHeight int PwdMinLength int PwdRequireUpper bool PwdRequireLower bool PwdRequireDigit bool PwdRequireSpecial bool LimitEnable bool LimitWindow int LimitCount int LockEnable bool LockThreshold int LockDuration int PwdExpireEnable bool PwdExpireDays int ForceNewUserChangePassword bool } func (securityConfigPO) TableName() string { return "sys_security_config" } type settingsRepo struct{ data *Data } func NewSettingsRepo(data *Data) biz.SettingsRepo { return &settingsRepo{data: data} } func dictionaryFromPO(po dictionaryPO) *biz.Dictionary { return &biz.Dictionary{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Type: po.Type, Status: po.Status, Desc: po.Desc, ParentID: po.ParentID} } func detailFromPO(po dictionaryDetailPO) *biz.DictionaryDetail { return &biz.DictionaryDetail{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Label: po.Label, Value: po.Value, Extend: po.Extend, Status: po.Status, Sort: po.Sort, DictionaryID: po.DictionaryID, ParentID: po.ParentID, Level: po.Level, Path: po.Path} } func parameterFromPO(po parameterPO) *biz.SystemParameter { return &biz.SystemParameter{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Key: po.Key, Value: po.Value, Desc: po.Desc} } func (r *settingsRepo) CreateDictionary(ctx context.Context, v *biz.Dictionary) error { po := dictionaryPO{Name: v.Name, Type: v.Type, Status: v.Status, Desc: v.Desc, ParentID: v.ParentID} if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil { return err } v.ID = po.ID return nil } func (r *settingsRepo) UpdateDictionary(ctx context.Context, v *biz.Dictionary) error { return r.data.gormDB.WithContext(ctx).Model(&dictionaryPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "type": v.Type, "status": v.Status, "desc": v.Desc, "parent_id": v.ParentID}).Error } func (r *settingsRepo) DeleteDictionary(ctx context.Context, id uint) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var count int64 if err := tx.Model(&dictionaryPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("存在子字典不可删除") } if err := tx.Where("sys_dictionary_id = ?", id).Delete(&dictionaryDetailPO{}).Error; err != nil { return err } return tx.Delete(&dictionaryPO{}, id).Error }) } func (r *settingsRepo) FindDictionary(ctx context.Context, id uint, typ string, details bool) (*biz.Dictionary, error) { var po dictionaryPO db := r.data.gormDB.WithContext(ctx) var err error if id != 0 { err = db.First(&po, id).Error } else { err = db.Where("type = ?", typ).First(&po).Error } if err != nil { return nil, err } out := dictionaryFromPO(po) if details { out.Details, _, err = r.ListDictionaryDetails(ctx, 1, 10000, out.ID, "", "") } return out, err } func (r *settingsRepo) ListDictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]*biz.Dictionary, int64, error) { if page < 1 { page = 1 } if size < 1 { size = 10 } db := r.data.gormDB.WithContext(ctx).Model(&dictionaryPO{}) if name != "" { db = db.Where("name LIKE ?", "%"+name+"%") } if typ != "" { db = db.Where("type LIKE ?", "%"+typ+"%") } var total int64 if err := db.Count(&total).Error; err != nil { return nil, 0, err } var pos []dictionaryPO if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil { return nil, 0, err } out := make([]*biz.Dictionary, 0, len(pos)) for _, po := range pos { v := dictionaryFromPO(po) if details { v.Details, _, _ = r.ListDictionaryDetails(ctx, 1, 10000, v.ID, "", "") } out = append(out, v) } return out, total, nil } func (r *settingsRepo) CreateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error { po := dictionaryDetailPO{Label: v.Label, Value: v.Value, Extend: v.Extend, Status: v.Status, Sort: v.Sort, DictionaryID: v.DictionaryID, ParentID: v.ParentID} po.Level = 0 po.Path = "" if v.ParentID != nil && *v.ParentID != 0 { var parent dictionaryDetailPO if err := r.data.gormDB.WithContext(ctx).First(&parent, *v.ParentID).Error; err != nil { return err } po.Level = parent.Level + 1 po.Path = strings.Trim(strings.Join([]string{parent.Path, fmt.Sprint(parent.ID)}, ","), ",") } if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil { return err } v.ID = po.ID v.Level = po.Level v.Path = po.Path return nil } func (r *settingsRepo) UpdateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error { var po dictionaryDetailPO if err := r.data.gormDB.WithContext(ctx).First(&po, v.ID).Error; err != nil { return err } level, path := 0, "" if v.ParentID != nil && *v.ParentID != 0 { if *v.ParentID == v.ID { return errors.New("不能将自身设为父级") } var parent dictionaryDetailPO if err := r.data.gormDB.WithContext(ctx).First(&parent, *v.ParentID).Error; err != nil { return err } if strings.Contains(","+parent.Path+",", fmt.Sprintf(",%d,", v.ID)) { return errors.New("不能移动到自己的子级") } level = parent.Level + 1 path = strings.Trim(strings.Join([]string{parent.Path, fmt.Sprint(parent.ID)}, ","), ",") } oldPrefix := strings.Trim(strings.Join([]string{po.Path, fmt.Sprint(po.ID)}, ","), ",") newPrefix := strings.Trim(strings.Join([]string{path, fmt.Sprint(po.ID)}, ","), ",") return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Model(&dictionaryDetailPO{}).Where("id = ?", v.ID).Updates(map[string]any{"label": v.Label, "value": v.Value, "extend": v.Extend, "status": v.Status, "sort": v.Sort, "sys_dictionary_id": v.DictionaryID, "parent_id": v.ParentID, "level": level, "path": path}).Error; err != nil { return err } return tx.Model(&dictionaryDetailPO{}).Where("path = ? OR path LIKE ?", oldPrefix, oldPrefix+",%").Update("path", gorm.Expr("REPLACE(path, ?, ?)", oldPrefix, newPrefix)).Error }) } func (r *settingsRepo) DeleteDictionaryDetail(ctx context.Context, id uint) error { var count int64 if err := r.data.gormDB.WithContext(ctx).Model(&dictionaryDetailPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("存在子级不可删除") } return r.data.gormDB.WithContext(ctx).Delete(&dictionaryDetailPO{}, id).Error } func (r *settingsRepo) FindDictionaryDetail(ctx context.Context, id uint) (*biz.DictionaryDetail, error) { var po dictionaryDetailPO if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return detailFromPO(po), nil } func (r *settingsRepo) ListDictionaryDetails(ctx context.Context, page, size int, dictionaryID uint, label, value string) ([]*biz.DictionaryDetail, int64, error) { if page < 1 { page = 1 } if size < 1 { size = 10 } db := r.data.gormDB.WithContext(ctx).Model(&dictionaryDetailPO{}) if dictionaryID != 0 { db = db.Where("sys_dictionary_id = ?", dictionaryID) } if label != "" { db = db.Where("label LIKE ?", "%"+label+"%") } if value != "" { db = db.Where("value LIKE ?", "%"+value+"%") } var total int64 if err := db.Count(&total).Error; err != nil { return nil, 0, err } var pos []dictionaryDetailPO if err := db.Order("sort,id").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil { return nil, 0, err } out := make([]*biz.DictionaryDetail, 0, len(pos)) for _, po := range pos { out = append(out, detailFromPO(po)) } return out, total, nil } func (r *settingsRepo) DictionaryDetailTree(ctx context.Context, dictionaryID uint, typ string) ([]*biz.DictionaryDetail, error) { if dictionaryID == 0 { dictionary, err := r.FindDictionary(ctx, 0, typ, false) if err != nil { return nil, err } dictionaryID = dictionary.ID } items, _, err := r.ListDictionaryDetails(ctx, 1, 100000, dictionaryID, "", "") if err != nil { return nil, err } byID := map[uint]*biz.DictionaryDetail{} for _, item := range items { item.Children = []*biz.DictionaryDetail{} byID[item.ID] = item } roots := []*biz.DictionaryDetail{} for _, item := range items { if item.ParentID != nil && byID[*item.ParentID] != nil { byID[*item.ParentID].Children = append(byID[*item.ParentID].Children, item) } else { roots = append(roots, item) } } return roots, nil } func (r *settingsRepo) CreateParameter(ctx context.Context, v *biz.SystemParameter) error { po := parameterPO{Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc} if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil { return err } v.ID = po.ID return nil } func (r *settingsRepo) UpdateParameter(ctx context.Context, v *biz.SystemParameter) error { return r.data.gormDB.WithContext(ctx).Model(¶meterPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "key": v.Key, "value": v.Value, "desc": v.Desc}).Error } func (r *settingsRepo) DeleteParameters(ctx context.Context, ids []uint) error { return r.data.gormDB.WithContext(ctx).Delete(¶meterPO{}, ids).Error } func (r *settingsRepo) FindParameter(ctx context.Context, id uint, key string) (*biz.SystemParameter, error) { var po parameterPO db := r.data.gormDB.WithContext(ctx) var err error if id != 0 { err = db.First(&po, id).Error } else { err = db.Where("`key` = ?", key).First(&po).Error } if err != nil { return nil, err } return parameterFromPO(po), nil } func (r *settingsRepo) ListParameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]*biz.SystemParameter, int64, error) { if page < 1 { page = 1 } if size < 1 { size = 10 } db := r.data.gormDB.WithContext(ctx).Model(¶meterPO{}) if q != nil { if q.Name != "" { db = db.Where("name LIKE ?", "%"+q.Name+"%") } if q.Key != "" { db = db.Where("`key` LIKE ?", "%"+q.Key+"%") } } var total int64 if err := db.Count(&total).Error; err != nil { return nil, 0, err } var pos []parameterPO if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil { return nil, 0, err } out := make([]*biz.SystemParameter, 0, len(pos)) for _, po := range pos { out = append(out, parameterFromPO(po)) } return out, total, nil } func (r *settingsRepo) UserHasAuthority(ctx context.Context, userID, authorityID uint) (*biz.User, bool, error) { var po userPO if err := r.data.gormDB.WithContext(ctx).First(&po, userID).Error; err != nil { return nil, false, err } var count int64 err := r.data.gormDB.WithContext(ctx).Model(&userAuthorityPO{}).Where("sys_user_id = ? AND sys_authority_authority_id = ?", userID, authorityID).Count(&count).Error if err != nil { return nil, false, err } user, err := (&systemRepo{data: r.data}).toBizUser(ctx, &po) return user, count > 0 || po.AuthorityID == authorityID, err } func (r *settingsRepo) CreateAPIToken(ctx context.Context, v *biz.APIToken) error { po := apiTokenPO{UserID: v.UserID, AuthorityID: v.AuthorityID, Token: v.Token, Status: v.Status, ExpiresAt: v.ExpiresAt, Remark: v.Remark} if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil { return err } v.ID = po.ID v.CreatedAt = po.CreatedAt return nil } func (r *settingsRepo) ListAPITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]*biz.APIToken, int64, error) { if page < 1 { page = 1 } if size < 1 { size = 10 } db := r.data.gormDB.WithContext(ctx).Model(&apiTokenPO{}) if userID != 0 { db = db.Where("user_id = ?", userID) } if status != nil { db = db.Where("status = ?", *status) } var total int64 if err := db.Count(&total).Error; err != nil { return nil, 0, err } var pos []apiTokenPO if err := db.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil { return nil, 0, err } out := make([]*biz.APIToken, 0, len(pos)) for _, po := range pos { v := &biz.APIToken{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, UserID: po.UserID, AuthorityID: po.AuthorityID, Token: po.Token, Status: po.Status, ExpiresAt: po.ExpiresAt, Remark: po.Remark} var userPO userPO if r.data.gormDB.WithContext(ctx).First(&userPO, po.UserID).Error == nil { v.User, _ = (&systemRepo{data: r.data}).toBizUser(ctx, &userPO) } out = append(out, v) } return out, total, nil } func (r *settingsRepo) DisableAPIToken(ctx context.Context, id uint) (string, error) { var po apiTokenPO if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil { return "", err } return po.Token, r.data.gormDB.WithContext(ctx).Model(&po).Update("status", false).Error } func (r *settingsRepo) IsTokenDisabled(ctx context.Context, token string) (bool, error) { var po apiTokenPO err := r.data.gormDB.WithContext(ctx).Where("token = ?", token).First(&po).Error if errors.Is(err, gorm.ErrRecordNotFound) { return false, nil } if err != nil { return false, err } return !po.Status || time.Now().After(po.ExpiresAt), nil } func defaultSecurityConfig() securityConfigPO { return securityConfigPO{ID: 1, CaptchaTimeout: 3600, KeyLong: 6, ImgWidth: 240, ImgHeight: 80, PwdMinLength: 8, LimitWindow: 60, LimitCount: 30, LockThreshold: 5, LockDuration: 30, PwdExpireDays: 90} } func securityFromPO(v securityConfigPO) *biz.SecurityConfig { return &biz.SecurityConfig{ID: v.ID, CaptchaOpen: v.CaptchaOpen, CaptchaTimeout: v.CaptchaTimeout, KeyLong: v.KeyLong, ImgWidth: v.ImgWidth, ImgHeight: v.ImgHeight, PwdMinLength: v.PwdMinLength, PwdRequireUpper: v.PwdRequireUpper, PwdRequireLower: v.PwdRequireLower, PwdRequireDigit: v.PwdRequireDigit, PwdRequireSpecial: v.PwdRequireSpecial, LimitEnable: v.LimitEnable, LimitWindow: v.LimitWindow, LimitCount: v.LimitCount, LockEnable: v.LockEnable, LockThreshold: v.LockThreshold, LockDuration: v.LockDuration, PwdExpireEnable: v.PwdExpireEnable, PwdExpireDays: v.PwdExpireDays, ForceNewUserChangePassword: v.ForceNewUserChangePassword} } func securityToPO(v *biz.SecurityConfig) securityConfigPO { return securityConfigPO{ID: 1, CaptchaOpen: v.CaptchaOpen, CaptchaTimeout: v.CaptchaTimeout, KeyLong: v.KeyLong, ImgWidth: v.ImgWidth, ImgHeight: v.ImgHeight, PwdMinLength: v.PwdMinLength, PwdRequireUpper: v.PwdRequireUpper, PwdRequireLower: v.PwdRequireLower, PwdRequireDigit: v.PwdRequireDigit, PwdRequireSpecial: v.PwdRequireSpecial, LimitEnable: v.LimitEnable, LimitWindow: v.LimitWindow, LimitCount: v.LimitCount, LockEnable: v.LockEnable, LockThreshold: v.LockThreshold, LockDuration: v.LockDuration, PwdExpireEnable: v.PwdExpireEnable, PwdExpireDays: v.PwdExpireDays, ForceNewUserChangePassword: v.ForceNewUserChangePassword} } func (r *settingsRepo) SecurityConfig(ctx context.Context) (*biz.SecurityConfig, error) { var po securityConfigPO err := r.data.gormDB.WithContext(ctx).First(&po, 1).Error if errors.Is(err, gorm.ErrRecordNotFound) { po = defaultSecurityConfig() err = r.data.gormDB.WithContext(ctx).Create(&po).Error } if err != nil { return nil, err } return securityFromPO(po), nil } func (r *settingsRepo) SaveSecurityConfig(ctx context.Context, v *biz.SecurityConfig) error { previous, err := r.SecurityConfig(ctx) if err != nil { return err } po := securityToPO(v) return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Save(&po).Error; err != nil { return err } if v.PwdExpireEnable && !previous.PwdExpireEnable { return tx.Model(&userPO{}).Where("password_updated_at IS NULL").Update("password_updated_at", time.Now()).Error } return nil }) }