迁移
This commit is contained in:
@@ -0,0 +1,941 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
|
||||
"github.com/casbin/casbin/v3"
|
||||
casbinmodel "github.com/casbin/casbin/v3/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type apiPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Path string `gorm:"uniqueIndex:idx_api_path_method"`
|
||||
Description string
|
||||
APIGroup string `gorm:"column:api_group"`
|
||||
Method string `gorm:"uniqueIndex:idx_api_path_method"`
|
||||
}
|
||||
|
||||
func (apiPO) TableName() string { return "sys_apis" }
|
||||
|
||||
type ignoredAPIPO struct {
|
||||
Path string `gorm:"primaryKey"`
|
||||
Method string `gorm:"primaryKey"`
|
||||
}
|
||||
|
||||
func (ignoredAPIPO) TableName() string { return "sys_ignore_apis" }
|
||||
|
||||
type authorityAPIPO struct {
|
||||
AuthorityID uint `gorm:"primaryKey;column:authority_id"`
|
||||
APIID uint `gorm:"primaryKey;column:api_id"`
|
||||
}
|
||||
|
||||
func (authorityAPIPO) TableName() string { return "sys_authority_apis" }
|
||||
|
||||
type menuButtonPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
Name string
|
||||
Description string `gorm:"column:desc"`
|
||||
MenuID uint `gorm:"column:sys_base_menu_id"`
|
||||
}
|
||||
|
||||
func (menuButtonPO) TableName() string { return "sys_base_menu_btns" }
|
||||
|
||||
type authorityButtonPO struct {
|
||||
AuthorityID uint `gorm:"primaryKey;column:authority_id"`
|
||||
MenuID uint `gorm:"primaryKey;column:sys_menu_id"`
|
||||
ButtonID uint `gorm:"primaryKey;column:sys_base_menu_btn_id"`
|
||||
}
|
||||
|
||||
func (authorityButtonPO) TableName() string { return "sys_authority_btns" }
|
||||
|
||||
type departmentPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Name string `gorm:"index"`
|
||||
ParentID uint
|
||||
Ancestors string
|
||||
Sort int
|
||||
LeaderID uint
|
||||
Status bool `gorm:"default:true"`
|
||||
}
|
||||
|
||||
func (departmentPO) TableName() string { return "sys_departments" }
|
||||
|
||||
type positionPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Name string `gorm:"index"`
|
||||
Code string
|
||||
Sort int
|
||||
Status bool `gorm:"default:true"`
|
||||
Remark string
|
||||
}
|
||||
|
||||
func (positionPO) TableName() string { return "sys_positions" }
|
||||
|
||||
type userDepartmentPO struct {
|
||||
UserID uint `gorm:"primaryKey;column:sys_user_id"`
|
||||
DepartmentID uint `gorm:"primaryKey;column:sys_department_id"`
|
||||
}
|
||||
|
||||
func (userDepartmentPO) TableName() string { return "sys_user_departments" }
|
||||
|
||||
type userPositionPO struct {
|
||||
UserID uint `gorm:"primaryKey;column:sys_user_id"`
|
||||
PositionID uint `gorm:"primaryKey;column:sys_position_id"`
|
||||
}
|
||||
|
||||
func (userPositionPO) TableName() string { return "sys_user_positions" }
|
||||
|
||||
type authorityDepartmentPO struct {
|
||||
AuthorityID uint `gorm:"primaryKey;column:authority_id"`
|
||||
DepartmentID uint `gorm:"primaryKey;column:department_id"`
|
||||
}
|
||||
|
||||
func (authorityDepartmentPO) TableName() string { return "sys_authority_departments" }
|
||||
|
||||
type accessRepo struct{ data *Data }
|
||||
|
||||
func NewAccessRepo(data *Data) biz.AccessRepo { return &accessRepo{data: data} }
|
||||
|
||||
func (r *accessRepo) CreateAuthority(ctx context.Context, value *biz.Authority) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var count int64
|
||||
if err := tx.Model(&authorityPO{}).Where("authority_id = ?", value.AuthorityID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("存在相同角色id")
|
||||
}
|
||||
if value.DefaultRouter == "" {
|
||||
value.DefaultRouter = "dashboard"
|
||||
}
|
||||
if err := tx.Create(&authorityPO{AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var dashboard menuPO
|
||||
if err := tx.Where("name = ?", "dashboard").First(&dashboard).Error; err == nil {
|
||||
if err = tx.Create(&authorityMenuPO{SysAuthorityAuthorityID: value.AuthorityID, SysBaseMenuID: dashboard.ID}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defaults := []struct{ path, method string }{{"/menu/getMenu", "POST"}, {"/jwt/jsonInBlacklist", "POST"}, {"/user/changePassword", "POST"}, {"/user/setUserAuthority", "POST"}, {"/user/getUserInfo", "GET"}, {"/user/setSelfInfo", "PUT"}, {"/fileUploadAndDownload/upload", "POST"}, {"/sysDictionary/findSysDictionary", "GET"}}
|
||||
for _, item := range defaults {
|
||||
var api apiPO
|
||||
if err := tx.Where("path = ? AND method = ?", item.path, item.method).First(&api).Error; err == nil {
|
||||
if err = tx.Create(&authorityAPIPO{AuthorityID: value.AuthorityID, APIID: api.ID}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) CopyAuthority(ctx context.Context, sourceID uint, value *biz.Authority) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var count int64
|
||||
if err := tx.Model(&authorityPO{}).Where("authority_id = ?", value.AuthorityID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count != 0 {
|
||||
return errors.New("存在相同角色id")
|
||||
}
|
||||
if err := tx.Create(&authorityPO{AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
copyLinks := func(table string, destination any, columns map[string]any) error {
|
||||
return tx.Table(table).Where(columns).Find(destination).Error
|
||||
}
|
||||
var menus []authorityMenuPO
|
||||
if err := copyLinks("sys_authority_menus", &menus, map[string]any{"sys_authority_authority_id": sourceID}); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range menus {
|
||||
menus[i].SysAuthorityAuthorityID = value.AuthorityID
|
||||
}
|
||||
if len(menus) > 0 {
|
||||
if err := tx.Create(&menus).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var apis []authorityAPIPO
|
||||
if err := copyLinks("sys_authority_apis", &apis, map[string]any{"authority_id": sourceID}); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range apis {
|
||||
apis[i].AuthorityID = value.AuthorityID
|
||||
}
|
||||
if len(apis) > 0 {
|
||||
if err := tx.Create(&apis).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var buttons []authorityButtonPO
|
||||
if err := copyLinks("sys_authority_btns", &buttons, map[string]any{"authority_id": sourceID}); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range buttons {
|
||||
buttons[i].AuthorityID = value.AuthorityID
|
||||
}
|
||||
if len(buttons) > 0 {
|
||||
if err := tx.Create(&buttons).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var departments []authorityDepartmentPO
|
||||
if err := copyLinks("sys_authority_departments", &departments, map[string]any{"authority_id": sourceID}); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range departments {
|
||||
departments[i].AuthorityID = value.AuthorityID
|
||||
}
|
||||
if len(departments) > 0 {
|
||||
return tx.Create(&departments).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) UpdateAuthority(ctx context.Context, value *biz.Authority) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&authorityPO{}).Where("authority_id = ?", value.AuthorityID).Updates(map[string]any{"authority_name": value.AuthorityName, "parent_id": value.ParentID, "data_scope": value.DataScope, "default_router": value.DefaultRouter}).Error
|
||||
}
|
||||
func (r *accessRepo) DeleteAuthority(ctx context.Context, id uint) error {
|
||||
if id == 888 {
|
||||
return errors.New("不能删除超级管理员")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var users, children int64
|
||||
if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Count(&users).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if users > 0 {
|
||||
return errors.New("此角色有用户正在使用禁止删除")
|
||||
}
|
||||
if err := tx.Model(&userPO{}).Where("authority_id = ?", id).Count(&users).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if users > 0 {
|
||||
return errors.New("此角色有用户正在使用禁止删除")
|
||||
}
|
||||
if err := tx.Model(&authorityPO{}).Where("parent_id = ?", id).Count(&children).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if children > 0 {
|
||||
return errors.New("此角色存在子角色不允许删除")
|
||||
}
|
||||
if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("authority_id = ?", id).Delete(&authorityAPIPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("authority_id = ?", id).Delete(&authorityButtonPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("authority_id = ?", id).Delete(&authorityDepartmentPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Delete(&authorityPO{}, "authority_id = ?", id).Error
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
|
||||
var pos []authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Order("authority_id").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*biz.Authority, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
v := toBizAuthority(po)
|
||||
out = append(out, &v)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *accessRepo) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]authorityMenuPO, 0, len(ids))
|
||||
for _, mid := range ids {
|
||||
links = append(links, authorityMenuPO{SysAuthorityAuthorityID: id, SysBaseMenuID: mid})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
return tx.Create(&links).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) AuthorityMenuIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_base_menu_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var oldIDs []uint
|
||||
if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &oldIDs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]userAuthorityPO, 0, len(ids))
|
||||
for _, uid := range ids {
|
||||
links = append(links, userAuthorityPO{SysUserID: uid, SysAuthorityAuthorityID: id})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
if err := tx.Create(&links).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
selected := map[uint]bool{}
|
||||
for _, uid := range ids {
|
||||
selected[uid] = true
|
||||
}
|
||||
for _, uid := range oldIDs {
|
||||
if selected[uid] {
|
||||
continue
|
||||
}
|
||||
var user userPO
|
||||
if err := tx.First(&user, uid).Error; err == nil && user.AuthorityID == id {
|
||||
if err = tx.Model(&user).Update("authority_id", 888).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
fallback := userAuthorityPO{SysUserID: uid, SysAuthorityAuthorityID: 888}
|
||||
if err = tx.FirstOrCreate(&fallback, fallback).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) SetDataScope(ctx context.Context, id uint, scope int, deptIDs []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&authorityPO{}).Where("authority_id = ?", id).Update("data_scope", scope).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("authority_id = ?", id).Delete(&authorityDepartmentPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]authorityDepartmentPO, 0, len(deptIDs))
|
||||
for _, did := range deptIDs {
|
||||
links = append(links, authorityDepartmentPO{AuthorityID: id, DepartmentID: did})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
return tx.Create(&links).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityDepartmentPO{}).Where("authority_id = ?", id).Pluck("department_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) {
|
||||
if authorityID == 888 {
|
||||
return biz.DataScope{All: true}, nil
|
||||
}
|
||||
var authority authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&authority, "authority_id = ?", authorityID).Error; err != nil {
|
||||
return biz.DataScope{}, err
|
||||
}
|
||||
if authority.DataScope == 1 {
|
||||
return biz.DataScope{All: true}, nil
|
||||
}
|
||||
if authority.DataScope == 4 {
|
||||
return biz.DataScope{OwnerUserID: userID}, nil
|
||||
}
|
||||
var ids []uint
|
||||
if authority.DataScope == 5 {
|
||||
var err error
|
||||
ids, err = r.DataScopeDepartmentIDs(ctx, authorityID)
|
||||
if err != nil {
|
||||
return biz.DataScope{}, err
|
||||
}
|
||||
} else {
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_user_id = ?", userID).Pluck("sys_department_id", &ids).Error; err != nil {
|
||||
return biz.DataScope{}, err
|
||||
}
|
||||
if authority.DataScope == 2 && len(ids) > 0 {
|
||||
var departments []departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Find(&departments).Error; err != nil {
|
||||
return biz.DataScope{}, err
|
||||
}
|
||||
selected := make(map[uint]bool, len(ids))
|
||||
for _, id := range ids {
|
||||
selected[id] = true
|
||||
}
|
||||
for _, department := range departments {
|
||||
for _, part := range strings.Split(department.Ancestors, ",") {
|
||||
value, _ := strconv.ParseUint(part, 10, 64)
|
||||
if selected[uint(value)] {
|
||||
selected[department.ID] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
ids = ids[:0]
|
||||
for id := range selected {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return biz.DataScope{DepartmentIDs: ids}, nil
|
||||
}
|
||||
|
||||
func menuFromPO(po menuPO) *biz.Menu {
|
||||
return &biz.Menu{ID: po.ID, ParentID: po.ParentID, Path: po.Path, Name: po.Name, Hidden: po.Hidden, Component: po.Component, Sort: po.Sort, ActiveName: po.ActiveName, KeepAlive: po.KeepAlive, DefaultMenu: po.DefaultMenu, Title: po.Title, Icon: po.Icon, CloseTab: po.CloseTab, TransitionType: po.TransitionType}
|
||||
}
|
||||
func menuToPO(v *biz.Menu) menuPO {
|
||||
return menuPO{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.ActiveName, KeepAlive: v.KeepAlive, DefaultMenu: v.DefaultMenu, Title: v.Title, Icon: v.Icon, CloseTab: v.CloseTab, TransitionType: v.TransitionType}
|
||||
}
|
||||
func (r *accessRepo) CreateMenu(ctx context.Context, v *biz.Menu) error {
|
||||
po := menuToPO(v)
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
return nil
|
||||
}
|
||||
func (r *accessRepo) UpdateMenu(ctx context.Context, v *biz.Menu) error {
|
||||
po := menuToPO(v)
|
||||
return r.data.gormDB.WithContext(ctx).Model(&menuPO{}).Where("id = ?", v.ID).Updates(&po).Error
|
||||
}
|
||||
func (r *accessRepo) DeleteMenu(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(&menuPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("此菜单存在子菜单不可删除")
|
||||
}
|
||||
if err := tx.Where("sys_base_menu_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Delete(&menuPO{}, id).Error
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) FindMenu(ctx context.Context, id uint) (*biz.Menu, error) {
|
||||
var po menuPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return menuFromPO(po), nil
|
||||
}
|
||||
func (r *accessRepo) ListMenus(ctx context.Context) ([]*biz.Menu, error) {
|
||||
var pos []menuPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Order("sort,id").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*biz.Menu, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, menuFromPO(po))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *accessRepo) MenuRoleIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_base_menu_id = ?", id).Pluck("sys_authority_authority_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) SetMenuRoles(ctx context.Context, id uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_base_menu_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]authorityMenuPO, 0, len(ids))
|
||||
for _, aid := range ids {
|
||||
links = append(links, authorityMenuPO{SysAuthorityAuthorityID: aid, SysBaseMenuID: id})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
return tx.Create(&links).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) ReplaceMenuButtons(ctx context.Context, menuID uint, buttons []*biz.MenuButton) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var oldIDs []uint
|
||||
if err := tx.Model(&menuButtonPO{}).Where("sys_base_menu_id = ?", menuID).Pluck("id", &oldIDs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(oldIDs) > 0 {
|
||||
if err := tx.Where("sys_base_menu_btn_id IN ?", oldIDs).Delete(&authorityButtonPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tx.Where("sys_base_menu_id = ?", menuID).Delete(&menuButtonPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
pos := make([]menuButtonPO, 0, len(buttons))
|
||||
for _, button := range buttons {
|
||||
pos = append(pos, menuButtonPO{Name: button.Name, Description: button.Description, MenuID: menuID})
|
||||
}
|
||||
if len(pos) > 0 {
|
||||
return tx.Create(&pos).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func apiFromPO(po apiPO) *biz.API {
|
||||
return &biz.API{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Path: po.Path, Description: po.Description, APIGroup: po.APIGroup, Method: po.Method}
|
||||
}
|
||||
func (r *accessRepo) CreateAPI(ctx context.Context, v *biz.API) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&apiPO{Path: v.Path, Description: v.Description, APIGroup: v.APIGroup, Method: strings.ToUpper(v.Method)}).Error
|
||||
}
|
||||
func (r *accessRepo) UpdateAPI(ctx context.Context, v *biz.API) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&apiPO{}).Where("id = ?", v.ID).Updates(map[string]any{"path": v.Path, "description": v.Description, "api_group": v.APIGroup, "method": strings.ToUpper(v.Method)}).Error
|
||||
}
|
||||
func (r *accessRepo) DeleteAPIs(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("api_id IN ?", ids).Delete(&authorityAPIPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Delete(&apiPO{}, ids).Error
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) FindAPI(ctx context.Context, id uint) (*biz.API, error) {
|
||||
var po apiPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return apiFromPO(po), nil
|
||||
}
|
||||
func (r *accessRepo) ListAPIs(ctx context.Context, page, size int, q *biz.API) ([]*biz.API, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&apiPO{})
|
||||
if q != nil {
|
||||
if q.Path != "" {
|
||||
db = db.Where("path LIKE ?", "%"+q.Path+"%")
|
||||
}
|
||||
if q.APIGroup != "" {
|
||||
db = db.Where("api_group = ?", q.APIGroup)
|
||||
}
|
||||
if q.Method != "" {
|
||||
db = db.Where("method = ?", strings.ToUpper(q.Method))
|
||||
}
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []apiPO
|
||||
if err := db.Order("api_group,path,method").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.API, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, apiFromPO(po))
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *accessRepo) APIRoleIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityAPIPO{}).Where("api_id = ?", id).Pluck("authority_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) SetAPIRoles(ctx context.Context, id uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("api_id = ?", id).Delete(&authorityAPIPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]authorityAPIPO, 0, len(ids))
|
||||
for _, aid := range ids {
|
||||
links = append(links, authorityAPIPO{AuthorityID: aid, APIID: id})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
return tx.Create(&links).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) Authorize(ctx context.Context, aid uint, path, method string) (bool, error) {
|
||||
policies, err := r.PolicyPaths(ctx, aid)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
model, err := casbinmodel.NewModelFromString(`[request_definition]
|
||||
r = sub, obj, act
|
||||
[policy_definition]
|
||||
p = sub, obj, act
|
||||
[policy_effect]
|
||||
e = some(where (p.eft == allow))
|
||||
[matchers]
|
||||
m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)`)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
enforcer, err := casbin.NewEnforcer(model)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
subject := strconv.FormatUint(uint64(aid), 10)
|
||||
for _, policy := range policies {
|
||||
if _, err := enforcer.AddPolicy(subject, policy.Path, strings.ToUpper(policy.Method)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return enforcer.Enforce(subject, path, strings.ToUpper(method))
|
||||
}
|
||||
func (r *accessRepo) PolicyPaths(ctx context.Context, aid uint) ([]*biz.API, error) {
|
||||
var pos []apiPO
|
||||
err := r.data.gormDB.WithContext(ctx).Table("sys_apis").Joins("JOIN sys_authority_apis ON sys_authority_apis.api_id = sys_apis.id").Where("sys_authority_apis.authority_id = ? AND sys_apis.deleted_at IS NULL", aid).Order("sys_apis.path,sys_apis.method").Find(&pos).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*biz.API, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, apiFromPO(po))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *accessRepo) SetPolicyPaths(ctx context.Context, aid uint, paths []*biz.API) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("authority_id = ?", aid).Delete(&authorityAPIPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]authorityAPIPO, 0, len(paths))
|
||||
for _, path := range paths {
|
||||
var po apiPO
|
||||
err := tx.Where("path = ? AND method = ?", path.Path, strings.ToUpper(path.Method)).First(&po).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
po = apiPO{Path: path.Path, Method: strings.ToUpper(path.Method), Description: path.Description, APIGroup: path.APIGroup}
|
||||
if err = tx.Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
links = append(links, authorityAPIPO{AuthorityID: aid, APIID: po.ID})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
return tx.Create(&links).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *accessRepo) IgnoredAPIs(ctx context.Context) ([]*biz.API, error) {
|
||||
var pos []ignoredAPIPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Order("path,method").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*biz.API, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, &biz.API{Path: po.Path, Method: po.Method})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *accessRepo) SetAPIIgnored(ctx context.Context, path, method string, ignored bool) error {
|
||||
po := ignoredAPIPO{Path: path, Method: strings.ToUpper(method)}
|
||||
if ignored {
|
||||
return r.data.gormDB.WithContext(ctx).FirstOrCreate(&po, po).Error
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Where("path = ? AND method = ?", po.Path, po.Method).Delete(&ignoredAPIPO{}).Error
|
||||
}
|
||||
func (r *accessRepo) ApplyAPISync(ctx context.Context, added, deleted []*biz.API) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
for _, item := range added {
|
||||
po := apiPO{Path: item.Path, Method: strings.ToUpper(item.Method), Description: item.Description, APIGroup: item.APIGroup}
|
||||
if err := tx.Where("path = ? AND method = ?", po.Path, po.Method).FirstOrCreate(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, item := range deleted {
|
||||
var ids []uint
|
||||
if err := tx.Model(&apiPO{}).Where("path = ? AND method = ?", item.Path, strings.ToUpper(item.Method)).Pluck("id", &ids).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
if err := tx.Where("api_id IN ?", ids).Delete(&authorityAPIPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("id IN ?", ids).Delete(&apiPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *accessRepo) Buttons(ctx context.Context, menuID uint) ([]*biz.MenuButton, error) {
|
||||
var pos []menuButtonPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", menuID).Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*biz.MenuButton, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, &biz.MenuButton{ID: po.ID, Name: po.Name, Description: po.Description, MenuID: po.MenuID})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *accessRepo) SetAuthorityButtons(ctx context.Context, aid uint, buttons map[uint][]uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("authority_id = ?", aid).Delete(&authorityButtonPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var links []authorityButtonPO
|
||||
for menuID, ids := range buttons {
|
||||
for _, id := range ids {
|
||||
links = append(links, authorityButtonPO{AuthorityID: aid, MenuID: menuID, ButtonID: id})
|
||||
}
|
||||
}
|
||||
if len(links) > 0 {
|
||||
return tx.Create(&links).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityButtonPO{}).Where("authority_id = ? AND sys_menu_id = ?", aid, menuID).Pluck("sys_base_menu_btn_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) SetSelectedButtons(ctx context.Context, aid, menuID uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("authority_id = ? AND sys_menu_id = ?", aid, menuID).Delete(&authorityButtonPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]authorityButtonPO, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
links = append(links, authorityButtonPO{AuthorityID: aid, MenuID: menuID, ButtonID: id})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
return tx.Create(&links).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) CanRemoveButton(ctx context.Context, id uint) (bool, error) {
|
||||
var count int64
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityButtonPO{}).Where("sys_base_menu_btn_id = ?", id).Count(&count).Error
|
||||
return count == 0, err
|
||||
}
|
||||
func (r *accessRepo) AuthorityButtonIDs(ctx context.Context, aid uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityButtonPO{}).Where("authority_id = ?", aid).Pluck("sys_base_menu_btn_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
|
||||
func deptFromPO(po departmentPO) *biz.Department {
|
||||
return &biz.Department{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, ParentID: po.ParentID, Ancestors: po.Ancestors, Sort: po.Sort, LeaderID: po.LeaderID, Status: po.Status}
|
||||
}
|
||||
func (r *accessRepo) CreateDepartment(ctx context.Context, v *biz.Department) error {
|
||||
v.Ancestors = "0"
|
||||
if v.ParentID != 0 {
|
||||
var parent departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&parent, v.ParentID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.Ancestors = strings.Trim(parent.Ancestors+","+strconv.FormatUint(uint64(parent.ID), 10), ",")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Create(&departmentPO{Name: v.Name, ParentID: v.ParentID, Ancestors: v.Ancestors, Sort: v.Sort, LeaderID: v.LeaderID, Status: v.Status}).Error
|
||||
}
|
||||
func (r *accessRepo) UpdateDepartment(ctx context.Context, v *biz.Department) error {
|
||||
v.Ancestors = "0"
|
||||
if v.ParentID != 0 {
|
||||
var parent departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&parent, v.ParentID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.Ancestors = strings.Trim(parent.Ancestors+","+strconv.FormatUint(uint64(parent.ID), 10), ",")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&departmentPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "parent_id": v.ParentID, "ancestors": v.Ancestors, "sort": v.Sort, "leader_id": v.LeaderID, "status": v.Status}).Error
|
||||
}
|
||||
func (r *accessRepo) DeleteDepartment(ctx context.Context, id uint) error {
|
||||
var count int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&departmentPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("部门存在子部门")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&departmentPO{}, id).Error
|
||||
}
|
||||
func (r *accessRepo) FindDepartment(ctx context.Context, id uint) (*biz.Department, error) {
|
||||
var po departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return deptFromPO(po), nil
|
||||
}
|
||||
func (r *accessRepo) ListDepartments(ctx context.Context, name string) ([]*biz.Department, error) {
|
||||
var pos []departmentPO
|
||||
db := r.data.gormDB.WithContext(ctx).Order("sort,id")
|
||||
if name != "" {
|
||||
db = db.Where("name LIKE ?", "%"+name+"%")
|
||||
}
|
||||
if err := db.Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes := map[uint]*biz.Department{}
|
||||
for _, po := range pos {
|
||||
nodes[po.ID] = deptFromPO(po)
|
||||
}
|
||||
var roots []*biz.Department
|
||||
for _, po := range pos {
|
||||
n := nodes[po.ID]
|
||||
if p := nodes[po.ParentID]; p != nil {
|
||||
p.Children = append(p.Children, n)
|
||||
} else {
|
||||
roots = append(roots, n)
|
||||
}
|
||||
}
|
||||
return roots, nil
|
||||
}
|
||||
func (r *accessRepo) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) SetDepartmentUsers(ctx context.Context, id uint, ids []uint) error {
|
||||
return r.replaceUserDepartments(ctx, id, ids)
|
||||
}
|
||||
func (r *accessRepo) replaceUserDepartments(ctx context.Context, id uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_department_id = ?", id).Delete(&userDepartmentPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]userDepartmentPO, 0, len(ids))
|
||||
for _, uid := range ids {
|
||||
links = append(links, userDepartmentPO{UserID: uid, DepartmentID: id})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
return tx.Create(&links).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) SetUserDepartments(ctx context.Context, uid uint, ids []uint, primary uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_user_id = ?", uid).Delete(&userDepartmentPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]userDepartmentPO, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
links = append(links, userDepartmentPO{UserID: uid, DepartmentID: id})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
if err := tx.Create(&links).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if primary == 0 && len(ids) > 0 {
|
||||
primary = ids[0]
|
||||
}
|
||||
return tx.Model(&userPO{}).Where("id = ?", uid).Update("dept_id", primary).Error
|
||||
})
|
||||
}
|
||||
|
||||
func posFromPO(po positionPO) *biz.Position {
|
||||
return &biz.Position{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Code: po.Code, Sort: po.Sort, Status: po.Status, Remark: po.Remark}
|
||||
}
|
||||
func (r *accessRepo) CreatePosition(ctx context.Context, v *biz.Position) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&positionPO{Name: v.Name, Code: v.Code, Sort: v.Sort, Status: v.Status, Remark: v.Remark}).Error
|
||||
}
|
||||
func (r *accessRepo) UpdatePosition(ctx context.Context, v *biz.Position) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&positionPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "code": v.Code, "sort": v.Sort, "status": v.Status, "remark": v.Remark}).Error
|
||||
}
|
||||
func (r *accessRepo) DeletePosition(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&positionPO{}, id).Error
|
||||
}
|
||||
func (r *accessRepo) FindPosition(ctx context.Context, id uint) (*biz.Position, error) {
|
||||
var po positionPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return posFromPO(po), nil
|
||||
}
|
||||
func (r *accessRepo) ListPositions(ctx context.Context, page, size int, q *biz.Position) ([]*biz.Position, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&positionPO{})
|
||||
if q != nil {
|
||||
if q.Name != "" {
|
||||
db = db.Where("name LIKE ?", "%"+q.Name+"%")
|
||||
}
|
||||
if q.Code != "" {
|
||||
db = db.Where("code LIKE ?", "%"+q.Code+"%")
|
||||
}
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []positionPO
|
||||
if err := db.Order("sort,id").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.Position, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, posFromPO(po))
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *accessRepo) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&userPositionPO{}).Where("sys_position_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) SetPositionUsers(ctx context.Context, id uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_position_id = ?", id).Delete(&userPositionPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]userPositionPO, 0, len(ids))
|
||||
for _, uid := range ids {
|
||||
links = append(links, userPositionPO{UserID: uid, PositionID: id})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
return tx.Create(&links).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) SetUserPositions(ctx context.Context, uid uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_user_id = ?", uid).Delete(&userPositionPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]userPositionPO, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
links = append(links, userPositionPO{UserID: uid, PositionID: id})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
return tx.Create(&links).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"gorm.io/gorm"
|
||||
"io/fs"
|
||||
"kra/internal/biz"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type operationPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
IP, Method, Path string
|
||||
Status int
|
||||
LatencyMS int64 `gorm:"column:latency_ms"`
|
||||
Agent string `gorm:"type:text"`
|
||||
ErrorMessage string
|
||||
Body string `gorm:"type:text"`
|
||||
Response string `gorm:"column:resp;type:text"`
|
||||
UserID uint
|
||||
RequestID string `gorm:"index"`
|
||||
TraceID string `gorm:"index"`
|
||||
DeviceID string
|
||||
}
|
||||
|
||||
func (operationPO) TableName() string { return "sys_operation_records" }
|
||||
|
||||
type loginLogPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Username, IP string
|
||||
Status bool
|
||||
ErrorMessage, Agent string
|
||||
UserID uint
|
||||
}
|
||||
|
||||
func (loginLogPO) TableName() string { return "sys_login_logs" }
|
||||
|
||||
type dataAccessLogPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
EventType, TargetTable, Operation string
|
||||
UserID, AuthorityID uint
|
||||
Scope int
|
||||
RequestID, Method, Path, Detail string
|
||||
}
|
||||
|
||||
func (dataAccessLogPO) TableName() string { return "sys_data_access_logs" }
|
||||
|
||||
type errorRecordPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Form string `gorm:"type:text"`
|
||||
Info string `gorm:"type:text"`
|
||||
Level string
|
||||
RequestID string `gorm:"index"`
|
||||
TraceID string `gorm:"index"`
|
||||
Solution string `gorm:"type:text"`
|
||||
Status string `gorm:"default:未处理"`
|
||||
}
|
||||
|
||||
func (errorRecordPO) TableName() string { return "sys_error" }
|
||||
|
||||
type auditRepo struct{ data *Data }
|
||||
|
||||
func NewAuditRepo(data *Data) biz.AuditRepo { return &auditRepo{data: data} }
|
||||
func (r *auditRepo) RecordOperation(ctx context.Context, v *biz.OperationRecord) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&operationPO{IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID}).Error
|
||||
}
|
||||
func opFromPO(v operationPO) *biz.OperationRecord {
|
||||
return &biz.OperationRecord{ID: v.ID, CreatedAt: v.CreatedAt, IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID}
|
||||
}
|
||||
func (r *auditRepo) ListOperations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]*biz.OperationRecord, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&operationPO{})
|
||||
if q != nil {
|
||||
if q.Path != "" {
|
||||
db = db.Where("path LIKE ?", "%"+q.Path+"%")
|
||||
}
|
||||
if q.Method != "" {
|
||||
db = db.Where("method = ?", q.Method)
|
||||
}
|
||||
if q.Status != 0 {
|
||||
db = db.Where("status = ?", q.Status)
|
||||
}
|
||||
if q.IP != "" {
|
||||
db = db.Where("ip LIKE ?", "%"+q.IP+"%")
|
||||
}
|
||||
if q.UserID != 0 {
|
||||
db = db.Where("user_id = ?", q.UserID)
|
||||
}
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []operationPO
|
||||
if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.OperationRecord, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, opFromPO(po))
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *auditRepo) FindOperation(ctx context.Context, id uint) (*biz.OperationRecord, error) {
|
||||
var po operationPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return opFromPO(po), nil
|
||||
}
|
||||
func (r *auditRepo) DeleteOperations(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&operationPO{}, ids).Error
|
||||
}
|
||||
func (r *auditRepo) RecordLogin(ctx context.Context, v *biz.LoginLog) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&loginLogPO{Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID}).Error
|
||||
}
|
||||
func loginFromPO(v loginLogPO) *biz.LoginLog {
|
||||
return &biz.LoginLog{ID: v.ID, CreatedAt: v.CreatedAt, Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID}
|
||||
}
|
||||
func (r *auditRepo) ListLogins(ctx context.Context, page, size int, q *biz.LoginLog) ([]*biz.LoginLog, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&loginLogPO{})
|
||||
if q != nil {
|
||||
if q.Username != "" {
|
||||
db = db.Where("username LIKE ?", "%"+q.Username+"%")
|
||||
}
|
||||
if q.IP != "" {
|
||||
db = db.Where("ip LIKE ?", "%"+q.IP+"%")
|
||||
}
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []loginLogPO
|
||||
if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.LoginLog, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, loginFromPO(po))
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *auditRepo) FindLogin(ctx context.Context, id uint) (*biz.LoginLog, error) {
|
||||
var po loginLogPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return loginFromPO(po), nil
|
||||
}
|
||||
func (r *auditRepo) DeleteLogins(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&loginLogPO{}, ids).Error
|
||||
}
|
||||
func (r *auditRepo) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&dataAccessLogPO{EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail}).Error
|
||||
}
|
||||
func dataAccessFromPO(v dataAccessLogPO) *biz.DataAccessLog {
|
||||
return &biz.DataAccessLog{ID: v.ID, CreatedAt: v.CreatedAt, EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail}
|
||||
}
|
||||
func (r *auditRepo) ListDataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]*biz.DataAccessLog, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&dataAccessLogPO{})
|
||||
if q != nil {
|
||||
if q.EventType != "" {
|
||||
db = db.Where("event_type = ?", q.EventType)
|
||||
}
|
||||
if q.TargetTable != "" {
|
||||
db = db.Where("target_table LIKE ?", "%"+q.TargetTable+"%")
|
||||
}
|
||||
if q.UserID != 0 {
|
||||
db = db.Where("user_id = ?", q.UserID)
|
||||
}
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []dataAccessLogPO
|
||||
if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.DataAccessLog, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, dataAccessFromPO(po))
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *auditRepo) DeleteDataAccess(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&dataAccessLogPO{}, ids).Error
|
||||
}
|
||||
|
||||
func logRoot() (string, error) {
|
||||
root, err := filepath.Abs("logs")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
info, err := os.Stat(root)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return root, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return "", errors.New("日志路径不是目录")
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
func (r *auditRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate, error) {
|
||||
if parsed, err := time.Parse("2006-01", month); err != nil || parsed.Format("2006-01") != month {
|
||||
return nil, errors.New("日志月份格式不正确")
|
||||
}
|
||||
root, err := logRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries, err := os.ReadDir(root)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return []biz.LogDate{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := []biz.LogDate{}
|
||||
for _, entry := range entries {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
if entry.Type()&os.ModeSymlink != 0 || !entry.IsDir() || !strings.HasPrefix(entry.Name(), month+"-") {
|
||||
continue
|
||||
}
|
||||
if parsed, parseErr := time.Parse("2006-01-02", entry.Name()); parseErr != nil || parsed.Format("2006-01-02") != entry.Name() {
|
||||
continue
|
||||
}
|
||||
count := 0
|
||||
_ = filepath.WalkDir(filepath.Join(root, entry.Name()), func(_ string, item fs.DirEntry, walkErr error) error {
|
||||
if walkErr == nil && !item.IsDir() && strings.EqualFold(filepath.Ext(item.Name()), ".log") {
|
||||
count++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if count > 0 {
|
||||
out = append(out, biz.LogDate{Date: entry.Name(), FileCount: count})
|
||||
}
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Date < out[j].Date })
|
||||
return out, nil
|
||||
}
|
||||
func (r *auditRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, error) {
|
||||
if parsed, err := time.Parse("2006-01-02", date); err != nil || parsed.Format("2006-01-02") != date {
|
||||
return nil, errors.New("日志日期格式不正确")
|
||||
}
|
||||
root, err := logRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dateRoot := filepath.Join(root, date)
|
||||
out := []biz.LogFile{}
|
||||
err = filepath.WalkDir(dateRoot, func(path string, entry fs.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
if errors.Is(walkErr, fs.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return walkErr
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if entry.Type()&os.ModeSymlink != 0 {
|
||||
if entry.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if entry.IsDir() || !strings.EqualFold(filepath.Ext(entry.Name()), ".log") {
|
||||
return nil
|
||||
}
|
||||
info, infoErr := entry.Info()
|
||||
if infoErr != nil {
|
||||
return infoErr
|
||||
}
|
||||
relative, _ := filepath.Rel(dateRoot, path)
|
||||
out = append(out, biz.LogFile{Path: filepath.ToSlash(relative), Name: entry.Name(), Size: info.Size(), ModifiedAt: info.ModTime()})
|
||||
return nil
|
||||
})
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Path < out[j].Path })
|
||||
return out, err
|
||||
}
|
||||
func (r *auditRepo) LogContent(ctx context.Context, date, path string, cursor *int64) (*biz.LogContent, error) {
|
||||
if _, err := time.Parse("2006-01-02", date); err != nil {
|
||||
return nil, errors.New("日志日期格式不正确")
|
||||
}
|
||||
root, err := logRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dateRoot := filepath.Join(root, date)
|
||||
target := filepath.Clean(filepath.Join(dateRoot, filepath.FromSlash(path)))
|
||||
if target == dateRoot || !strings.HasPrefix(target, dateRoot+string(os.PathSeparator)) || !strings.EqualFold(filepath.Ext(target), ".log") {
|
||||
return nil, errors.New("日志文件路径不合法")
|
||||
}
|
||||
info, err := os.Lstat(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
|
||||
return nil, errors.New("日志文件路径不合法")
|
||||
}
|
||||
end := info.Size()
|
||||
if cursor != nil && *cursor >= 0 && *cursor < end {
|
||||
end = *cursor
|
||||
}
|
||||
start := end - int64(2*1024*1024)
|
||||
limited := start > 0
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
file, err := os.Open(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
data := make([]byte, end-start)
|
||||
n, err := file.ReadAt(data, start)
|
||||
if err != nil && n == 0 {
|
||||
return nil, err
|
||||
}
|
||||
data = data[:n]
|
||||
lines := bytes.Split(data, []byte("\n"))
|
||||
if len(lines) > 501 {
|
||||
drop := len(lines) - 501
|
||||
offset := 0
|
||||
for _, line := range lines[:drop] {
|
||||
offset += len(line) + 1
|
||||
}
|
||||
start += int64(offset)
|
||||
data = data[offset:]
|
||||
limited = true
|
||||
}
|
||||
return &biz.LogContent{Date: date, Path: path, Content: string(data), LineCount: bytes.Count(data, []byte("\n")), NextCursor: start, HasMore: start > 0, LimitedByBytes: limited, Size: info.Size(), ModifiedAt: info.ModTime()}, nil
|
||||
}
|
||||
func errorFromPO(v errorRecordPO) *biz.ErrorRecord {
|
||||
return &biz.ErrorRecord{ID: v.ID, CreatedAt: v.CreatedAt, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status}
|
||||
}
|
||||
func (r *auditRepo) CreateError(ctx context.Context, v *biz.ErrorRecord) error {
|
||||
if v.Status == "" {
|
||||
v.Status = "未处理"
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Create(&errorRecordPO{Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status}).Error
|
||||
}
|
||||
func (r *auditRepo) UpdateError(ctx context.Context, v *biz.ErrorRecord) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&errorRecordPO{}).Where("id = ?", v.ID).Updates(map[string]any{"form": v.Form, "info": v.Info, "level": v.Level, "solution": v.Solution, "status": v.Status}).Error
|
||||
}
|
||||
func (r *auditRepo) DeleteErrors(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&errorRecordPO{}, ids).Error
|
||||
}
|
||||
func (r *auditRepo) FindError(ctx context.Context, id uint) (*biz.ErrorRecord, error) {
|
||||
var po errorRecordPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return errorFromPO(po), nil
|
||||
}
|
||||
func (r *auditRepo) ListErrors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]*biz.ErrorRecord, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&errorRecordPO{})
|
||||
if q != nil {
|
||||
if q.Form != "" {
|
||||
db = db.Where("form LIKE ?", "%"+q.Form+"%")
|
||||
}
|
||||
if q.Level != "" {
|
||||
db = db.Where("level = ?", q.Level)
|
||||
}
|
||||
if q.Status != "" {
|
||||
db = db.Where("status = ?", q.Status)
|
||||
}
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []errorRecordPO
|
||||
if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.ErrorRecord, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, errorFromPO(po))
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type memoryCacheEntry struct {
|
||||
value string
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
type cacheStore struct {
|
||||
client *redis.Client
|
||||
mu sync.Mutex
|
||||
memory map[string]memoryCacheEntry
|
||||
}
|
||||
|
||||
func NewCache(data *Data) biz.Cache {
|
||||
return &cacheStore{client: data.redis, memory: make(map[string]memoryCacheEntry)}
|
||||
}
|
||||
|
||||
func (s *cacheStore) Get(ctx context.Context, key string) (string, bool, error) {
|
||||
if s.client != nil {
|
||||
value, err := s.client.Get(ctx, key).Result()
|
||||
if err == nil {
|
||||
return value, true, nil
|
||||
}
|
||||
if err != redis.Nil {
|
||||
return "", false, err
|
||||
}
|
||||
return "", false, nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
entry, ok := s.memory[key]
|
||||
if !ok {
|
||||
return "", false, nil
|
||||
}
|
||||
if !entry.expiresAt.IsZero() && time.Now().After(entry.expiresAt) {
|
||||
delete(s.memory, key)
|
||||
return "", false, nil
|
||||
}
|
||||
return entry.value, true, nil
|
||||
}
|
||||
|
||||
func (s *cacheStore) Set(ctx context.Context, key, value string, expiration time.Duration) error {
|
||||
if s.client != nil {
|
||||
return s.client.Set(ctx, key, value, expiration).Err()
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
entry := memoryCacheEntry{value: value}
|
||||
if expiration > 0 {
|
||||
entry.expiresAt = time.Now().Add(expiration)
|
||||
}
|
||||
s.memory[key] = entry
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *cacheStore) Delete(ctx context.Context, key string) error {
|
||||
if s.client != nil {
|
||||
return s.client.Del(ctx, key).Err()
|
||||
}
|
||||
s.mu.Lock()
|
||||
delete(s.memory, key)
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *cacheStore) Increment(ctx context.Context, key string, expiration time.Duration) (int64, error) {
|
||||
if s.client != nil {
|
||||
value, err := s.client.Incr(ctx, key).Result()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if value == 1 && expiration > 0 {
|
||||
if err := s.client.Expire(ctx, key, expiration).Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
entry, ok := s.memory[key]
|
||||
if ok && !entry.expiresAt.IsZero() && time.Now().After(entry.expiresAt) {
|
||||
ok = false
|
||||
}
|
||||
value := int64(0)
|
||||
if ok {
|
||||
value, _ = strconv.ParseInt(entry.value, 10, 64)
|
||||
}
|
||||
value++
|
||||
entry.value = strconv.FormatInt(value, 10)
|
||||
if !ok && expiration > 0 {
|
||||
entry.expiresAt = time.Now().Add(expiration)
|
||||
}
|
||||
s.memory[key] = entry
|
||||
return value, nil
|
||||
}
|
||||
+49
-7
@@ -2,29 +2,66 @@ package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/google/wire"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/data/ent"
|
||||
"kra/internal/data/ent/migrate"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
// ProviderSet is data providers.
|
||||
var ProviderSet = wire.NewSet(NewData, NewAdminRepo)
|
||||
var ProviderSet = wire.NewSet(NewData, NewAdminRepo, NewSystemRepo, NewAccessRepo, NewSettingsRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewCache, NewFileStorage)
|
||||
|
||||
// Data is a struct that contains the database client.
|
||||
type Data struct {
|
||||
db *ent.Client
|
||||
db *ent.Client
|
||||
gormDB *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// NewData creates a new Data instance.
|
||||
func NewData(c *conf.Data) (*Data, func(), error) {
|
||||
if c == nil || c.Database == nil {
|
||||
return nil, nil, fmt.Errorf("database configuration is required")
|
||||
}
|
||||
db, err := ent.Open(c.Database.Driver, c.Database.Source)
|
||||
if err != nil {
|
||||
log.Fatalf("failed opening connection to database: %v", err)
|
||||
return nil, nil, fmt.Errorf("open ent database: %w", err)
|
||||
}
|
||||
gormDB, err := gorm.Open(mysql.Open(c.Database.Source), &gorm.Config{})
|
||||
if err != nil {
|
||||
_ = db.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
var redisClient *redis.Client
|
||||
if c.Redis != nil && c.Redis.Addr != "" {
|
||||
options := &redis.Options{Addr: c.Redis.Addr}
|
||||
if c.Redis.Network != "" {
|
||||
options.Network = c.Redis.Network
|
||||
}
|
||||
if c.Redis.ReadTimeout != nil {
|
||||
options.ReadTimeout = c.Redis.ReadTimeout.AsDuration()
|
||||
}
|
||||
if c.Redis.WriteTimeout != nil {
|
||||
options.WriteTimeout = c.Redis.WriteTimeout.AsDuration()
|
||||
}
|
||||
candidate := redis.NewClient(options)
|
||||
pingCtx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond)
|
||||
if pingErr := candidate.Ping(pingCtx).Err(); pingErr != nil {
|
||||
log.Printf("redis unavailable, using in-memory cache: %v", pingErr)
|
||||
_ = candidate.Close()
|
||||
} else {
|
||||
redisClient = candidate
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
if os.Getenv("DEPLOY_ENV") == "dev" {
|
||||
// Enable debug mode for detailed logging.
|
||||
@@ -35,9 +72,14 @@ func NewData(c *conf.Data) (*Data, func(), error) {
|
||||
}
|
||||
}
|
||||
cleanup := func() {
|
||||
db.Close()
|
||||
_ = db.Close()
|
||||
if redisClient != nil {
|
||||
_ = redisClient.Close()
|
||||
}
|
||||
}
|
||||
return &Data{
|
||||
db: db,
|
||||
db: db,
|
||||
gormDB: gormDB,
|
||||
redis: redisClient,
|
||||
}, cleanup, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"kra/internal/biz"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type exportTemplatePO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
DBName string
|
||||
Name string
|
||||
DBTableName string `gorm:"column:table_name"`
|
||||
TemplateID string `gorm:"uniqueIndex"`
|
||||
TemplateInfo string `gorm:"type:text"`
|
||||
SQL string `gorm:"type:text"`
|
||||
ImportSQL string `gorm:"type:text"`
|
||||
Limit *int
|
||||
Order string
|
||||
}
|
||||
|
||||
func (exportTemplatePO) TableName() string { return "sys_export_templates" }
|
||||
|
||||
type exportConditionPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
TemplateID string `gorm:"index"`
|
||||
From string
|
||||
Column string
|
||||
Operator string
|
||||
}
|
||||
|
||||
func (exportConditionPO) TableName() string { return "sys_export_template_condition" }
|
||||
|
||||
type exportJoinPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
TemplateID string `gorm:"index"`
|
||||
Join string `gorm:"column:joins"`
|
||||
Table string
|
||||
On string `gorm:"column:on"`
|
||||
}
|
||||
|
||||
func (exportJoinPO) TableName() string { return "sys_export_template_join" }
|
||||
|
||||
type exportRepo struct{ data *Data }
|
||||
|
||||
func NewExportRepo(data *Data) biz.ExportRepo { return &exportRepo{data: data} }
|
||||
func exportFromPO(po exportTemplatePO, conditions []exportConditionPO, joins []exportJoinPO) *biz.ExportTemplate {
|
||||
v := &biz.ExportTemplate{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DBName: po.DBName, Name: po.Name, TableName: po.DBTableName, TemplateID: po.TemplateID, TemplateInfo: po.TemplateInfo, SQL: po.SQL, ImportSQL: po.ImportSQL, Limit: po.Limit, Order: po.Order}
|
||||
for _, x := range conditions {
|
||||
v.Conditions = append(v.Conditions, biz.ExportCondition{From: x.From, Column: x.Column, Operator: x.Operator})
|
||||
}
|
||||
for _, x := range joins {
|
||||
v.Joins = append(v.Joins, biz.ExportJoin{Join: x.Join, Table: x.Table, On: x.On})
|
||||
}
|
||||
return v
|
||||
}
|
||||
func exportToPO(v *biz.ExportTemplate) exportTemplatePO {
|
||||
return exportTemplatePO{ID: v.ID, DBName: v.DBName, Name: v.Name, DBTableName: v.TableName, TemplateID: v.TemplateID, TemplateInfo: v.TemplateInfo, SQL: v.SQL, ImportSQL: v.ImportSQL, Limit: v.Limit, Order: v.Order}
|
||||
}
|
||||
func (r *exportRepo) saveRelations(tx *gorm.DB, v *biz.ExportTemplate) error {
|
||||
if err := tx.Where("template_id = ?", v.TemplateID).Delete(&exportConditionPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("template_id = ?", v.TemplateID).Delete(&exportJoinPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
conditions := make([]exportConditionPO, 0, len(v.Conditions))
|
||||
for _, x := range v.Conditions {
|
||||
conditions = append(conditions, exportConditionPO{TemplateID: v.TemplateID, From: x.From, Column: x.Column, Operator: x.Operator})
|
||||
}
|
||||
if len(conditions) > 0 {
|
||||
if err := tx.Create(&conditions).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
joins := make([]exportJoinPO, 0, len(v.Joins))
|
||||
for _, x := range v.Joins {
|
||||
joins = append(joins, exportJoinPO{TemplateID: v.TemplateID, Join: x.Join, Table: x.Table, On: x.On})
|
||||
}
|
||||
if len(joins) > 0 {
|
||||
return tx.Create(&joins).Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (r *exportRepo) CreateExportTemplate(ctx context.Context, v *biz.ExportTemplate) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
po := exportToPO(v)
|
||||
if err := tx.Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
return r.saveRelations(tx, v)
|
||||
})
|
||||
}
|
||||
func (r *exportRepo) UpdateExportTemplate(ctx context.Context, v *biz.ExportTemplate) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
po := exportToPO(v)
|
||||
if err := tx.Model(&exportTemplatePO{}).Where("id = ?", v.ID).Updates(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return r.saveRelations(tx, v)
|
||||
})
|
||||
}
|
||||
func (r *exportRepo) DeleteExportTemplates(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var tids []string
|
||||
if err := tx.Model(&exportTemplatePO{}).Where("id IN ?", ids).Pluck("template_id", &tids).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(tids) > 0 {
|
||||
if err := tx.Where("template_id IN ?", tids).Delete(&exportConditionPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("template_id IN ?", tids).Delete(&exportJoinPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Delete(&exportTemplatePO{}, ids).Error
|
||||
})
|
||||
}
|
||||
func (r *exportRepo) FindExportTemplate(ctx context.Context, id uint, tid string) (*biz.ExportTemplate, error) {
|
||||
var po exportTemplatePO
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
var err error
|
||||
if id != 0 {
|
||||
err = db.First(&po, id).Error
|
||||
} else {
|
||||
err = db.Where("template_id = ?", tid).First(&po).Error
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var conditions []exportConditionPO
|
||||
var joins []exportJoinPO
|
||||
if err = db.Where("template_id = ?", po.TemplateID).Find(&conditions).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = db.Where("template_id = ?", po.TemplateID).Find(&joins).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return exportFromPO(po, conditions, joins), nil
|
||||
}
|
||||
func (r *exportRepo) ListExportTemplates(ctx context.Context, page, size int, q *biz.ExportTemplate) ([]*biz.ExportTemplate, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&exportTemplatePO{})
|
||||
if q != nil {
|
||||
if q.Name != "" {
|
||||
db = db.Where("name LIKE ?", "%"+q.Name+"%")
|
||||
}
|
||||
if q.TableName != "" {
|
||||
db = db.Where("table_name = ?", q.TableName)
|
||||
}
|
||||
if q.TemplateID != "" {
|
||||
db = db.Where("template_id = ?", q.TemplateID)
|
||||
}
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []exportTemplatePO
|
||||
if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.ExportTemplate, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
v, err := r.FindExportTemplate(ctx, po.ID, "")
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out = append(out, v)
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
|
||||
var safeIdentifier = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_.]*$`)
|
||||
var allowedOperator = map[string]bool{"=": true, "!=": true, ">": true, ">=": true, "<": true, "<=": true, "LIKE": true, "IN": true, "NOT IN": true, "BETWEEN": true}
|
||||
|
||||
func validateSelectSQL(sql string) error {
|
||||
normalized := strings.ToLower(strings.TrimSpace(sql))
|
||||
if !strings.HasPrefix(normalized, "select ") || strings.Contains(normalized, ";") {
|
||||
return errors.New("仅允许单条 SELECT 查询")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (r *exportRepo) QueryExport(ctx context.Context, t *biz.ExportTemplate, params map[string]string) ([]map[string]any, string, error) {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
var rows []map[string]any
|
||||
if t.SQL != "" {
|
||||
if err := validateSelectSQL(t.SQL); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
args := map[string]any{}
|
||||
for k, v := range params {
|
||||
args[k] = v
|
||||
}
|
||||
err := db.Raw(t.SQL, args).Scan(&rows).Error
|
||||
return rows, t.SQL, err
|
||||
}
|
||||
if !safeIdentifier.MatchString(t.TableName) {
|
||||
return nil, "", errors.New("表名不合法")
|
||||
}
|
||||
columns := make([]string, 0)
|
||||
for key := range parseTemplateColumns(t.TemplateInfo) {
|
||||
if !safeIdentifier.MatchString(key) {
|
||||
return nil, "", errors.New("列名不合法")
|
||||
}
|
||||
columns = append(columns, key)
|
||||
}
|
||||
sort.Strings(columns)
|
||||
if len(columns) == 0 {
|
||||
return nil, "", errors.New("模板列为空")
|
||||
}
|
||||
query := db.Table(t.TableName).Select(strings.Join(columns, ","))
|
||||
for _, j := range t.Joins {
|
||||
joinType := strings.ToUpper(strings.TrimSpace(j.Join))
|
||||
if joinType != "LEFT JOIN" && joinType != "RIGHT JOIN" && joinType != "INNER JOIN" && joinType != "JOIN" {
|
||||
return nil, "", errors.New("关联类型不合法")
|
||||
}
|
||||
if !safeIdentifier.MatchString(j.Table) {
|
||||
return nil, "", errors.New("关联表不合法")
|
||||
}
|
||||
query = query.Joins(joinType + " " + j.Table + " ON " + j.On)
|
||||
}
|
||||
for _, condition := range t.Conditions {
|
||||
operator := strings.ToUpper(strings.TrimSpace(condition.Operator))
|
||||
if !allowedOperator[operator] || !safeIdentifier.MatchString(condition.Column) {
|
||||
return nil, "", errors.New("查询条件不合法")
|
||||
}
|
||||
value := params[condition.From]
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
switch operator {
|
||||
case "LIKE":
|
||||
query = query.Where(condition.Column+" LIKE ?", "%"+value+"%")
|
||||
case "IN", "NOT IN":
|
||||
query = query.Where(condition.Column+" "+operator+" ?", strings.Split(value, ","))
|
||||
case "BETWEEN":
|
||||
start, end := params["start"+condition.From], params["end"+condition.From]
|
||||
if start != "" && end != "" {
|
||||
query = query.Where(condition.Column+" BETWEEN ? AND ?", start, end)
|
||||
}
|
||||
default:
|
||||
query = query.Where(condition.Column+" "+operator+" ?", value)
|
||||
}
|
||||
}
|
||||
limit := 0
|
||||
if raw := params["limit"]; raw != "" {
|
||||
limit, _ = strconv.Atoi(raw)
|
||||
} else if t.Limit != nil {
|
||||
limit = *t.Limit
|
||||
}
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit)
|
||||
}
|
||||
if t.Order != "" {
|
||||
parts := strings.Fields(t.Order)
|
||||
if len(parts) < 1 || len(parts) > 2 || !safeIdentifier.MatchString(parts[0]) || (len(parts) == 2 && strings.ToUpper(parts[1]) != "ASC" && strings.ToUpper(parts[1]) != "DESC") {
|
||||
return nil, "", errors.New("排序不合法")
|
||||
}
|
||||
query = query.Order(t.Order)
|
||||
}
|
||||
preview := query.ToSQL(func(tx *gorm.DB) *gorm.DB { return tx.Find(&[]map[string]any{}) })
|
||||
err := query.Find(&rows).Error
|
||||
return rows, preview, err
|
||||
}
|
||||
func parseTemplateColumns(raw string) map[string]string {
|
||||
out := map[string]string{}
|
||||
parts := strings.Split(strings.Trim(strings.TrimSpace(raw), "{}"), ",")
|
||||
for _, part := range parts {
|
||||
pair := strings.SplitN(part, ":", 2)
|
||||
if len(pair) == 2 {
|
||||
out[strings.Trim(strings.TrimSpace(pair[0]), `"`)] = strings.Trim(strings.TrimSpace(pair[1]), `"`)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
func (r *exportRepo) ImportExportRows(ctx context.Context, t *biz.ExportTemplate, rows []map[string]any) error {
|
||||
sql := strings.TrimSpace(t.ImportSQL)
|
||||
lower := strings.ToLower(sql)
|
||||
if sql == "" || strings.Contains(sql, ";") || (!strings.HasPrefix(lower, "insert ") && !strings.HasPrefix(lower, "update ")) {
|
||||
return errors.New("导入 SQL 仅允许单条 INSERT 或 UPDATE")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
for index, row := range rows {
|
||||
if err := tx.Exec(sql, row).Error; err != nil {
|
||||
return fmt.Errorf("第%d行导入失败: %w", index+2, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
)
|
||||
|
||||
type fileStorage struct {
|
||||
root string
|
||||
urlPrefix string
|
||||
}
|
||||
|
||||
func NewFileStorage(config *conf.AdminBackend) (biz.FileStorage, error) {
|
||||
root, prefix := "uploads/file", "uploads/file"
|
||||
if config != nil && config.Local != nil {
|
||||
if config.Local.StorePath != "" {
|
||||
root = config.Local.StorePath
|
||||
}
|
||||
if config.Local.PathPrefix != "" {
|
||||
prefix = config.Local.PathPrefix
|
||||
}
|
||||
}
|
||||
root = filepath.Clean(root)
|
||||
if err := os.MkdirAll(root, 0o755); err != nil {
|
||||
return nil, fmt.Errorf("create upload directory: %w", err)
|
||||
}
|
||||
return &fileStorage{root: root, urlPrefix: "/" + strings.Trim(prefix, "/")}, nil
|
||||
}
|
||||
|
||||
func (s *fileStorage) resolve(name string) (string, error) {
|
||||
clean := filepath.Clean(strings.TrimPrefix(name, "/"))
|
||||
if clean == "." || filepath.IsAbs(clean) || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
|
||||
return "", fmt.Errorf("invalid storage path")
|
||||
}
|
||||
return filepath.Join(s.root, clean), nil
|
||||
}
|
||||
|
||||
func (s *fileStorage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) {
|
||||
path, err := s.resolve(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size, copyErr := io.Copy(file, reader)
|
||||
closeErr := file.Close()
|
||||
if copyErr != nil {
|
||||
return nil, copyErr
|
||||
}
|
||||
if closeErr != nil {
|
||||
return nil, closeErr
|
||||
}
|
||||
return &biz.StoredFile{Name: filepath.Base(name), Path: name, URL: s.urlPrefix + "/" + strings.TrimPrefix(filepath.ToSlash(name), "/"), Size: size}, nil
|
||||
}
|
||||
|
||||
func (s *fileStorage) Open(ctx context.Context, name string) (io.ReadCloser, error) {
|
||||
path, err := s.resolve(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
func (s *fileStorage) Delete(ctx context.Context, name string) error {
|
||||
path, err := s.resolve(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *fileStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) {
|
||||
reader, writer := io.Pipe()
|
||||
hash := md5.New()
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
defer writer.Close()
|
||||
for _, name := range names {
|
||||
if ctx.Err() != nil {
|
||||
errCh <- ctx.Err()
|
||||
return
|
||||
}
|
||||
file, err := s.Open(ctx, name)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(io.MultiWriter(writer, hash), file)
|
||||
_ = file.Close()
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
errCh <- nil
|
||||
}()
|
||||
stored, err := s.Put(ctx, destination, reader)
|
||||
composeErr := <-errCh
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if composeErr != nil {
|
||||
_ = s.Delete(ctx, destination)
|
||||
return nil, "", composeErr
|
||||
}
|
||||
return stored, hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
func (s *fileStorage) DeletePrefix(ctx context.Context, prefix string) error {
|
||||
path, err := s.resolve(strings.TrimSuffix(prefix, "/") + "/placeholder")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir := filepath.Dir(path)
|
||||
if !strings.HasPrefix(dir, s.root+string(filepath.Separator)) {
|
||||
return fmt.Errorf("invalid storage path")
|
||||
}
|
||||
if err = os.RemoveAll(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *fileStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) {
|
||||
if limit < 1 || limit > 1000 {
|
||||
limit = 100
|
||||
}
|
||||
root, err := s.resolve(strings.TrimSuffix(prefix, "/") + "/placeholder")
|
||||
if err != nil {
|
||||
return nil, "", false, err
|
||||
}
|
||||
walkRoot := filepath.Dir(root)
|
||||
items := []*biz.StoredFile{}
|
||||
err = filepath.WalkDir(walkRoot, func(path string, entry os.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
if os.IsNotExist(walkErr) {
|
||||
return nil
|
||||
}
|
||||
return walkErr
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if entry.IsDir() || entry.Type()&os.ModeSymlink != 0 {
|
||||
return nil
|
||||
}
|
||||
relative, _ := filepath.Rel(s.root, path)
|
||||
relative = filepath.ToSlash(relative)
|
||||
if relative <= cursor {
|
||||
return nil
|
||||
}
|
||||
info, infoErr := entry.Info()
|
||||
if infoErr != nil {
|
||||
return infoErr
|
||||
}
|
||||
items = append(items, &biz.StoredFile{Name: entry.Name(), Path: relative, URL: s.urlPrefix + "/" + relative, Size: info.Size()})
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", false, err
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool { return items[i].Path < items[j].Path })
|
||||
hasMore := len(items) > limit
|
||||
if hasMore {
|
||||
items = items[:limit]
|
||||
}
|
||||
next := ""
|
||||
if len(items) > 0 {
|
||||
next = items[len(items)-1].Path
|
||||
}
|
||||
return items, next, hasMore, nil
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"kra/internal/biz"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mediaPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Name string
|
||||
CategoryID int `gorm:"column:class_id"`
|
||||
URL string
|
||||
Tag string
|
||||
Key string `gorm:"index"`
|
||||
Size int64
|
||||
Mime string
|
||||
MD5 string `gorm:"index;column:md5"`
|
||||
UserID uint `gorm:"index"`
|
||||
}
|
||||
|
||||
func (mediaPO) TableName() string { return "media_file_upload_and_downloads" }
|
||||
|
||||
type categoryPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Name string
|
||||
ParentID uint `gorm:"column:pid"`
|
||||
}
|
||||
|
||||
func (categoryPO) TableName() string { return "media_attachment_category" }
|
||||
|
||||
type uploadSessionPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
UserID uint `gorm:"index"`
|
||||
FileName string
|
||||
FileHash string `gorm:"index"`
|
||||
FileSize int64
|
||||
ChunkSize int64
|
||||
ChunkTotal int
|
||||
Status string
|
||||
StorageKey string
|
||||
MediaID uint
|
||||
}
|
||||
|
||||
func (uploadSessionPO) TableName() string { return "media_uploads" }
|
||||
|
||||
type uploadChunkPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
UploadID uint `gorm:"uniqueIndex:idx_upload_chunk"`
|
||||
ChunkIndex int `gorm:"uniqueIndex:idx_upload_chunk"`
|
||||
ChunkHash string
|
||||
Size int64
|
||||
}
|
||||
|
||||
func (uploadChunkPO) TableName() string { return "media_upload_chunks" }
|
||||
|
||||
type mediaRepo struct{ data *Data }
|
||||
|
||||
func NewMediaRepo(data *Data) biz.MediaRepo { return &mediaRepo{data: data} }
|
||||
func mediaFromPO(v mediaPO) *biz.MediaFile {
|
||||
return &biz.MediaFile{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, CategoryID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID}
|
||||
}
|
||||
func mediaToPO(v *biz.MediaFile) mediaPO {
|
||||
return mediaPO{ID: v.ID, Name: v.Name, CategoryID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID}
|
||||
}
|
||||
func (r *mediaRepo) CreateMedia(ctx context.Context, v *biz.MediaFile) error {
|
||||
po := mediaToPO(v)
|
||||
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 *mediaRepo) FindMedia(ctx context.Context, id uint) (*biz.MediaFile, error) {
|
||||
var po mediaPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mediaFromPO(po), nil
|
||||
}
|
||||
func (r *mediaRepo) FindMediaByHash(ctx context.Context, userID uint, hash string) (*biz.MediaFile, error) {
|
||||
var po mediaPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("user_id = ? AND md5 = ?", userID, hash).First(&po).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mediaFromPO(po), nil
|
||||
}
|
||||
func (r *mediaRepo) ListMedia(ctx context.Context, page, size int, keyword string, categoryID int, tag string, userID uint) ([]*biz.MediaFile, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&mediaPO{})
|
||||
if keyword != "" {
|
||||
db = db.Where("name LIKE ?", "%"+keyword+"%")
|
||||
}
|
||||
if categoryID > 0 {
|
||||
db = db.Where("class_id = ?", categoryID)
|
||||
}
|
||||
if tag != "" {
|
||||
db = db.Where("tag = ?", tag)
|
||||
}
|
||||
if userID > 0 {
|
||||
db = db.Where("user_id = ?", userID)
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []mediaPO
|
||||
if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.MediaFile, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, mediaFromPO(po))
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *mediaRepo) UpdateMediaName(ctx context.Context, id uint, name string) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&mediaPO{}).Where("id = ?", id).Update("name", name).Error
|
||||
}
|
||||
func (r *mediaRepo) DeleteMedia(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Unscoped().Delete(&mediaPO{}, id).Error
|
||||
}
|
||||
func (r *mediaRepo) MediaKeyReferences(ctx context.Context, key string) (int64, error) {
|
||||
var count int64
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&mediaPO{}).Where("`key` = ?", key).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
func (r *mediaRepo) CreateMediaBatch(ctx context.Context, items []*biz.MediaFile) error {
|
||||
pos := make([]mediaPO, 0, len(items))
|
||||
for _, v := range items {
|
||||
pos = append(pos, mediaToPO(v))
|
||||
}
|
||||
if len(pos) == 0 {
|
||||
return nil
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Create(&pos).Error
|
||||
}
|
||||
func (r *mediaRepo) SaveCategory(ctx context.Context, v *biz.AttachmentCategory) error {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
var count int64
|
||||
query := db.Model(&categoryPO{}).Where("name = ? AND pid = ?", v.Name, v.ParentID)
|
||||
if v.ID != 0 {
|
||||
query = query.Where("id <> ?", v.ID)
|
||||
}
|
||||
if err := query.Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("分类名称已存在")
|
||||
}
|
||||
if v.ID == 0 {
|
||||
po := categoryPO{Name: v.Name, ParentID: v.ParentID}
|
||||
if err := db.Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
return nil
|
||||
}
|
||||
return db.Model(&categoryPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "pid": v.ParentID}).Error
|
||||
}
|
||||
func (r *mediaRepo) DeleteCategory(ctx context.Context, id uint) error {
|
||||
var count int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&categoryPO{}).Where("pid = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("请先删除子级")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Unscoped().Delete(&categoryPO{}, id).Error
|
||||
}
|
||||
func (r *mediaRepo) ListCategories(ctx context.Context) ([]*biz.AttachmentCategory, error) {
|
||||
var pos []categoryPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Order("id").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byID := map[uint]*biz.AttachmentCategory{}
|
||||
for _, po := range pos {
|
||||
byID[po.ID] = &biz.AttachmentCategory{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, ParentID: po.ParentID, Children: []*biz.AttachmentCategory{}}
|
||||
}
|
||||
roots := []*biz.AttachmentCategory{}
|
||||
for _, po := range pos {
|
||||
v := byID[po.ID]
|
||||
if parent := byID[po.ParentID]; parent != nil {
|
||||
parent.Children = append(parent.Children, v)
|
||||
} else {
|
||||
roots = append(roots, v)
|
||||
}
|
||||
}
|
||||
return roots, nil
|
||||
}
|
||||
func uploadFromPO(v uploadSessionPO) *biz.UploadSession {
|
||||
return &biz.UploadSession{ID: v.ID, UserID: v.UserID, FileName: v.FileName, FileHash: v.FileHash, FileSize: v.FileSize, ChunkSize: v.ChunkSize, ChunkTotal: v.ChunkTotal, Status: v.Status, StorageKey: v.StorageKey, MediaID: v.MediaID}
|
||||
}
|
||||
func (r *mediaRepo) FindUploadingSession(ctx context.Context, userID uint, hash string) (*biz.UploadSession, error) {
|
||||
var po uploadSessionPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("user_id = ? AND file_hash = ? AND status = ?", userID, hash, "uploading").First(&po).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uploadFromPO(po), nil
|
||||
}
|
||||
func (r *mediaRepo) CreateUploadSession(ctx context.Context, v *biz.UploadSession) error {
|
||||
po := uploadSessionPO{UserID: v.UserID, FileName: v.FileName, FileHash: v.FileHash, FileSize: v.FileSize, ChunkSize: v.ChunkSize, ChunkTotal: v.ChunkTotal, Status: v.Status}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
return nil
|
||||
}
|
||||
func (r *mediaRepo) FindUploadSession(ctx context.Context, id uint) (*biz.UploadSession, error) {
|
||||
var po uploadSessionPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uploadFromPO(po), nil
|
||||
}
|
||||
func (r *mediaRepo) CompleteUploadSession(ctx context.Context, id uint, key string, mediaID uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ?", id).Updates(map[string]any{"status": "completed", "storage_key": key, "media_id": mediaID}).Error
|
||||
}
|
||||
func (r *mediaRepo) DeleteUploadSession(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Unscoped().Delete(&uploadSessionPO{}, id).Error
|
||||
}
|
||||
func (r *mediaRepo) UpsertChunk(ctx context.Context, uploadID uint, v *biz.UploadChunk) error {
|
||||
po := uploadChunkPO{UploadID: uploadID, ChunkIndex: v.Index, ChunkHash: v.Hash, Size: v.Size}
|
||||
return r.data.gormDB.WithContext(ctx).Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "upload_id"}, {Name: "chunk_index"}}, DoUpdates: clause.AssignmentColumns([]string{"chunk_hash", "size", "updated_at"})}).Create(&po).Error
|
||||
}
|
||||
func (r *mediaRepo) ListChunks(ctx context.Context, uploadID uint) ([]*biz.UploadChunk, error) {
|
||||
var pos []uploadChunkPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("upload_id = ?", uploadID).Order("chunk_index").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*biz.UploadChunk, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, &biz.UploadChunk{Index: po.ChunkIndex, Hash: po.ChunkHash, Size: po.Size})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *mediaRepo) DeleteChunks(ctx context.Context, uploadID uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Unscoped().Where("upload_id = ?", uploadID).Delete(&uploadChunkPO{}).Error
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
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
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type userPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
UUID string `gorm:"type:char(36);uniqueIndex"`
|
||||
Username string `gorm:"index;uniqueIndex"`
|
||||
Password string
|
||||
NickName string `gorm:"column:nick_name"`
|
||||
HeaderImg string `gorm:"column:header_img"`
|
||||
AuthorityID uint `gorm:"column:authority_id"`
|
||||
DeptID uint `gorm:"column:dept_id"`
|
||||
Phone string
|
||||
Email string
|
||||
Enable int `gorm:"default:1"`
|
||||
OriginSetting string `gorm:"type:text"`
|
||||
PasswordUpdatedAt *time.Time
|
||||
MustChangePassword bool
|
||||
}
|
||||
|
||||
func (userPO) TableName() string { return "sys_users" }
|
||||
|
||||
type authorityPO struct {
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
AuthorityID uint `gorm:"primaryKey;column:authority_id"`
|
||||
AuthorityName string
|
||||
ParentID *uint
|
||||
DataScope int `gorm:"default:1"`
|
||||
DefaultRouter string `gorm:"default:dashboard"`
|
||||
}
|
||||
|
||||
func (authorityPO) TableName() string { return "sys_authorities" }
|
||||
|
||||
type menuPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
MenuLevel uint
|
||||
ParentID uint `gorm:"column:parent_id"`
|
||||
Path string
|
||||
Name string
|
||||
Hidden bool
|
||||
Component string
|
||||
Sort int
|
||||
ActiveName string
|
||||
KeepAlive bool
|
||||
DefaultMenu bool
|
||||
Title string
|
||||
Icon string
|
||||
CloseTab bool
|
||||
TransitionType string
|
||||
}
|
||||
|
||||
func (menuPO) TableName() string { return "sys_base_menus" }
|
||||
|
||||
type userAuthorityPO struct {
|
||||
SysUserID uint `gorm:"primaryKey;column:sys_user_id"`
|
||||
SysAuthorityAuthorityID uint `gorm:"primaryKey;column:sys_authority_authority_id"`
|
||||
}
|
||||
|
||||
func (userAuthorityPO) TableName() string { return "sys_user_authority" }
|
||||
|
||||
type authorityMenuPO struct {
|
||||
SysAuthorityAuthorityID uint `gorm:"primaryKey;column:sys_authority_authority_id"`
|
||||
SysBaseMenuID uint `gorm:"primaryKey;column:sys_base_menu_id"`
|
||||
}
|
||||
|
||||
func (authorityMenuPO) TableName() string { return "sys_authority_menus" }
|
||||
|
||||
type systemRepo struct{ data *Data }
|
||||
|
||||
func NewSystemRepo(data *Data) biz.SystemRepo { return &systemRepo{data: data} }
|
||||
|
||||
func (r *systemRepo) IsInitialized(ctx context.Context) (bool, error) {
|
||||
return r.data.gormDB.WithContext(ctx).Migrator().HasTable(&userPO{}), nil
|
||||
}
|
||||
|
||||
func (r *systemRepo) Initialize(ctx context.Context) error {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
if err := db.AutoMigrate(&userPO{}, &authorityPO{}, &menuPO{}, &userAuthorityPO{}, &authorityMenuPO{}, &apiPO{}, &ignoredAPIPO{}, &authorityAPIPO{}, &menuButtonPO{}, &authorityButtonPO{}, &departmentPO{}, &positionPO{}, &userDepartmentPO{}, &userPositionPO{}, &authorityDepartmentPO{}, &dictionaryPO{}, &dictionaryDetailPO{}, ¶meterPO{}, &apiTokenPO{}, &securityConfigPO{}, &versionPO{}, &exportTemplatePO{}, &exportConditionPO{}, &exportJoinPO{}, &operationPO{}, &loginLogPO{}, &dataAccessLogPO{}, &errorRecordPO{}, &taskPO{}, &taskLogPO{}, &mediaPO{}, &categoryPO{}, &uploadSessionPO{}, &uploadChunkPO{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
authority := authorityPO{AuthorityID: 888, AuthorityName: "超级管理员", DataScope: 1, DefaultRouter: "dashboard"}
|
||||
if err := tx.FirstOrCreate(&authority, authorityPO{AuthorityID: 888}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
menus := defaultMenus()
|
||||
for i := range menus {
|
||||
if err := tx.Where("name = ?", menus[i].Name).FirstOrCreate(&menus[i]).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var persisted []menuPO
|
||||
if err := tx.Order("sort asc, id asc").Find(&persisted).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
nameID := make(map[string]uint, len(persisted))
|
||||
for _, menu := range persisted {
|
||||
nameID[menu.Name] = menu.ID
|
||||
}
|
||||
for i := range menus {
|
||||
if menus[i].ParentID == 0 || menus[i].ActiveName == "" {
|
||||
continue
|
||||
}
|
||||
_ = tx.Model(&menuPO{}).Where("name = ?", menus[i].Name).Updates(map[string]any{"parent_id": nameID[menus[i].ActiveName], "active_name": ""}).Error
|
||||
}
|
||||
if err := tx.Where("sys_authority_authority_id = ?", 888).Delete(&authorityMenuPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]authorityMenuPO, 0, len(persisted))
|
||||
for _, menu := range persisted {
|
||||
links = append(links, authorityMenuPO{SysAuthorityAuthorityID: 888, SysBaseMenuID: menu.ID})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
if err := tx.Create(&links).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var count int64
|
||||
if err := tx.Model(&userPO{}).Where("username = ?", "admin").Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
user := userPO{UUID: uuid.NewString(), Username: "admin", Password: string(hash), NickName: "超级管理员", AuthorityID: 888, Enable: 1, PasswordUpdatedAt: &now}
|
||||
if err := tx.Create(&user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&userAuthorityPO{SysUserID: user.ID, SysAuthorityAuthorityID: 888}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *systemRepo) FindUserByUsername(ctx context.Context, username string) (*biz.User, error) {
|
||||
var po userPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("username = ?", username).First(&po).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, biz.ErrAdminNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return r.toBizUser(ctx, &po)
|
||||
}
|
||||
|
||||
func (r *systemRepo) FindUserByID(ctx context.Context, id uint) (*biz.User, error) {
|
||||
var po userPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, biz.ErrAdminNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return r.toBizUser(ctx, &po)
|
||||
}
|
||||
|
||||
func (r *systemRepo) toBizUser(ctx context.Context, po *userPO) (*biz.User, error) {
|
||||
var authority authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&authority, "authority_id = ?", po.AuthorityID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var authorityPOs []authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Table("sys_authorities").Joins("JOIN sys_user_authority ON sys_user_authority.sys_authority_authority_id = sys_authorities.authority_id").Where("sys_user_authority.sys_user_id = ? AND sys_authorities.deleted_at IS NULL", po.ID).Find(&authorityPOs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorities := make([]biz.Authority, 0, len(authorityPOs))
|
||||
for _, item := range authorityPOs {
|
||||
authorities = append(authorities, toBizAuthority(item))
|
||||
}
|
||||
setting := map[string]any(nil)
|
||||
if po.OriginSetting != "" {
|
||||
_ = json.Unmarshal([]byte(po.OriginSetting), &setting)
|
||||
}
|
||||
var departmentPOs []departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Table("sys_departments").Joins("JOIN sys_user_departments ON sys_user_departments.sys_department_id = sys_departments.id").Where("sys_user_departments.sys_user_id = ? AND sys_departments.deleted_at IS NULL", po.ID).Find(&departmentPOs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
departments := make([]biz.Department, 0, len(departmentPOs))
|
||||
var primary *biz.Department
|
||||
for _, item := range departmentPOs {
|
||||
value := deptFromPO(item)
|
||||
departments = append(departments, *value)
|
||||
if item.ID == po.DeptID {
|
||||
copy := *value
|
||||
primary = ©
|
||||
}
|
||||
}
|
||||
var positionPOs []positionPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Table("sys_positions").Joins("JOIN sys_user_positions ON sys_user_positions.sys_position_id = sys_positions.id").Where("sys_user_positions.sys_user_id = ? AND sys_positions.deleted_at IS NULL", po.ID).Find(&positionPOs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
positions := make([]biz.Position, 0, len(positionPOs))
|
||||
for _, item := range positionPOs {
|
||||
positions = append(positions, *posFromPO(item))
|
||||
}
|
||||
return &biz.User{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, UUID: po.UUID, Username: po.Username, Password: po.Password, NickName: po.NickName, HeaderImg: po.HeaderImg, AuthorityID: po.AuthorityID, Authority: toBizAuthority(authority), Authorities: authorities, DeptID: po.DeptID, Department: primary, Departments: departments, Positions: positions, Phone: po.Phone, Email: po.Email, Enable: po.Enable, OriginSetting: setting, MustChangePassword: po.MustChangePassword, PasswordUpdatedAt: po.PasswordUpdatedAt}, nil
|
||||
}
|
||||
|
||||
func toBizAuthority(po authorityPO) biz.Authority {
|
||||
return biz.Authority{AuthorityID: po.AuthorityID, AuthorityName: po.AuthorityName, ParentID: po.ParentID, DataScope: po.DataScope, DefaultRouter: po.DefaultRouter}
|
||||
}
|
||||
|
||||
func (r *systemRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*biz.Menu, error) {
|
||||
var pos []menuPO
|
||||
err := r.data.gormDB.WithContext(ctx).Table("sys_base_menus").
|
||||
Joins("JOIN sys_authority_menus ON sys_authority_menus.sys_base_menu_id = sys_base_menus.id").
|
||||
Where("sys_authority_menus.sys_authority_authority_id = ? AND sys_base_menus.deleted_at IS NULL", authorityID).
|
||||
Order("sys_base_menus.sort asc, sys_base_menus.id asc").Scan(&pos).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byID := make(map[uint]*biz.Menu, len(pos))
|
||||
for _, po := range pos {
|
||||
byID[po.ID] = &biz.Menu{ID: po.ID, ParentID: po.ParentID, Path: po.Path, Name: po.Name, Hidden: po.Hidden, Component: po.Component, Sort: po.Sort, ActiveName: po.ActiveName, KeepAlive: po.KeepAlive, DefaultMenu: po.DefaultMenu, Title: po.Title, Icon: po.Icon, CloseTab: po.CloseTab, TransitionType: po.TransitionType, Children: []*biz.Menu{}}
|
||||
}
|
||||
roots := make([]*biz.Menu, 0)
|
||||
for _, po := range pos {
|
||||
menu := byID[po.ID]
|
||||
if parent := byID[po.ParentID]; parent != nil {
|
||||
parent.Children = append(parent.Children, menu)
|
||||
} else {
|
||||
roots = append(roots, menu)
|
||||
}
|
||||
}
|
||||
return roots, nil
|
||||
}
|
||||
|
||||
func (r *systemRepo) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]*biz.User, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&userPO{})
|
||||
if scope, ok := biz.DataScopeFromContext(ctx); ok && !scope.All {
|
||||
if scope.OwnerUserID != 0 {
|
||||
db = db.Where("sys_users.id = ?", scope.OwnerUserID)
|
||||
} else if len(scope.DepartmentIDs) > 0 {
|
||||
db = db.Joins("JOIN sys_user_departments ON sys_user_departments.sys_user_id = sys_users.id").Where("sys_user_departments.sys_department_id IN ?", scope.DepartmentIDs).Distinct("sys_users.id")
|
||||
} else {
|
||||
db = db.Where("1 = 0")
|
||||
}
|
||||
}
|
||||
order := "id desc"
|
||||
if filter != nil {
|
||||
if filter.Username != "" {
|
||||
db = db.Where("username LIKE ?", "%"+filter.Username+"%")
|
||||
}
|
||||
if filter.NickName != "" {
|
||||
db = db.Where("nick_name LIKE ?", "%"+filter.NickName+"%")
|
||||
}
|
||||
if filter.Phone != "" {
|
||||
db = db.Where("phone LIKE ?", "%"+filter.Phone+"%")
|
||||
}
|
||||
if filter.Email != "" {
|
||||
db = db.Where("email LIKE ?", "%"+filter.Email+"%")
|
||||
}
|
||||
allowed := map[string]bool{"id": true, "username": true, "nick_name": true, "phone": true, "email": true, "created_at": true}
|
||||
if allowed[filter.OrderKey] {
|
||||
order = filter.OrderKey
|
||||
if filter.Desc {
|
||||
order += " desc"
|
||||
}
|
||||
}
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []userPO
|
||||
if err := db.Order(order).Offset((page - 1) * pageSize).Limit(pageSize).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
users := make([]*biz.User, 0, len(pos))
|
||||
for i := range pos {
|
||||
user, err := r.toBizUser(ctx, &pos[i])
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
users = append(users, user)
|
||||
}
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
func (r *systemRepo) CreateUser(ctx context.Context, user *biz.User) (*biz.User, error) {
|
||||
if user.UUID == "" {
|
||||
user.UUID = uuid.NewString()
|
||||
}
|
||||
if user.Enable == 0 {
|
||||
user.Enable = 1
|
||||
}
|
||||
if user.AuthorityID == 0 {
|
||||
user.AuthorityID = 888
|
||||
}
|
||||
if user.NickName == "" {
|
||||
user.NickName = "系统用户"
|
||||
}
|
||||
now := time.Now()
|
||||
po := userPO{UUID: user.UUID, Username: user.Username, Password: user.Password, NickName: user.NickName, HeaderImg: user.HeaderImg, AuthorityID: user.AuthorityID, Phone: user.Phone, Email: user.Email, Enable: user.Enable, PasswordUpdatedAt: &now, MustChangePassword: user.MustChangePassword}
|
||||
if err := r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(&userAuthorityPO{SysUserID: po.ID, SysAuthorityAuthorityID: po.AuthorityID}).Error
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.FindUserByID(ctx, po.ID)
|
||||
}
|
||||
|
||||
func (r *systemRepo) UpdateUser(ctx context.Context, user *biz.User) error {
|
||||
updates := map[string]any{"nick_name": user.NickName, "header_img": user.HeaderImg, "phone": user.Phone, "email": user.Email, "enable": user.Enable}
|
||||
if user.AuthorityID != 0 {
|
||||
updates["authority_id"] = user.AuthorityID
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("id = ?", user.ID).Updates(updates).Error
|
||||
}
|
||||
func (r *systemRepo) DeleteUser(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_user_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Delete(&userPO{}, id).Error
|
||||
})
|
||||
}
|
||||
func (r *systemRepo) UpdatePassword(ctx context.Context, id uint, password string) error {
|
||||
now := time.Now()
|
||||
return r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("id = ?", id).Updates(map[string]any{"password": password, "password_updated_at": now, "must_change_password": false}).Error
|
||||
}
|
||||
func (r *systemRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
|
||||
var pos []authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Order("authority_id").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*biz.Authority, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
a := toBizAuthority(po)
|
||||
result = append(result, &a)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *systemRepo) SetUserAuthorities(ctx context.Context, id uint, authorityIDs []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_user_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]userAuthorityPO, 0, len(authorityIDs))
|
||||
for _, authorityID := range authorityIDs {
|
||||
var count int64
|
||||
if err := tx.Model(&authorityPO{}).Where("authority_id = ?", authorityID).Count(&count).Error; err != nil || count == 0 {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New("角色不存在")
|
||||
}
|
||||
links = append(links, userAuthorityPO{SysUserID: id, SysAuthorityAuthorityID: authorityID})
|
||||
}
|
||||
if err := tx.Create(&links).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&userPO{}).Where("id = ?", id).Update("authority_id", authorityIDs[0]).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (r *systemRepo) SetUserAuthority(ctx context.Context, id, authorityID uint) error {
|
||||
var count int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&userAuthorityPO{}).Where("sys_user_id = ? AND sys_authority_authority_id = ?", id, authorityID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return errors.New("该用户无此角色")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("id = ?", id).Update("authority_id", authorityID).Error
|
||||
}
|
||||
|
||||
func (r *systemRepo) SetUserSetting(ctx context.Context, id uint, setting map[string]any) error {
|
||||
value, err := json.Marshal(setting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("id = ?", id).Update("origin_setting", string(value)).Error
|
||||
}
|
||||
|
||||
// ActiveName temporarily carries the parent menu name during seeding. It is
|
||||
// cleared from responses by the service for these records.
|
||||
func defaultMenus() []menuPO {
|
||||
root := func(path, name, title, icon string, sort int) menuPO {
|
||||
return menuPO{Path: path, Name: name, Component: "view/routerHolder.vue", Title: title, Icon: icon, Sort: sort}
|
||||
}
|
||||
child := func(parent, path, name, component, title, icon string, sort int) menuPO {
|
||||
return menuPO{MenuLevel: 1, Path: path, Name: name, Component: component, Title: title, Icon: icon, Sort: sort, ActiveName: parent}
|
||||
}
|
||||
return []menuPO{
|
||||
{Path: "dashboard", Name: "dashboard", Component: "view/dashboard/index.vue", Title: "仪表盘", Icon: "odometer", Sort: 1},
|
||||
root("permission", "permission", "权限管理", "perm-gva", 2), root("org", "org", "组织管理", "share", 3), root("systemConfig", "systemConfig", "系统设置", "config-gva", 4), root("monitor", "monitor", "运维监控", "monitor-gva", 5), root("media", "media", "媒体管理", "folder-opened", 6),
|
||||
{Path: "person", Name: "person", Component: "view/person/person.vue", Title: "个人信息", Icon: "postcard", Hidden: true, Sort: 13},
|
||||
child("permission", "authority", "authority", "view/superAdmin/authority/authority.vue", "角色管理", "role-gva", 1), child("permission", "menu", "menu", "view/superAdmin/menu/menu.vue", "菜单管理", "tickets", 2), child("permission", "api", "api", "view/superAdmin/api/api.vue", "api管理", "api-gva", 3), child("permission", "apiToken", "apiToken", "view/systemTools/apiToken/index.vue", "API Token", "key", 4),
|
||||
child("org", "user", "user", "view/superAdmin/user/user.vue", "用户管理", "user", 1), child("org", "department", "department", "view/superAdmin/department/department.vue", "部门管理", "office-building", 2), child("org", "position", "position", "view/superAdmin/position/position.vue", "岗位管理", "postcard", 3),
|
||||
child("systemConfig", "system", "system", "view/systemTools/system/system.vue", "配置文件", "config-file-gva", 1), child("systemConfig", "dictionary", "dictionary", "view/superAdmin/dictionary/sysDictionary.vue", "字典管理", "notebook", 2), child("systemConfig", "sysParams", "sysParams", "view/superAdmin/params/sysParams.vue", "参数管理", "set-up", 3), child("systemConfig", "security", "security", "view/system/security/index.vue", "安全配置", "security-gva", 4),
|
||||
child("monitor", "operation", "operation", "view/superAdmin/operation/sysOperationRecord.vue", "操作历史", "document", 1), child("monitor", "loginLog", "loginLog", "view/systemTools/loginLog/index.vue", "登录日志", "clock", 2), child("monitor", "sysError", "sysError", "view/systemTools/sysError/sysError.vue", "错误日志", "error-gva", 3), child("monitor", "sysVersion", "sysVersion", "view/systemTools/version/version.vue", "版本管理", "version-gva", 4), child("monitor", "state", "state", "view/system/state.vue", "服务器状态", "server", 5), child("monitor", "dataAccessLog", "dataAccessLog", "view/superAdmin/dataAccessLog/dataAccessLog.vue", "数据权限审计", "warning", 6), child("monitor", "timedTask", "timedTask", "view/systemTools/timedTask/index.vue", "定时任务", "timer", 7), child("monitor", "logViewer", "logViewer", "view/systemTools/logViewer/index.vue", "文件日志", "document", 8),
|
||||
child("media", "upload", "upload", "view/media/upload.vue", "媒体库(上传下载)", "upload", 1), child("media", "chunkUpload", "chunkUpload", "view/media/chunkUpload.vue", "大文件上传", "folder-add", 2),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gorm.io/gorm"
|
||||
"kra/internal/biz"
|
||||
"time"
|
||||
)
|
||||
|
||||
type taskPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Name string `gorm:"index"`
|
||||
Description, Spec string
|
||||
WithSeconds bool
|
||||
ExecutorType, MethodName string
|
||||
Params []byte `gorm:"type:json"`
|
||||
HTTPURL string
|
||||
HTTPMethod string
|
||||
HTTPHeader []byte `gorm:"type:json"`
|
||||
HTTPBody string `gorm:"type:text"`
|
||||
HTTPAllowPrivate, Enabled bool
|
||||
}
|
||||
|
||||
func (taskPO) TableName() string { return "sys_timed_tasks" }
|
||||
|
||||
type taskLogPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
TaskID uint `gorm:"index"`
|
||||
TaskName, TriggerType string
|
||||
StartedAt, FinishedAt time.Time
|
||||
DurationMS int64
|
||||
Status string
|
||||
ErrorMsg, Output string `gorm:"type:text"`
|
||||
}
|
||||
|
||||
func (taskLogPO) TableName() string { return "sys_timed_task_logs" }
|
||||
|
||||
type taskRepo struct{ data *Data }
|
||||
|
||||
func NewTaskRepo(data *Data) biz.TaskRepo { return &taskRepo{data: data} }
|
||||
func taskToPO(v *biz.TimedTask) taskPO {
|
||||
return taskPO{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: v.Params, HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: v.HTTPHeader, HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled}
|
||||
}
|
||||
func taskFromPO(v taskPO) *biz.TimedTask {
|
||||
return &biz.TimedTask{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: v.Params, HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: v.HTTPHeader, HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled}
|
||||
}
|
||||
func (r *taskRepo) CreateTask(ctx context.Context, v *biz.TimedTask) error {
|
||||
po := taskToPO(v)
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
return nil
|
||||
}
|
||||
func (r *taskRepo) UpdateTask(ctx context.Context, v *biz.TimedTask) error {
|
||||
po := taskToPO(v)
|
||||
return r.data.gormDB.WithContext(ctx).Model(&taskPO{}).Where("id = ?", v.ID).Select("name", "description", "spec", "with_seconds", "executor_type", "method_name", "params", "http_url", "http_method", "http_header", "http_body", "http_allow_private", "enabled").Updates(&po).Error
|
||||
}
|
||||
func (r *taskRepo) DeleteTask(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&taskPO{}, id).Error
|
||||
}
|
||||
func (r *taskRepo) FindTask(ctx context.Context, id uint) (*biz.TimedTask, error) {
|
||||
var po taskPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return taskFromPO(po), nil
|
||||
}
|
||||
func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *biz.TimedTask) ([]*biz.TimedTask, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&taskPO{})
|
||||
if q != nil {
|
||||
if q.Name != "" {
|
||||
db = db.Where("name LIKE ?", "%"+q.Name+"%")
|
||||
}
|
||||
if q.ExecutorType != "" {
|
||||
db = db.Where("executor_type = ?", q.ExecutorType)
|
||||
}
|
||||
if q.EnabledFilter != nil {
|
||||
db = db.Where("enabled = ?", *q.EnabledFilter)
|
||||
}
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []taskPO
|
||||
if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.TimedTask, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, taskFromPO(po))
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *taskRepo) ToggleTask(ctx context.Context, id uint, enabled bool) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&taskPO{}).Where("id = ?", id).Update("enabled", enabled).Error
|
||||
}
|
||||
func (r *taskRepo) RecordTaskLog(ctx context.Context, v *biz.TimedTaskLog) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&taskLogPO{TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}).Error
|
||||
}
|
||||
func taskLogFromPO(v taskLogPO) *biz.TimedTaskLog {
|
||||
return &biz.TimedTaskLog{ID: v.ID, CreatedAt: v.CreatedAt, TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}
|
||||
}
|
||||
func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint, status string) ([]*biz.TimedTaskLog, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&taskLogPO{})
|
||||
if taskID != 0 {
|
||||
db = db.Where("task_id = ?", taskID)
|
||||
}
|
||||
if status != "" {
|
||||
db = db.Where("status = ?", status)
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []taskLogPO
|
||||
if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.TimedTaskLog, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, taskLogFromPO(po))
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *taskRepo) CleanupLogs(ctx context.Context, before time.Time) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Unscoped().Where("created_at < ?", before).Delete(&operationPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Unscoped().Where("created_at < ?", before).Delete(&loginLogPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Unscoped().Where("created_at < ?", before).Delete(&taskLogPO{}).Error
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"kra/internal/biz"
|
||||
)
|
||||
|
||||
type versionPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Name string `gorm:"column:version_name"`
|
||||
Code string `gorm:"column:version_code"`
|
||||
Description string
|
||||
Data string `gorm:"column:version_data;type:text"`
|
||||
}
|
||||
|
||||
func (versionPO) TableName() string { return "sys_versions" }
|
||||
|
||||
type versionRepo struct{ data *Data }
|
||||
|
||||
func NewVersionRepo(data *Data) biz.VersionRepo { return &versionRepo{data: data} }
|
||||
func versionFromPO(v versionPO) *biz.Version {
|
||||
return &biz.Version{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Code: v.Code, Description: v.Description, Data: v.Data}
|
||||
}
|
||||
func (r *versionRepo) CreateVersion(ctx context.Context, v *biz.Version) error {
|
||||
po := versionPO{Name: v.Name, Code: v.Code, Description: v.Description, Data: v.Data}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
return nil
|
||||
}
|
||||
func (r *versionRepo) DeleteVersions(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&versionPO{}, ids).Error
|
||||
}
|
||||
func (r *versionRepo) FindVersion(ctx context.Context, id uint) (*biz.Version, error) {
|
||||
var po versionPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return versionFromPO(po), nil
|
||||
}
|
||||
func (r *versionRepo) ListVersions(ctx context.Context, page, size int, name, code string) ([]*biz.Version, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&versionPO{})
|
||||
if name != "" {
|
||||
db = db.Where("version_name LIKE ?", "%"+name+"%")
|
||||
}
|
||||
if code != "" {
|
||||
db = db.Where("version_code = ?", code)
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var pos []versionPO
|
||||
if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.Version, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, versionFromPO(po))
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, dictIDs []uint) (*biz.VersionBundle, error) {
|
||||
bundle := &biz.VersionBundle{}
|
||||
var menus []menuPO
|
||||
if len(menuIDs) > 0 {
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("id IN ?", menuIDs).Order("sort,id").Find(&menus).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
byID := map[uint]*biz.Menu{}
|
||||
for _, po := range menus {
|
||||
v := menuFromPO(po)
|
||||
v.Children = []*biz.Menu{}
|
||||
var btns []menuButtonPO
|
||||
_ = r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", po.ID).Find(&btns).Error
|
||||
for _, btn := range btns {
|
||||
v.Buttons = append(v.Buttons, &biz.MenuButton{Name: btn.Name, Description: btn.Description})
|
||||
}
|
||||
byID[po.ID] = v
|
||||
}
|
||||
for _, po := range menus {
|
||||
v := byID[po.ID]
|
||||
if parent := byID[po.ParentID]; parent != nil {
|
||||
parent.Children = append(parent.Children, v)
|
||||
} else {
|
||||
bundle.Menus = append(bundle.Menus, v)
|
||||
}
|
||||
}
|
||||
var apis []apiPO
|
||||
if len(apiIDs) > 0 {
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("id IN ?", apiIDs).Find(&apis).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, po := range apis {
|
||||
v := apiFromPO(po)
|
||||
v.ID = 0
|
||||
bundle.APIs = append(bundle.APIs, v)
|
||||
}
|
||||
var dictionaries []dictionaryPO
|
||||
if len(dictIDs) > 0 {
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("id IN ?", dictIDs).Find(&dictionaries).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
settings := &settingsRepo{data: r.data}
|
||||
for _, po := range dictionaries {
|
||||
v := dictionaryFromPO(po)
|
||||
v.ID = 0
|
||||
details, _, err := settings.ListDictionaryDetails(ctx, 1, 100000, po.ID, "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, d := range details {
|
||||
d.ID = 0
|
||||
d.DictionaryID = 0
|
||||
d.ParentID = nil
|
||||
d.Path = ""
|
||||
d.Level = 0
|
||||
}
|
||||
v.Details = details
|
||||
bundle.Dictionaries = append(bundle.Dictionaries, v)
|
||||
}
|
||||
return bundle, nil
|
||||
}
|
||||
func (r *versionRepo) ImportVersionBundle(ctx context.Context, b *biz.VersionBundle) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var addMenus func([]*biz.Menu, uint) error
|
||||
addMenus = func(items []*biz.Menu, parent uint) error {
|
||||
for _, item := range items {
|
||||
var existing menuPO
|
||||
err := tx.Where("name = ? AND path = ?", item.Name, item.Path).First(&existing).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
po := menuToPO(item)
|
||||
po.ID = 0
|
||||
po.ParentID = parent
|
||||
if err = tx.Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
existing = po
|
||||
for _, button := range item.Buttons {
|
||||
if err = tx.Create(&menuButtonPO{Name: button.Name, Description: button.Description, MenuID: po.ID}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = addMenus(item.Children, existing.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := addMenus(b.Menus, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range b.APIs {
|
||||
po := apiPO{Path: item.Path, Description: item.Description, APIGroup: item.APIGroup, Method: strings.ToUpper(item.Method)}
|
||||
if err := tx.Where("path = ? AND method = ?", po.Path, po.Method).FirstOrCreate(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, item := range b.Dictionaries {
|
||||
var count int64
|
||||
if err := tx.Model(&dictionaryPO{}).Where("type = ?", item.Type).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
continue
|
||||
}
|
||||
po := dictionaryPO{Name: item.Name, Type: item.Type, Status: item.Status, Desc: item.Desc}
|
||||
if err := tx.Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, detail := range item.Details {
|
||||
d := dictionaryDetailPO{Label: detail.Label, Value: detail.Value, Extend: detail.Extend, Status: detail.Status, Sort: detail.Sort, DictionaryID: po.ID}
|
||||
if err := tx.Create(&d).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user