This commit is contained in:
2026-08-15 03:14:56 +08:00
parent a34133cce5
commit 2a3c55bb53
71 changed files with 1445 additions and 1076 deletions
-160
View File
@@ -1,160 +0,0 @@
package biz
import (
"context"
"strings"
"time"
)
type API struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Path string
Description string
APIGroup string
Method string
OrderKey string
Desc bool
StrictAll bool
}
type APIRepo interface {
CreateAPI(context.Context, *API) error
UpdateAPI(context.Context, *API) error
DeleteAPIs(context.Context, []uint) error
FindAPI(context.Context, uint) (*API, error)
ListAPIs(context.Context, int, int, *API) ([]*API, int64, error)
APIRoleIDs(context.Context, string, string) ([]uint, error)
SetAPIRoles(context.Context, string, string, []uint) error
Authorize(context.Context, uint, string, string) (bool, error)
PolicyPaths(context.Context, uint) ([]*API, error)
SetPolicyPaths(context.Context, uint, []*API) error
IgnoredAPIs(context.Context) ([]*API, error)
SetAPIIgnored(context.Context, string, string, bool) error
ApplyAPISync(context.Context, []*API, []*API) error
}
type AuthorityAccessRepo interface {
CreateAuthority(context.Context, *Authority) error
CopyAuthority(context.Context, uint, *Authority) error
UpdateAuthority(context.Context, *Authority) error
DeleteAuthority(context.Context, uint) error
ListAuthorities(context.Context) ([]*Authority, error)
SetAuthorityUsers(context.Context, uint, []uint) error
AuthorityUserIDs(context.Context, uint) ([]uint, error)
SetDataScope(context.Context, uint, int, []uint) error
DataScopeDepartmentIDs(context.Context, uint) ([]uint, error)
ResolveDataScope(context.Context, uint, uint) (DataScope, error)
}
type PermissionRepo interface {
Buttons(context.Context, uint) ([]*MenuButton, error)
SetAuthorityButtons(context.Context, uint, map[uint][]uint) error
AuthorityButtonIDs(context.Context, uint) ([]uint, error)
SelectedButtons(context.Context, uint, uint) ([]uint, error)
SetSelectedButtons(context.Context, uint, uint, []uint) error
CanRemoveButton(context.Context, uint) (bool, error)
}
type AuthorityUsecase struct{ AuthorityAccessRepo }
func NewAuthorityUsecase(repo AuthorityAccessRepo) *AuthorityUsecase {
return &AuthorityUsecase{AuthorityAccessRepo: repo}
}
type APIUsecase struct{ APIRepo }
func NewAPIUsecase(repo APIRepo) *APIUsecase { return &APIUsecase{APIRepo: repo} }
type PermissionUsecase struct{ PermissionRepo }
func NewPermissionUsecase(repo PermissionRepo) *PermissionUsecase {
return &PermissionUsecase{PermissionRepo: repo}
}
type AccessControlUsecase struct {
authorities AuthorityAccessRepo
apis APIRepo
}
func NewAccessControlUsecase(authorities AuthorityAccessRepo, apis APIRepo) *AccessControlUsecase {
return &AccessControlUsecase{authorities: authorities, apis: apis}
}
func (uc *AccessControlUsecase) Authorize(ctx context.Context, authorityID uint, path, method string) (bool, error) {
if authorityID == 888 {
return true, nil
}
return uc.apis.Authorize(ctx, authorityID, path, method)
}
func (uc *AccessControlUsecase) ResolveDataScope(ctx context.Context, authorityID, userID uint) (DataScope, error) {
return uc.authorities.ResolveDataScope(ctx, authorityID, userID)
}
type APISyncDiff struct {
Added []*API
Deleted []*API
Ignored []*API
}
func (uc *APIUsecase) SyncAPIs(ctx context.Context, routes []*API) (*APISyncDiff, error) {
stored, _, err := uc.ListAPIs(ctx, 0, 0, nil)
if err != nil {
return nil, err
}
ignored, err := uc.IgnoredAPIs(ctx)
if err != nil {
return nil, err
}
key := func(value *API) string { return strings.ToUpper(value.Method) + " " + value.Path }
ignoreSet := make(map[string]bool, len(ignored))
for _, item := range ignored {
ignoreSet[key(item)] = true
}
routeSet := make(map[string]*API, len(routes))
for _, item := range routes {
if !ignoreSet[key(item)] {
routeSet[key(item)] = item
}
}
storedSet := make(map[string]*API, len(stored))
for _, item := range stored {
storedSet[key(item)] = item
}
diff := &APISyncDiff{Ignored: ignored}
for routeKey, item := range routeSet {
if storedSet[routeKey] == nil {
diff.Added = append(diff.Added, item)
}
}
for storedKey, item := range storedSet {
if routeSet[storedKey] == nil && !ignoreSet[storedKey] {
diff.Deleted = append(diff.Deleted, item)
}
}
return diff, nil
}
func (uc *AuthorityUsecase) AuthorityTree(ctx context.Context) ([]*Authority, error) {
items, err := uc.ListAuthorities(ctx)
if err != nil {
return nil, err
}
byID := make(map[uint]*Authority, len(items))
for _, item := range items {
item.Children = nil
byID[item.AuthorityID] = item
}
roots := make([]*Authority, 0)
for _, item := range items {
if item.ParentID != nil && *item.ParentID != 0 && byID[*item.ParentID] != nil {
parent := byID[*item.ParentID]
parent.Children = append(parent.Children, item)
} else {
roots = append(roots, item)
}
}
return roots, nil
}
+23
View File
@@ -0,0 +1,23 @@
package biz
import "context"
type AccessControlUsecase struct {
authorities AuthorityAccessRepo
apis APIRepo
}
func NewAccessControlUsecase(authorities AuthorityAccessRepo, apis APIRepo) *AccessControlUsecase {
return &AccessControlUsecase{authorities: authorities, apis: apis}
}
func (uc *AccessControlUsecase) Authorize(ctx context.Context, authorityID uint, path, method string) (bool, error) {
if authorityID == 888 {
return true, nil
}
return uc.apis.Authorize(ctx, authorityID, path, method)
}
func (uc *AccessControlUsecase) ResolveDataScope(ctx context.Context, authorityID, userID uint) (DataScope, error) {
return uc.authorities.ResolveDataScope(ctx, authorityID, userID)
}
+84
View File
@@ -0,0 +1,84 @@
package biz
import (
"context"
"strings"
"time"
)
type API struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Path string
Description string
APIGroup string
Method string
OrderKey string
Desc bool
StrictAll bool
}
type APIRepo interface {
CreateAPI(context.Context, *API) error
UpdateAPI(context.Context, *API) error
DeleteAPIs(context.Context, []uint) error
FindAPI(context.Context, uint) (*API, error)
ListAPIs(context.Context, int, int, *API) ([]*API, int64, error)
APIRoleIDs(context.Context, string, string) ([]uint, error)
SetAPIRoles(context.Context, string, string, []uint) error
Authorize(context.Context, uint, string, string) (bool, error)
PolicyPaths(context.Context, uint) ([]*API, error)
SetPolicyPaths(context.Context, uint, []*API) error
IgnoredAPIs(context.Context) ([]*API, error)
SetAPIIgnored(context.Context, string, string, bool) error
ApplyAPISync(context.Context, []*API, []*API) error
}
type APIUsecase struct{ APIRepo }
func NewAPIUsecase(repo APIRepo) *APIUsecase { return &APIUsecase{APIRepo: repo} }
type APISyncDiff struct {
Added []*API
Deleted []*API
Ignored []*API
}
func (uc *APIUsecase) SyncAPIs(ctx context.Context, routes []*API) (*APISyncDiff, error) {
stored, _, err := uc.ListAPIs(ctx, 0, 0, nil)
if err != nil {
return nil, err
}
ignored, err := uc.IgnoredAPIs(ctx)
if err != nil {
return nil, err
}
key := func(value *API) string { return strings.ToUpper(value.Method) + " " + value.Path }
ignoreSet := make(map[string]bool, len(ignored))
for _, item := range ignored {
ignoreSet[key(item)] = true
}
routeSet := make(map[string]*API, len(routes))
for _, item := range routes {
if !ignoreSet[key(item)] {
routeSet[key(item)] = item
}
}
storedSet := make(map[string]*API, len(stored))
for _, item := range stored {
storedSet[key(item)] = item
}
diff := &APISyncDiff{Ignored: ignored}
for routeKey, item := range routeSet {
if storedSet[routeKey] == nil {
diff.Added = append(diff.Added, item)
}
}
for storedKey, item := range storedSet {
if routeSet[storedKey] == nil && !ignoreSet[storedKey] {
diff.Deleted = append(diff.Deleted, item)
}
}
return diff, nil
}
-28
View File
@@ -27,13 +27,6 @@ type OperationRecord struct {
User *User
}
type OperationLogRepo interface {
RecordOperation(context.Context, *OperationRecord) error
ListOperations(context.Context, int, int, *OperationRecord) ([]*OperationRecord, int64, error)
FindOperation(context.Context, uint) (*OperationRecord, error)
DeleteOperations(context.Context, []uint) error
}
type LoginLog struct {
ID uint
CreatedAt time.Time
@@ -45,13 +38,6 @@ type LoginLog struct {
FilterByStatus bool
}
type LoginLogRepo interface {
RecordLogin(context.Context, *LoginLog) error
ListLogins(context.Context, int, int, *LoginLog) ([]*LoginLog, int64, error)
FindLogin(context.Context, uint) (*LoginLog, error)
DeleteLogins(context.Context, []uint) error
}
type DataAccessLog struct {
ID uint
CreatedAt time.Time
@@ -61,12 +47,6 @@ type DataAccessLog struct {
RequestID, Method, Path, Detail string
}
type DataAccessLogRepo interface {
RecordDataAccess(context.Context, *DataAccessLog) error
ListDataAccess(context.Context, int, int, *DataAccessLog) ([]*DataAccessLog, int64, error)
DeleteDataAccess(context.Context, []uint) error
}
type LogDate struct {
Date string
FileCount int
@@ -104,14 +84,6 @@ type ErrorRecord struct {
CreatedAtRange []time.Time
}
type ErrorRecordRepo interface {
CreateError(context.Context, *ErrorRecord) error
UpdateError(context.Context, *ErrorRecord) error
DeleteErrors(context.Context, []uint) error
FindError(context.Context, uint) (*ErrorRecord, error)
ListErrors(context.Context, int, int, *ErrorRecord) ([]*ErrorRecord, int64, error)
}
type AuditRecordRepo interface {
RecordOperation(context.Context, *OperationRecord) error
RecordLogin(context.Context, *LoginLog) error
+30
View File
@@ -40,6 +40,11 @@ type AuthenticationResult struct {
NeedChangePassword bool
}
type TokenAuthentication struct {
Claims *AuthClaims
Refreshed *IssuedToken
}
type AuthenticationUsecase struct {
users *UserUsecase
security *SecurityUsecase
@@ -148,3 +153,28 @@ func (uc *AuthenticationUsecase) SwitchAuthority(ctx context.Context, userID, au
}
return &AuthenticationResult{User: user, Token: issued.Value, ExpiresAt: issued.ExpiresAt, NeedChangePassword: user.MustChangePassword}, nil
}
func (uc *AuthenticationUsecase) AuthenticateToken(ctx context.Context, token string) (*TokenAuthentication, error) {
claims, err := uc.issuer.ParseToken(token)
if err != nil {
return nil, err
}
disabled, err := uc.security.tokens.IsTokenDisabled(ctx, token)
if err != nil || disabled {
return nil, ErrTokenDisabled
}
result := &TokenAuthentication{Claims: claims}
if claims.BufferTime <= 0 || time.Until(claims.ExpiresAt) >= claims.BufferTime {
return result, nil
}
user := &User{ID: claims.ID, UUID: claims.UUID, Username: claims.Username, NickName: claims.NickName, AuthorityID: claims.AuthorityID, MustChangePassword: claims.MustChangePwd}
issued, err := uc.issuer.IssueToken(user, claims.AuthorityID, claims.MustChangePwd, 0)
if err != nil {
return result, nil
}
if err = uc.security.RotateActiveToken(ctx, claims.Username, token, issued.Value, issued.TTL); err != nil {
return result, nil
}
result.Refreshed = issued
return result, nil
}
+44
View File
@@ -0,0 +1,44 @@
package biz
import "context"
type AuthorityAccessRepo interface {
CreateAuthority(context.Context, *Authority) error
CopyAuthority(context.Context, uint, *Authority) error
UpdateAuthority(context.Context, *Authority) error
DeleteAuthority(context.Context, uint) error
ListAuthorities(context.Context) ([]*Authority, error)
SetAuthorityUsers(context.Context, uint, []uint) error
AuthorityUserIDs(context.Context, uint) ([]uint, error)
SetDataScope(context.Context, uint, int, []uint) error
DataScopeDepartmentIDs(context.Context, uint) ([]uint, error)
ResolveDataScope(context.Context, uint, uint) (DataScope, error)
}
type AuthorityUsecase struct{ AuthorityAccessRepo }
func NewAuthorityUsecase(repo AuthorityAccessRepo) *AuthorityUsecase {
return &AuthorityUsecase{AuthorityAccessRepo: repo}
}
func (uc *AuthorityUsecase) AuthorityTree(ctx context.Context) ([]*Authority, error) {
items, err := uc.ListAuthorities(ctx)
if err != nil {
return nil, err
}
byID := make(map[uint]*Authority, len(items))
for _, item := range items {
item.Children = nil
byID[item.AuthorityID] = item
}
roots := make([]*Authority, 0)
for _, item := range items {
if item.ParentID != nil && *item.ParentID != 0 && byID[*item.ParentID] != nil {
parent := byID[*item.ParentID]
parent.Children = append(parent.Children, item)
} else {
roots = append(roots, item)
}
}
return roots, nil
}
+46
View File
@@ -0,0 +1,46 @@
package biz
import (
"context"
"time"
)
type Department struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
ParentID uint
Ancestors string
Sort int
LeaderID uint
Leader *User
Status bool
Children []*Department
NamePath string
}
type DepartmentRepo interface {
CreateDepartment(context.Context, *Department) error
UpdateDepartment(context.Context, *Department) error
DeleteDepartment(context.Context, uint) error
FindDepartment(context.Context, uint) (*Department, error)
ListDepartments(context.Context, string) ([]*Department, error)
DepartmentUserIDs(context.Context, uint) ([]uint, error)
SetDepartmentUsers(context.Context, uint, []uint) error
SetUserDepartments(context.Context, uint, []uint, uint) error
}
type DepartmentUsecase struct{ DepartmentRepo }
func NewDepartmentUsecase(repo DepartmentRepo) *DepartmentUsecase {
return &DepartmentUsecase{DepartmentRepo: repo}
}
func (uc *DepartmentUsecase) Departments(ctx context.Context, name string) ([]*Department, error) {
return uc.ListDepartments(ctx, name)
}
func (uc *DepartmentUsecase) Department(ctx context.Context, id uint) (*Department, error) {
return uc.FindDepartment(ctx, id)
}
+23
View File
@@ -2,6 +2,7 @@ package biz
import (
"context"
"errors"
"io"
"time"
)
@@ -68,6 +69,28 @@ type IssuedToken struct {
TTL time.Duration
}
type AuthClaims struct {
UUID string
ID uint
Username string
NickName string
AuthorityID uint
BufferTime time.Duration
MustChangePwd bool
Issuer string
ExpiresAt time.Time
}
var (
ErrTokenExpired = errors.New("token expired")
ErrTokenMalformed = errors.New("token malformed")
ErrTokenSignatureInvalid = errors.New("token signature invalid")
ErrTokenNotValidYet = errors.New("token not valid yet")
ErrTokenInvalid = errors.New("token invalid")
ErrTokenDisabled = errors.New("token disabled")
)
type TokenIssuer interface {
IssueToken(*User, uint, bool, time.Duration) (*IssuedToken, error)
ParseToken(string) (*AuthClaims, error)
}
+18
View File
@@ -0,0 +1,18 @@
package biz
import "context"
type PermissionRepo interface {
Buttons(context.Context, uint) ([]*MenuButton, error)
SetAuthorityButtons(context.Context, uint, map[uint][]uint) error
AuthorityButtonIDs(context.Context, uint) ([]uint, error)
SelectedButtons(context.Context, uint, uint) ([]uint, error)
SetSelectedButtons(context.Context, uint, uint, []uint) error
CanRemoveButton(context.Context, uint) (bool, error)
}
type PermissionUsecase struct{ PermissionRepo }
func NewPermissionUsecase(repo PermissionRepo) *PermissionUsecase {
return &PermissionUsecase{PermissionRepo: repo}
}
@@ -5,45 +5,6 @@ import (
"time"
)
type Department struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
ParentID uint
Ancestors string
Sort int
LeaderID uint
Leader *User
Status bool
Children []*Department
NamePath string
}
type DepartmentRepo interface {
CreateDepartment(context.Context, *Department) error
UpdateDepartment(context.Context, *Department) error
DeleteDepartment(context.Context, uint) error
FindDepartment(context.Context, uint) (*Department, error)
ListDepartments(context.Context, string) ([]*Department, error)
DepartmentUserIDs(context.Context, uint) ([]uint, error)
SetDepartmentUsers(context.Context, uint, []uint) error
SetUserDepartments(context.Context, uint, []uint, uint) error
}
type DepartmentUsecase struct{ DepartmentRepo }
func NewDepartmentUsecase(repo DepartmentRepo) *DepartmentUsecase {
return &DepartmentUsecase{DepartmentRepo: repo}
}
func (uc *DepartmentUsecase) Departments(ctx context.Context, name string) ([]*Department, error) {
return uc.ListDepartments(ctx, name)
}
func (uc *DepartmentUsecase) Department(ctx context.Context, id uint) (*Department, error) {
return uc.FindDepartment(ctx, id)
}
type Position struct {
ID uint
CreatedAt time.Time
@@ -81,6 +42,7 @@ func NewPositionUsecase(repo PositionRepo) *PositionUsecase {
func (uc *PositionUsecase) Positions(ctx context.Context, page, size int, filter *PositionListFilter) ([]*Position, int64, error) {
return uc.ListPositions(ctx, page, size, filter)
}
func (uc *PositionUsecase) Position(ctx context.Context, id uint) (*Position, error) {
return uc.FindPosition(ctx, id)
}
+14
View File
@@ -2,6 +2,7 @@ package biz
import (
"context"
"encoding/json"
"errors"
)
@@ -24,6 +25,9 @@ type InitializationRepo interface {
PersistAdminConfig(context.Context, []byte) error
PersistRuntimeConfig(context.Context, []byte, []byte) error
ReloadConfig(context.Context) error
ConfigurationJSON() (json.RawMessage, error)
SaveConfigurationJSON(context.Context, json.RawMessage) error
DiskMountPoints() []string
}
type SystemConfigUsecase struct {
@@ -68,3 +72,13 @@ func (uc *SystemConfigUsecase) ReloadConfig(ctx context.Context) error {
}
return nil
}
func (uc *SystemConfigUsecase) ConfigurationJSON() (json.RawMessage, error) {
return uc.repo.ConfigurationJSON()
}
func (uc *SystemConfigUsecase) SaveConfigurationJSON(ctx context.Context, value json.RawMessage) error {
return uc.repo.SaveConfigurationJSON(ctx, value)
}
func (uc *SystemConfigUsecase) DiskMountPoints() []string { return uc.repo.DiskMountPoints() }