refactor: separate backend domains and storage adapters

This commit is contained in:
2026-08-14 01:37:56 +08:00
parent e6775f0966
commit 8c4c6ba661
102 changed files with 6644 additions and 5735 deletions
+28
View File
@@ -0,0 +1,28 @@
package biz
import (
"context"
"time"
)
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 APITokenRepo interface {
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)
BlacklistToken(context.Context, string) error
IsTokenDisabled(context.Context, string) (bool, error)
}
+49
View File
@@ -0,0 +1,49 @@
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 DictionaryRepo 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)
}
+9 -146
View File
@@ -5,13 +5,12 @@ import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"github.com/google/uuid"
"io"
"path/filepath"
"sort"
"strings"
"time"
"github.com/google/uuid"
)
type MediaFile struct {
@@ -33,21 +32,7 @@ type AttachmentCategory struct {
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 {
type MediaMetadataRepo interface {
CreateMedia(context.Context, *MediaFile) error
FindMedia(context.Context, uint) (*MediaFile, error)
FindMediaByHash(context.Context, uint, string) (*MediaFile, error)
@@ -59,17 +44,13 @@ type MediaRepo interface {
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
StaleUploadSessionIDs(context.Context, time.Time) ([]uint, error)
DeleteUploadData(context.Context, uint) error
}
type MediaRepo interface {
MediaMetadataRepo
UploadRepo
}
type MediaUsecase struct {
repo MediaRepo
files FileStorage
@@ -127,124 +108,6 @@ func (uc *MediaUsecase) Delete(ctx context.Context, id uint) error {
}
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) CleanupStale(ctx context.Context, ttlHours int) error {
if ttlHours <= 0 {
ttlHours = 24
}
ids, err := uc.repo.StaleUploadSessionIDs(ctx, time.Now().Add(-time.Duration(ttlHours)*time.Hour))
if err != nil {
return err
}
for _, id := range ids {
if err = uc.repo.DeleteUploadData(ctx, id); err != nil {
return err
}
if err = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", id)); err != nil {
return err
}
}
return nil
}
func (uc *MediaUsecase) ListStorage(ctx context.Context, prefix, cursor string, limit int) ([]*StoredFile, string, bool, error) {
return uc.files.List(ctx, prefix, cursor, limit)
}
+162
View File
@@ -0,0 +1,162 @@
package biz
import (
"context"
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"io"
"path/filepath"
"sort"
"strings"
"time"
"github.com/google/uuid"
)
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 UploadRepo interface {
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
StaleUploadSessionIDs(context.Context, time.Time) ([]uint, error)
DeleteUploadData(context.Context, uint) error
}
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) CleanupStale(ctx context.Context, ttlHours int) error {
if ttlHours <= 0 {
ttlHours = 24
}
ids, err := uc.repo.StaleUploadSessionIDs(ctx, time.Now().Add(-time.Duration(ttlHours)*time.Hour))
if err != nil {
return err
}
for _, id := range ids {
if err = uc.repo.DeleteUploadData(ctx, id); err != nil {
return err
}
if err = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", id)); err != nil {
return err
}
}
return nil
}
+24
View File
@@ -0,0 +1,24 @@
package biz
import (
"context"
"time"
)
type SystemParameter struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
Key string
Value string
Desc string
}
type ParameterRepo interface {
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)
}
+31
View File
@@ -0,0 +1,31 @@
package biz
import "context"
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 SecurityRepo interface {
SecurityConfig(context.Context) (*SecurityConfig, error)
SaveSecurityConfig(context.Context, *SecurityConfig) error
}
+4 -106
View File
@@ -1,112 +1,10 @@
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)
BlacklistToken(context.Context, string) error
IsTokenDisabled(context.Context, string) (bool, error)
SecurityConfig(context.Context) (*SecurityConfig, error)
SaveSecurityConfig(context.Context, *SecurityConfig) error
DictionaryRepo
ParameterRepo
APITokenRepo
SecurityRepo
}
type SettingsUsecase struct{ repo SettingsRepo }
+2 -145
View File
@@ -3,69 +3,11 @@ 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 DatabaseConfig struct {
Driver, Host, Port, User, Password, Name, Path, Config, Template, AdminPassword string
APIs []*API
}
type Authority struct {
AuthorityID uint
AuthorityName string
ParentID *uint
DataScope int
DefaultRouter string
}
type SystemRepo interface {
IsInitialized(context.Context) (bool, error)
Initialize(context.Context, *DatabaseConfig) 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, bool) 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)
PersistConfig(context.Context) error
PersistAdminConfig(context.Context, []byte) error
ReloadConfig(context.Context) error
InitializationRepo
UserRepo
}
type SystemUsecase struct {
@@ -90,88 +32,3 @@ func (uc *SystemUsecase) CacheDelete(ctx context.Context, key string) error {
func (uc *SystemUsecase) CacheIncrement(ctx context.Context, key string, expiration time.Duration) (int64, error) {
return uc.cache.Increment(ctx, key, expiration)
}
func (uc *SystemUsecase) IsInitialized(ctx context.Context) (bool, error) {
return uc.repo.IsInitialized(ctx)
}
func (uc *SystemUsecase) Initialize(ctx context.Context, config *DatabaseConfig) error {
if config == nil || len(config.AdminPassword) < 6 {
return ErrInvalidCredentials
}
return uc.repo.Initialize(ctx, config)
}
func (uc *SystemUsecase) PersistConfig(ctx context.Context) error { return uc.repo.PersistConfig(ctx) }
func (uc *SystemUsecase) PersistAdminConfig(ctx context.Context, value []byte) error {
return uc.repo.PersistAdminConfig(ctx, value)
}
func (uc *SystemUsecase) ReloadConfig(ctx context.Context) error { return uc.repo.ReloadConfig(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), false)
}
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
}
hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
return err
}
return uc.repo.UpdatePassword(ctx, id, string(hash), true)
}
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)
}
+31
View File
@@ -0,0 +1,31 @@
package biz
import "context"
type DatabaseConfig struct {
Driver, Host, Port, User, Password, Name, Path, Config, Template, AdminPassword string
APIs []*API
}
type InitializationRepo interface {
IsInitialized(context.Context) (bool, error)
Initialize(context.Context, *DatabaseConfig) error
PersistConfig(context.Context) error
PersistAdminConfig(context.Context, []byte) error
ReloadConfig(context.Context) error
}
func (uc *SystemUsecase) IsInitialized(ctx context.Context) (bool, error) {
return uc.repo.IsInitialized(ctx)
}
func (uc *SystemUsecase) Initialize(ctx context.Context, config *DatabaseConfig) error {
if config == nil || len(config.AdminPassword) < 6 {
return ErrInvalidCredentials
}
return uc.repo.Initialize(ctx, config)
}
func (uc *SystemUsecase) PersistConfig(ctx context.Context) error { return uc.repo.PersistConfig(ctx) }
func (uc *SystemUsecase) PersistAdminConfig(ctx context.Context, value []byte) error {
return uc.repo.PersistAdminConfig(ctx, value)
}
func (uc *SystemUsecase) ReloadConfig(ctx context.Context) error { return uc.repo.ReloadConfig(ctx) }
+129
View File
@@ -0,0 +1,129 @@
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 UserRepo interface {
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, bool) 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)
}
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), false)
}
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
}
hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
return err
}
return uc.repo.UpdatePassword(ctx, id, string(hash), true)
}
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)
}