This commit is contained in:
2026-08-13 21:51:20 +08:00
parent be5362913b
commit 21c264fcd7
60 changed files with 10174 additions and 69 deletions
+122
View File
@@ -0,0 +1,122 @@
package biz
import (
"context"
"time"
)
type API struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Path string
Description string
APIGroup string
Method string
}
type MenuButton struct {
ID uint
Name string
Description string
MenuID uint
}
type Department struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
ParentID uint
Ancestors string
Sort int
LeaderID uint
Status bool
Children []*Department
}
type Position struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
Code string
Sort int
Status bool
Remark string
}
type AccessRepo 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)
SetAuthorityMenus(context.Context, uint, []uint) error
AuthorityMenuIDs(context.Context, uint) ([]uint, 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)
CreateMenu(context.Context, *Menu) error
UpdateMenu(context.Context, *Menu) error
DeleteMenu(context.Context, uint) error
FindMenu(context.Context, uint) (*Menu, error)
ListMenus(context.Context) ([]*Menu, error)
MenuRoleIDs(context.Context, uint) ([]uint, error)
SetMenuRoles(context.Context, uint, []uint) error
ReplaceMenuButtons(context.Context, uint, []*MenuButton) error
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, uint) ([]uint, error)
SetAPIRoles(context.Context, uint, []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
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)
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
CreatePosition(context.Context, *Position) error
UpdatePosition(context.Context, *Position) error
DeletePosition(context.Context, uint) error
FindPosition(context.Context, uint) (*Position, error)
ListPositions(context.Context, int, int, *Position) ([]*Position, int64, error)
PositionUserIDs(context.Context, uint) ([]uint, error)
SetPositionUsers(context.Context, uint, []uint) error
SetUserPositions(context.Context, uint, []uint) error
}
type AccessUsecase struct{ repo AccessRepo }
func NewAccessUsecase(repo AccessRepo) *AccessUsecase { return &AccessUsecase{repo: repo} }
func (uc *AccessUsecase) Repo() AccessRepo { return uc.repo }
func (uc *AccessUsecase) Authorize(ctx context.Context, authorityID uint, path, method string) (bool, error) {
if authorityID == 888 {
return true, nil
}
return uc.repo.Authorize(ctx, authorityID, path, method)
}
+80
View File
@@ -0,0 +1,80 @@
package biz
import (
"context"
"time"
)
type OperationRecord struct {
ID uint
CreatedAt time.Time
IP, Method, Path string
Status int
LatencyMS int64
Agent, ErrorMessage, Body, Response string
UserID uint
RequestID, TraceID, DeviceID string
}
type LoginLog struct {
ID uint
CreatedAt time.Time
Username, IP string
Status bool
ErrorMessage, Agent string
UserID uint
}
type DataAccessLog struct {
ID uint
CreatedAt time.Time
EventType, TargetTable, Operation string
UserID, AuthorityID uint
Scope int
RequestID, Method, Path, Detail string
}
type LogDate struct {
Date string
FileCount int
}
type LogFile struct {
Path, Name string
Size int64
ModifiedAt time.Time
}
type LogContent struct {
Date, Path, Content string
LineCount int
NextCursor int64
HasMore, LimitedByBytes bool
Size int64
ModifiedAt time.Time
}
type ErrorRecord struct {
ID uint
CreatedAt time.Time
Form, Info, Level, RequestID, TraceID, Solution, Status string
}
type AuditRepo 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
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
RecordDataAccess(context.Context, *DataAccessLog) error
ListDataAccess(context.Context, int, int, *DataAccessLog) ([]*DataAccessLog, int64, error)
DeleteDataAccess(context.Context, []uint) error
LogDates(context.Context, string) ([]LogDate, error)
LogFiles(context.Context, string) ([]LogFile, error)
LogContent(context.Context, string, string, *int64) (*LogContent, error)
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 AuditUsecase struct{ repo AuditRepo }
func NewAuditUsecase(repo AuditRepo) *AuditUsecase { return &AuditUsecase{repo: repo} }
func (uc *AuditUsecase) Repo() AuditRepo { return uc.repo }
+1 -1
View File
@@ -3,4 +3,4 @@ package biz
import "github.com/google/wire"
// ProviderSet is biz providers.
var ProviderSet = wire.NewSet(NewAdminUsecase)
var ProviderSet = wire.NewSet(NewAdminUsecase, NewSystemUsecase, NewAccessUsecase, NewSettingsUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewTaskUsecase, NewMediaUsecase)
+20
View File
@@ -0,0 +1,20 @@
package biz
import "context"
type DataScope struct {
All bool
OwnerUserID uint
DepartmentIDs []uint
}
type dataScopeKey struct{}
func NewDataScopeContext(ctx context.Context, scope DataScope) context.Context {
return context.WithValue(ctx, dataScopeKey{}, scope)
}
func DataScopeFromContext(ctx context.Context) (DataScope, bool) {
scope, ok := ctx.Value(dataScopeKey{}).(DataScope)
return scope, ok
}
+32
View File
@@ -0,0 +1,32 @@
package biz
import (
"context"
"time"
)
type ExportCondition struct{ From, Column, Operator string }
type ExportJoin struct{ Join, Table, On string }
type ExportTemplate struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
DBName, Name, TableName, TemplateID, TemplateInfo, SQL, ImportSQL string
Limit *int
Order string
Conditions []ExportCondition
Joins []ExportJoin
}
type ExportRepo interface {
CreateExportTemplate(context.Context, *ExportTemplate) error
UpdateExportTemplate(context.Context, *ExportTemplate) error
DeleteExportTemplates(context.Context, []uint) error
FindExportTemplate(context.Context, uint, string) (*ExportTemplate, error)
ListExportTemplates(context.Context, int, int, *ExportTemplate) ([]*ExportTemplate, int64, error)
QueryExport(context.Context, *ExportTemplate, map[string]string) ([]map[string]any, string, error)
ImportExportRows(context.Context, *ExportTemplate, []map[string]any) error
}
type ExportUsecase struct{ repo ExportRepo }
func NewExportUsecase(repo ExportRepo) *ExportUsecase { return &ExportUsecase{repo: repo} }
func (uc *ExportUsecase) Repo() ExportRepo { return uc.repo }
+33
View File
@@ -0,0 +1,33 @@
package biz
import (
"context"
"io"
"time"
)
// Cache is the shared cache seam. The data implementation uses Redis when it
// is reachable and falls back to process memory for local development.
type Cache interface {
Get(context.Context, string) (string, bool, error)
Set(context.Context, string, string, time.Duration) error
Delete(context.Context, string) error
Increment(context.Context, string, time.Duration) (int64, error)
}
type StoredFile struct {
Name string
Path string
URL string
Size int64
}
// FileStorage owns the persistence boundary for uploaded files.
type FileStorage interface {
Put(context.Context, string, io.Reader) (*StoredFile, error)
Open(context.Context, string) (io.ReadCloser, error)
Delete(context.Context, string) error
Compose(context.Context, []string, string) (*StoredFile, string, error)
DeletePrefix(context.Context, string) error
List(context.Context, string, string, int) ([]*StoredFile, string, bool, error)
}
+230
View File
@@ -0,0 +1,230 @@
package biz
import (
"context"
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"github.com/google/uuid"
"io"
"path/filepath"
"sort"
"strings"
"time"
)
type MediaFile struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
CategoryID int
URL, Tag, Key string
Size int64
Mime, MD5 string
UserID uint
}
type AttachmentCategory struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
ParentID uint
Children []*AttachmentCategory
}
type UploadSession struct {
ID uint
UserID uint
FileName, FileHash string
FileSize, ChunkSize int64
ChunkTotal int
Status, StorageKey string
MediaID uint
}
type UploadChunk struct {
Index int
Hash string
Size int64
}
type MediaRepo interface {
CreateMedia(context.Context, *MediaFile) error
FindMedia(context.Context, uint) (*MediaFile, error)
FindMediaByHash(context.Context, uint, string) (*MediaFile, error)
ListMedia(context.Context, int, int, string, int, string, uint) ([]*MediaFile, int64, error)
UpdateMediaName(context.Context, uint, string) error
DeleteMedia(context.Context, uint) error
MediaKeyReferences(context.Context, string) (int64, error)
CreateMediaBatch(context.Context, []*MediaFile) error
SaveCategory(context.Context, *AttachmentCategory) error
DeleteCategory(context.Context, uint) error
ListCategories(context.Context) ([]*AttachmentCategory, error)
FindUploadingSession(context.Context, uint, string) (*UploadSession, error)
CreateUploadSession(context.Context, *UploadSession) error
FindUploadSession(context.Context, uint) (*UploadSession, error)
CompleteUploadSession(context.Context, uint, string, uint) error
DeleteUploadSession(context.Context, uint) error
UpsertChunk(context.Context, uint, *UploadChunk) error
ListChunks(context.Context, uint) ([]*UploadChunk, error)
DeleteChunks(context.Context, uint) error
}
type MediaUsecase struct {
repo MediaRepo
files FileStorage
}
func NewMediaUsecase(repo MediaRepo, files FileStorage) *MediaUsecase {
return &MediaUsecase{repo: repo, files: files}
}
func (uc *MediaUsecase) Repo() MediaRepo { return uc.repo }
var blockedExtensions = map[string]bool{".exe": true, ".dll": true, ".bat": true, ".cmd": true, ".sh": true, ".php": true, ".jsp": true, ".asp": true, ".aspx": true, ".html": true, ".htm": true, ".svg": true}
func validateMediaName(name string) error {
if name == "" || filepath.Base(name) != name {
return errors.New("文件名不合法")
}
if blockedExtensions[strings.ToLower(filepath.Ext(name))] {
return errors.New("文件类型不允许上传")
}
return nil
}
func (uc *MediaUsecase) Upload(ctx context.Context, userID uint, name, mime string, categoryID int, reader io.Reader, save bool) (*MediaFile, error) {
if err := validateMediaName(name); err != nil {
return nil, err
}
ext := strings.ToLower(filepath.Ext(name))
key := time.Now().Format("20060102") + "/" + uuid.NewString() + ext
hash := md5.New()
stored, err := uc.files.Put(ctx, key, io.TeeReader(reader, hash))
if err != nil {
return nil, err
}
media := &MediaFile{Name: name, CategoryID: categoryID, URL: stored.URL, Tag: strings.TrimPrefix(ext, "."), Key: key, Size: stored.Size, Mime: mime, MD5: hex.EncodeToString(hash.Sum(nil)), UserID: userID}
if save {
if err = uc.repo.CreateMedia(ctx, media); err != nil {
_ = uc.files.Delete(ctx, key)
return nil, err
}
}
return media, nil
}
func (uc *MediaUsecase) Delete(ctx context.Context, id uint) error {
media, err := uc.repo.FindMedia(ctx, id)
if err != nil {
return err
}
count, err := uc.repo.MediaKeyReferences(ctx, media.Key)
if err != nil {
return err
}
if count <= 1 {
if err = uc.files.Delete(ctx, media.Key); err != nil {
return err
}
}
return uc.repo.DeleteMedia(ctx, id)
}
func (uc *MediaUsecase) InitUpload(ctx context.Context, userID uint, name, hash string, size, chunkSize int64, total int) (*UploadSession, *MediaFile, []int, error) {
if err := validateMediaName(name); err != nil {
return nil, nil, nil, err
}
if size <= 0 || chunkSize <= 0 || total <= 0 {
return nil, nil, nil, errors.New("上传参数不合法")
}
if media, err := uc.repo.FindMediaByHash(ctx, userID, hash); err == nil {
copy := *media
copy.ID = 0
copy.Name = name
if err = uc.repo.CreateMedia(ctx, &copy); err == nil {
return nil, &copy, nil, nil
}
}
session, err := uc.repo.FindUploadingSession(ctx, userID, hash)
if err != nil {
session = &UploadSession{UserID: userID, FileName: name, FileHash: hash, FileSize: size, ChunkSize: chunkSize, ChunkTotal: total, Status: "uploading"}
if err = uc.repo.CreateUploadSession(ctx, session); err != nil {
return nil, nil, nil, err
}
}
chunks, err := uc.repo.ListChunks(ctx, session.ID)
if err != nil {
return nil, nil, nil, err
}
indices := make([]int, 0, len(chunks))
for _, v := range chunks {
indices = append(indices, v.Index)
}
sort.Ints(indices)
return session, nil, indices, nil
}
func (uc *MediaUsecase) SaveChunk(ctx context.Context, userID, uploadID uint, index int, expected string, reader io.Reader) error {
session, err := uc.repo.FindUploadSession(ctx, uploadID)
if err != nil || session.UserID != userID {
return errors.New("上传会话不存在或无权操作")
}
if session.Status != "uploading" || index < 0 || index >= session.ChunkTotal {
return errors.New("上传会话状态或分片序号不合法")
}
hash := md5.New()
key := fmt.Sprintf(".chunks/%d/%08d", uploadID, index)
stored, err := uc.files.Put(ctx, key, io.TeeReader(reader, hash))
if err != nil {
return err
}
actual := hex.EncodeToString(hash.Sum(nil))
if !strings.EqualFold(actual, expected) {
_ = uc.files.Delete(ctx, key)
return errors.New("分片校验失败")
}
return uc.repo.UpsertChunk(ctx, uploadID, &UploadChunk{Index: index, Hash: actual, Size: stored.Size})
}
func (uc *MediaUsecase) CompleteUpload(ctx context.Context, userID, uploadID uint, mime string) (*MediaFile, error) {
session, err := uc.repo.FindUploadSession(ctx, uploadID)
if err != nil || session.UserID != userID {
return nil, errors.New("上传会话不存在或无权操作")
}
chunks, err := uc.repo.ListChunks(ctx, uploadID)
if err != nil || len(chunks) != session.ChunkTotal {
return nil, errors.New("分片不完整")
}
sort.Slice(chunks, func(i, j int) bool { return chunks[i].Index < chunks[j].Index })
names := make([]string, 0, len(chunks))
for index, chunk := range chunks {
if chunk.Index != index {
return nil, errors.New("分片序号不连续")
}
names = append(names, fmt.Sprintf(".chunks/%d/%08d", uploadID, index))
}
ext := strings.ToLower(filepath.Ext(session.FileName))
key := time.Now().Format("20060102") + "/" + uuid.NewString() + ext
stored, hash, err := uc.files.Compose(ctx, names, key)
if err != nil {
return nil, err
}
if !strings.EqualFold(hash, session.FileHash) {
_ = uc.files.Delete(ctx, key)
return nil, errors.New("整文件校验失败")
}
media := &MediaFile{Name: session.FileName, URL: stored.URL, Tag: strings.TrimPrefix(ext, "."), Key: key, Size: stored.Size, Mime: mime, MD5: hash, UserID: userID}
if err = uc.repo.CreateMedia(ctx, media); err != nil {
_ = uc.files.Delete(ctx, key)
return nil, err
}
_ = uc.repo.CompleteUploadSession(ctx, uploadID, key, media.ID)
_ = uc.repo.DeleteChunks(ctx, uploadID)
_ = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", uploadID))
return media, nil
}
func (uc *MediaUsecase) CancelUpload(ctx context.Context, userID, uploadID uint) error {
session, err := uc.repo.FindUploadSession(ctx, uploadID)
if err != nil || session.UserID != userID {
return errors.New("上传会话不存在或无权操作")
}
_ = uc.repo.DeleteChunks(ctx, uploadID)
_ = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", uploadID))
return uc.repo.DeleteUploadSession(ctx, uploadID)
}
func (uc *MediaUsecase) ListStorage(ctx context.Context, prefix, cursor string, limit int) ([]*StoredFile, string, bool, error) {
return uc.files.List(ctx, prefix, cursor, limit)
}
+114
View File
@@ -0,0 +1,114 @@
package biz
import (
"context"
"time"
)
type Dictionary struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
Type string
Status bool
Desc string
ParentID *uint
Children []*Dictionary
Details []*DictionaryDetail
}
type DictionaryDetail struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Label string
Value string
Extend string
Status bool
Sort int
DictionaryID uint
ParentID *uint
Level int
Path string
Children []*DictionaryDetail
}
type SystemParameter struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
Key string
Value string
Desc string
}
type APIToken struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
UserID uint
AuthorityID uint
Token string
Status bool
ExpiresAt time.Time
Remark string
User *User
}
type SecurityConfig struct {
ID uint
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
}
type SettingsRepo interface {
CreateDictionary(context.Context, *Dictionary) error
UpdateDictionary(context.Context, *Dictionary) error
DeleteDictionary(context.Context, uint) error
FindDictionary(context.Context, uint, string, bool) (*Dictionary, error)
ListDictionaries(context.Context, int, int, string, string, bool) ([]*Dictionary, int64, error)
CreateDictionaryDetail(context.Context, *DictionaryDetail) error
UpdateDictionaryDetail(context.Context, *DictionaryDetail) error
DeleteDictionaryDetail(context.Context, uint) error
FindDictionaryDetail(context.Context, uint) (*DictionaryDetail, error)
ListDictionaryDetails(context.Context, int, int, uint, string, string) ([]*DictionaryDetail, int64, error)
DictionaryDetailTree(context.Context, uint, string) ([]*DictionaryDetail, error)
CreateParameter(context.Context, *SystemParameter) error
UpdateParameter(context.Context, *SystemParameter) error
DeleteParameters(context.Context, []uint) error
FindParameter(context.Context, uint, string) (*SystemParameter, error)
ListParameters(context.Context, int, int, *SystemParameter) ([]*SystemParameter, int64, error)
UserHasAuthority(context.Context, uint, uint) (*User, bool, error)
CreateAPIToken(context.Context, *APIToken) error
ListAPITokens(context.Context, int, int, uint, *bool) ([]*APIToken, int64, error)
DisableAPIToken(context.Context, uint) (string, error)
IsTokenDisabled(context.Context, string) (bool, error)
SecurityConfig(context.Context) (*SecurityConfig, error)
SaveSecurityConfig(context.Context, *SecurityConfig) error
}
type SettingsUsecase struct{ repo SettingsRepo }
func NewSettingsUsecase(repo SettingsRepo) *SettingsUsecase { return &SettingsUsecase{repo: repo} }
func (uc *SettingsUsecase) Repo() SettingsRepo { return uc.repo }
+171
View File
@@ -0,0 +1,171 @@
package biz
import (
"context"
"time"
"golang.org/x/crypto/bcrypt"
)
type User struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
UUID string
Username string
Password string
NickName string
HeaderImg string
AuthorityID uint
Authority Authority
Authorities []Authority
DeptID uint
Department *Department
Departments []Department
Positions []Position
Phone string
Email string
Enable int
OriginSetting map[string]any
MustChangePassword bool
PasswordUpdatedAt *time.Time
}
type UserListFilter struct {
Username, NickName, Phone, Email, OrderKey string
Desc bool
}
type Authority struct {
AuthorityID uint
AuthorityName string
ParentID *uint
DataScope int
DefaultRouter string
}
type Menu struct {
ID uint
ParentID uint
Path string
Name string
Hidden bool
Component string
Sort int
ActiveName string
KeepAlive bool
DefaultMenu bool
Title string
Icon string
CloseTab bool
TransitionType string
Children []*Menu
Buttons []*MenuButton
}
type SystemRepo interface {
IsInitialized(context.Context) (bool, error)
Initialize(context.Context) error
FindUserByUsername(context.Context, string) (*User, error)
FindUserByID(context.Context, uint) (*User, error)
MenusByAuthority(context.Context, uint) ([]*Menu, error)
ListUsers(context.Context, int, int, *UserListFilter) ([]*User, int64, error)
CreateUser(context.Context, *User) (*User, error)
UpdateUser(context.Context, *User) error
DeleteUser(context.Context, uint) error
UpdatePassword(context.Context, uint, string) error
SetUserAuthorities(context.Context, uint, []uint) error
SetUserAuthority(context.Context, uint, uint) error
SetUserSetting(context.Context, uint, map[string]any) error
ListAuthorities(context.Context) ([]*Authority, error)
}
type SystemUsecase struct {
repo SystemRepo
cache Cache
files FileStorage
}
func NewSystemUsecase(repo SystemRepo, cache Cache, files FileStorage) *SystemUsecase {
return &SystemUsecase{repo: repo, cache: cache, files: files}
}
func (uc *SystemUsecase) CacheGet(ctx context.Context, key string) (string, bool, error) {
return uc.cache.Get(ctx, key)
}
func (uc *SystemUsecase) CacheSet(ctx context.Context, key, value string, expiration time.Duration) error {
return uc.cache.Set(ctx, key, value, expiration)
}
func (uc *SystemUsecase) CacheDelete(ctx context.Context, key string) error {
return uc.cache.Delete(ctx, key)
}
func (uc *SystemUsecase) IsInitialized(ctx context.Context) (bool, error) {
return uc.repo.IsInitialized(ctx)
}
func (uc *SystemUsecase) Initialize(ctx context.Context) error { return uc.repo.Initialize(ctx) }
func (uc *SystemUsecase) Login(ctx context.Context, username, password string) (*User, error) {
u, err := uc.repo.FindUserByUsername(ctx, username)
if err != nil || bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) != nil {
return nil, ErrInvalidCredentials
}
return u, nil
}
func (uc *SystemUsecase) User(ctx context.Context, id uint) (*User, error) {
return uc.repo.FindUserByID(ctx, id)
}
func (uc *SystemUsecase) Menus(ctx context.Context, authorityID uint) ([]*Menu, error) {
return uc.repo.MenusByAuthority(ctx, authorityID)
}
func (uc *SystemUsecase) ListUsers(ctx context.Context, page, pageSize int, filter *UserListFilter) ([]*User, int64, error) {
return uc.repo.ListUsers(ctx, page, pageSize, filter)
}
func (uc *SystemUsecase) CreateUser(ctx context.Context, user *User) (*User, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
user.Password = string(hash)
return uc.repo.CreateUser(ctx, user)
}
func (uc *SystemUsecase) UpdateUser(ctx context.Context, user *User) error {
return uc.repo.UpdateUser(ctx, user)
}
func (uc *SystemUsecase) DeleteUser(ctx context.Context, id uint) error {
return uc.repo.DeleteUser(ctx, id)
}
func (uc *SystemUsecase) ResetPassword(ctx context.Context, id uint, password string) error {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
return uc.repo.UpdatePassword(ctx, id, string(hash))
}
func (uc *SystemUsecase) ChangePassword(ctx context.Context, id uint, oldPassword, newPassword string) error {
user, err := uc.repo.FindUserByID(ctx, id)
if err != nil {
return err
}
if bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(oldPassword)) != nil {
return ErrInvalidCredentials
}
return uc.ResetPassword(ctx, id, newPassword)
}
func (uc *SystemUsecase) Authorities(ctx context.Context) ([]*Authority, error) {
return uc.repo.ListAuthorities(ctx)
}
func (uc *SystemUsecase) SetUserAuthorities(ctx context.Context, id uint, authorityIDs []uint) error {
if len(authorityIDs) == 0 {
return ErrInvalidCredentials
}
return uc.repo.SetUserAuthorities(ctx, id, authorityIDs)
}
func (uc *SystemUsecase) SetUserAuthority(ctx context.Context, id, authorityID uint) error {
return uc.repo.SetUserAuthority(ctx, id, authorityID)
}
func (uc *SystemUsecase) SetUserSetting(ctx context.Context, id uint, setting map[string]any) error {
return uc.repo.SetUserSetting(ctx, id, setting)
}
+45
View File
@@ -0,0 +1,45 @@
package biz
import (
"context"
"time"
)
type TimedTask struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name, Description, Spec string
WithSeconds bool
ExecutorType, MethodName string
Params []byte
HTTPURL, HTTPMethod string
HTTPHeader []byte
HTTPBody string
HTTPAllowPrivate, Enabled bool
EnabledFilter *bool
}
type TimedTaskLog struct {
ID uint
CreatedAt time.Time
TaskID uint
TaskName, TriggerType string
StartedAt, FinishedAt time.Time
DurationMS int64
Status, ErrorMsg, Output string
}
type TaskRepo interface {
CreateTask(context.Context, *TimedTask) error
UpdateTask(context.Context, *TimedTask) error
DeleteTask(context.Context, uint) error
FindTask(context.Context, uint) (*TimedTask, error)
ListTasks(context.Context, int, int, *TimedTask) ([]*TimedTask, int64, error)
ToggleTask(context.Context, uint, bool) error
RecordTaskLog(context.Context, *TimedTaskLog) error
ListTaskLogs(context.Context, int, int, uint, string) ([]*TimedTaskLog, int64, error)
CleanupLogs(context.Context, time.Time) error
}
type TaskUsecase struct{ repo TaskRepo }
func NewTaskUsecase(repo TaskRepo) *TaskUsecase { return &TaskUsecase{repo: repo} }
func (uc *TaskUsecase) Repo() TaskRepo { return uc.repo }
+37
View File
@@ -0,0 +1,37 @@
package biz
import (
"context"
"time"
)
type Version struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
Code string
Description string
Data string
}
type VersionBundle struct {
Name string
Code string
Description string
ExportTime string
Menus []*Menu
APIs []*API
Dictionaries []*Dictionary
}
type VersionRepo interface {
CreateVersion(context.Context, *Version) error
DeleteVersions(context.Context, []uint) error
FindVersion(context.Context, uint) (*Version, error)
ListVersions(context.Context, int, int, string, string) ([]*Version, int64, error)
BuildVersionBundle(context.Context, []uint, []uint, []uint) (*VersionBundle, error)
ImportVersionBundle(context.Context, *VersionBundle) error
}
type VersionUsecase struct{ repo VersionRepo }
func NewVersionUsecase(repo VersionRepo) *VersionUsecase { return &VersionUsecase{repo: repo} }
func (uc *VersionUsecase) Repo() VersionRepo { return uc.repo }