refactor: separate backend domains and storage adapters
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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, ©); err == nil {
|
||||
return nil, ©, 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)
|
||||
}
|
||||
|
||||
@@ -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, ©); err == nil {
|
||||
return nil, ©, 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user