kra-new/internal/data/settings.go

486 lines
17 KiB
Go

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(&parameterPO{}).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(&parameterPO{}, 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(&parameterPO{})
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
})
}