215 lines
5.7 KiB
Go
215 lines
5.7 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"filestoragesystem/internal/config"
|
|
"filestoragesystem/internal/model"
|
|
"filestoragesystem/internal/repository"
|
|
"filestoragesystem/internal/utils"
|
|
"filestoragesystem/pkg/apperr"
|
|
)
|
|
|
|
// AdminService 系统管理服务(设置/日志/备份)
|
|
type AdminService struct {
|
|
cfg *config.Config
|
|
db *gorm.DB
|
|
settingRepo *repository.SettingRepo
|
|
opLogRepo *repository.OpLogRepo
|
|
sysLogRepo *repository.SysLogRepo
|
|
}
|
|
|
|
// ---- 系统设置 ----
|
|
|
|
// GetSettings 获取全部设置(按分组组织)
|
|
func (s *AdminService) GetSettings() (map[string][]model.SystemSetting, error) {
|
|
settings, err := s.settingRepo.GetAll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
groups := make(map[string][]model.SystemSetting)
|
|
for _, st := range settings {
|
|
groups[st.GroupName] = append(groups[st.GroupName], st)
|
|
}
|
|
return groups, nil
|
|
}
|
|
|
|
// GetSettingsByGroup 按分组获取设置
|
|
func (s *AdminService) GetSettingsByGroup(group string) ([]model.SystemSetting, error) {
|
|
return s.settingRepo.GetByGroup(group)
|
|
}
|
|
|
|
// UpdateSettings 批量更新设置(仅更新已存在的键)
|
|
func (s *AdminService) UpdateSettings(values map[string]string) error {
|
|
if len(values) == 0 {
|
|
return nil
|
|
}
|
|
existing := make(map[string]bool)
|
|
all, err := s.settingRepo.GetAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, st := range all {
|
|
existing[st.Key] = true
|
|
}
|
|
valid := make(map[string]string)
|
|
for k, v := range values {
|
|
if existing[k] {
|
|
valid[k] = v
|
|
}
|
|
}
|
|
if len(valid) == 0 {
|
|
return fmt.Errorf("没有可更新的有效设置项")
|
|
}
|
|
return s.settingRepo.UpdateValues(valid)
|
|
}
|
|
|
|
// ---- 日志 ----
|
|
|
|
// ListOperationLogs 操作日志列表
|
|
func (s *AdminService) ListOperationLogs(f repository.OpLogFilter) ([]model.OperationLog, int64, error) {
|
|
if f.Page <= 0 {
|
|
f.Page = 1
|
|
}
|
|
if f.PageSize <= 0 {
|
|
f.PageSize = 20
|
|
}
|
|
return s.opLogRepo.List(f)
|
|
}
|
|
|
|
// GetOperationLog 操作日志详情
|
|
func (s *AdminService) GetOperationLog(id uint) (*model.OperationLog, error) {
|
|
l, err := s.opLogRepo.FindByID(id)
|
|
if err != nil {
|
|
return nil, apperr.ErrNotFound
|
|
}
|
|
return l, nil
|
|
}
|
|
|
|
// ListSystemLogs 系统日志列表
|
|
func (s *AdminService) ListSystemLogs(f repository.SysLogFilter) ([]model.SystemLog, int64, error) {
|
|
if f.Page <= 0 {
|
|
f.Page = 1
|
|
}
|
|
if f.PageSize <= 0 {
|
|
f.PageSize = 20
|
|
}
|
|
return s.sysLogRepo.List(f)
|
|
}
|
|
|
|
// GetSystemLog 系统日志详情
|
|
func (s *AdminService) GetSystemLog(id uint) (*model.SystemLog, error) {
|
|
l, err := s.sysLogRepo.FindByID(id)
|
|
if err != nil {
|
|
return nil, apperr.ErrNotFound
|
|
}
|
|
return l, nil
|
|
}
|
|
|
|
// CleanupLogs 清理days天前的日志
|
|
func (s *AdminService) CleanupLogs(days int) (opDeleted, sysDeleted int64, err error) {
|
|
if days <= 0 {
|
|
days = 30
|
|
}
|
|
opDeleted, err = s.opLogRepo.DeleteBefore(days)
|
|
if err != nil {
|
|
return
|
|
}
|
|
sysDeleted, err = s.sysLogRepo.DeleteBefore(days)
|
|
utils.Info("system", "清理%d天前日志:操作日志%d条,系统日志%d条", days, opDeleted, sysDeleted)
|
|
return
|
|
}
|
|
|
|
// ---- 备份 ----
|
|
|
|
// BackupDatabase 使用VACUUM INTO备份数据库
|
|
func (s *AdminService) BackupDatabase() (string, error) {
|
|
if err := os.MkdirAll(s.cfg.Storage.Backups, 0o755); err != nil {
|
|
return "", err
|
|
}
|
|
name := fmt.Sprintf("backup-%s.db", time.Now().Format("20060102-150405"))
|
|
target := filepath.Join(s.cfg.Storage.Backups, name)
|
|
// SQLite安全备份:VACUUM INTO
|
|
quoted := strings.ReplaceAll(target, "'", "''")
|
|
if err := s.db.Exec(fmt.Sprintf("VACUUM INTO '%s'", quoted)).Error; err != nil {
|
|
utils.Error("system", "数据库备份失败: %v", err)
|
|
return "", fmt.Errorf("备份失败: %w", err)
|
|
}
|
|
utils.Info("system", "数据库备份完成: %s", name)
|
|
return name, nil
|
|
}
|
|
|
|
// ListBackups 备份列表
|
|
func (s *AdminService) ListBackups() ([]map[string]interface{}, error) {
|
|
entries, err := os.ReadDir(s.cfg.Storage.Backups)
|
|
if err != nil {
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
var list []map[string]interface{}
|
|
for _, e := range entries {
|
|
if e.IsDir() || !strings.HasSuffix(e.Name(), ".db") {
|
|
continue
|
|
}
|
|
info, _ := e.Info()
|
|
list = append(list, map[string]interface{}{
|
|
"name": e.Name(),
|
|
"size": info.Size(),
|
|
"time": info.ModTime().Format(time.RFC3339),
|
|
})
|
|
}
|
|
sort.Slice(list, func(i, j int) bool {
|
|
return list[i]["name"].(string) > list[j]["name"].(string)
|
|
})
|
|
return list, nil
|
|
}
|
|
|
|
// BackupPath 校验备份文件名并返回其物理路径(供下载)
|
|
func (s *AdminService) BackupPath(name string) (string, error) {
|
|
// 只允许 backup-*.db 格式,防止路径穿越
|
|
if strings.ContainsAny(name, "/\\") || strings.Contains(name, "..") || !strings.HasPrefix(name, "backup-") || !strings.HasSuffix(name, ".db") {
|
|
return "", fmt.Errorf("非法的备份文件名")
|
|
}
|
|
p := filepath.Join(s.cfg.Storage.Backups, name)
|
|
if _, err := os.Stat(p); err != nil {
|
|
return "", fmt.Errorf("备份文件不存在")
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// ---- 站点状态 ----
|
|
|
|
// SiteStatus 公开的站点状态信息
|
|
func (s *AdminService) SiteStatus() map[string]interface{} {
|
|
get := func(k, def string) string {
|
|
if v, err := s.settingRepo.GetValue(k); err == nil {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
return map[string]interface{}{
|
|
"site_name": get("site_name", "云泽文件存储云平台"),
|
|
"site_description": get("site_description", ""),
|
|
"site_logo": get("site_logo", ""),
|
|
"site_icp": get("site_icp", ""),
|
|
"site_copyright": get("site_copyright", ""),
|
|
"site_enabled": get("site_enabled", "true") == "true",
|
|
"register_enabled": get("register_enabled", "true") == "true",
|
|
"maintenance_msg": get("site_maintenance_msg", ""),
|
|
}
|
|
}
|
|
|
|
// IsSiteEnabled 站点是否开启
|
|
func (s *AdminService) IsSiteEnabled() bool {
|
|
if v, err := s.settingRepo.GetValue("site_enabled"); err == nil {
|
|
return v == "true" || v == "1"
|
|
}
|
|
return true
|
|
}
|