Files
jiang13-bbs/backend/service/operations_maintenance.go

139 lines
4.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"context"
"errors"
"github.com/freefire/jiang13-bbs/model"
"github.com/freefire/jiang13-bbs/version"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"os"
"path/filepath"
"strings"
"time"
)
func (o *Operations) ResetPassword(email, code, password string) error {
cfg, e := o.Security()
if e != nil || !cfg.PasswordReset {
return errors.New("找回密码未开启")
}
if len(password) < 6 || len(password) > 64 {
return errors.New("密码长度须为 6–64 字节")
}
hash, e := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if e != nil {
return e
}
return o.db.Transaction(func(tx *gorm.DB) error {
scoped := *o
scoped.db = tx
if e := scoped.ConsumeCode(email, "reset", code); e != nil {
return e
}
var users []model.User
if e := tx.Where("LOWER(email) = ?", strings.ToLower(strings.TrimSpace(email))).Limit(2).Find(&users).Error; e != nil {
return e
}
if len(users) != 1 {
return errors.New("账号不可用")
}
if e := tx.Model(&model.User{}).Where("id = ?", users[0].ID).Updates(map[string]any{
"password": string(hash),
"token_version": gorm.Expr("token_version + 1"),
}).Error; e != nil {
return e
}
return tx.Model(&model.RefreshToken{}).Where("user_id = ? AND revoked = ?", users[0].ID, false).
Updates(map[string]any{"revoked": true, "token_cipher": ""}).Error
})
}
func (o *Operations) Diagnostics(ctx context.Context) map[string]any {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
dbState := "正常"
sql, e := o.db.DB()
if e != nil {
dbState = "异常"
} else if sql.PingContext(ctx) != nil {
dbState = "异常"
}
local := "正常"
if o.LocalWritable() != nil {
local = "异常"
}
v, _, e := o.read(o.db, "storage")
storage := "未知"
if e == nil {
storage = "本地:" + local
if v.(*StorageConfig).Backend == "s3" {
storage = "S3:未知(使用存储页测试)"
}
}
var last model.SettingsAudit
result := map[string]any{"state": "未知"}
if o.db.Where("module = ? AND action = ?", "mail", "test").Order("id desc").First(&last).Error == nil {
result = map[string]any{"result": last.Result, "tested_at": last.CreatedAt, "note": "历史测试,不代表持续健康"}
}
var queued int64
queueErr := o.db.WithContext(ctx).Model(&model.MailTask{}).Where("status IN ?", []string{"queued", "retry", "sending"}).Count(&queued).Error
queueState := "正常 · PostgreSQL 持久邮件队列"
if queueErr != nil {
queueState = "异常 · 暂时无法读取邮件队列"
}
return map[string]any{"version": version.Version, "uptime_seconds": int(time.Since(o.Started).Seconds()), "database": dbState, "cache": "未配置独立应用缓存", "tasks": queueState, "queued_mail": queued, "storage": storage, "mail_test": result}
}
// Draft references live in browsers, so unattached database rows are NEVER deleted.
func (o *Operations) ScanTemporary() (map[string]any, error) {
m, e := o.Maintenance()
if e != nil {
return nil, e
}
count, size := 0, int64(0)
cutoff := time.Now().Add(-time.Duration(m.TempDays) * 24 * time.Hour)
for _, dir := range []string{filepath.Join(o.cfg.DataDir, "uploads"), filepath.Join(o.cfg.DataDir, "private")} {
e = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.Type()&os.ModeSymlink != 0 {
return nil
}
if d.IsDir() || !strings.HasSuffix(d.Name(), ".partial") {
return nil
}
info, e := d.Info()
if e != nil {
return e
}
if info.ModTime().Before(cutoff) {
count++
size += info.Size()
}
return nil
})
if e != nil {
return nil, e
}
}
candidates, e := o.temporaryCandidates()
if e != nil {
return nil, e
}
var drafts int64
if e = o.db.Model(&model.PostAttachment{}).Where("post_id = 0").Count(&drafts).Error; e != nil {
return nil, e
}
return map[string]any{"candidates": candidates, "expired_partial_count": count, "expired_partial_bytes": size, "protected_draft_attachments": drafts, "retention_days": m.TempDays, "message": "保留所有已入库图片和草稿附件。只清理受本版本上传锁保护且已过期的候选文件;未知来源的旧 partial 不在线删除。"}, nil
}
func (o *Operations) ClearMailLogs() (int64, error) {
v, _, e := o.read(o.db, "mail")
if e != nil {
return 0, e
}
c := v.(*MailConfig)
r := o.db.Where("status IN ? AND updated_at < ?", []string{"accepted", "failed"}, time.Now().Add(-time.Duration(c.Retention)*24*time.Hour)).Delete(&model.MailTask{})
return r.RowsAffected, r.Error
}