**新增功能:**
用户名编辑:输入框替换静态文本,白名单验证(中文/英文/数字/下划线/连字符),前端计数器(n/16),utf8对齐PG VARCHAR,XSS防控。
个性签名编辑:Textarea,128字符上限,实时计数器。
头像上传管线:校验→解码→裁切→CatmullRom缩放→WebP二分编码≤100KB→原子写入(.tmp→os.Rename)。限制5MB,128-3840px,JPEG/PNG/WebP。输出512x512 WebP。文件名 {uid}_{timestamp}.webp。清理旧头像。
自定义裁切弹窗:浅色主题,固定裁切框+图片平移/缩放(1×-3×滚轮),box-shadow遮罩,三等分网格。坐标映射pan/zoom→原图像素→subImage。
CSP修复:img-src允许data:URI(FileReader预览)。
**文件变更:**
修改: README, docs/{development,structure}.md, go.{mod,sum}, cmd/server/main.go, controller/settings, middleware/security, router, templates/settings/{index.html,css}
新增: internal/service/avatar_service.go, docs/settings.md
74 lines
2.0 KiB
Go
74 lines
2.0 KiB
Go
package main
|
||
|
||
import (
|
||
"log"
|
||
|
||
"metazone.cc/metalab/internal/config"
|
||
"metazone.cc/metalab/internal/model"
|
||
"metazone.cc/metalab/internal/router"
|
||
"metazone.cc/metalab/internal/theme"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/driver/postgres"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
func main() {
|
||
// 配置
|
||
cfg := config.Load("config.yaml")
|
||
|
||
// 从配置初始化角色系统(OCP:新增角色无需修改源代码)
|
||
model.InitRoles(cfg.Roles.Levels, cfg.Roles.Names, cfg.Roles.Permissions.OperableRoles)
|
||
|
||
// 数据库
|
||
db, err := gorm.Open(postgres.Open(cfg.Database.DSN()), &gorm.Config{})
|
||
if err != nil {
|
||
log.Fatalf("连接数据库失败: %v", err)
|
||
}
|
||
if err := db.AutoMigrate(&model.User{}); err != nil {
|
||
log.Fatalf("数据库迁移失败: %v", err)
|
||
}
|
||
|
||
// 邮箱唯一索引迁移:从全表唯一改为部分唯一(仅 deleted_at IS NULL 的行)
|
||
// 支持软删除用户邮箱复用
|
||
migrations := []string{
|
||
`DROP INDEX IF EXISTS idx_users_email`,
|
||
`CREATE INDEX IF NOT EXISTS idx_users_email ON users (email)`,
|
||
`CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email_active ON users (email) WHERE deleted_at IS NULL`,
|
||
}
|
||
for _, m := range migrations {
|
||
if err := db.Exec(m).Error; err != nil {
|
||
log.Fatalf("数据库迁移失败 (%s): %v", m, err)
|
||
}
|
||
}
|
||
log.Println("数据库连接成功")
|
||
|
||
// Gin 模式
|
||
gin.SetMode(cfg.Server.Mode)
|
||
|
||
r := gin.Default()
|
||
|
||
// 模板加载(前端主题 + 管理后台)
|
||
tmpl, err := theme.LoadTemplates(
|
||
theme.TemplateRoot{Dir: "templates/MetaLab-2026/html", Prefix: ""},
|
||
theme.TemplateRoot{Dir: "templates/admin/html", Prefix: "admin/"},
|
||
)
|
||
if err != nil {
|
||
panic("加载模板失败: " + err.Error())
|
||
}
|
||
r.SetHTMLTemplate(tmpl)
|
||
|
||
// 静态资源
|
||
r.Static("/static", "./templates/MetaLab-2026/static")
|
||
r.Static("/admin/static", "./templates/admin/static")
|
||
r.Static("/shared/static", "./templates/shared/static")
|
||
r.Static("/uploads/avatars", "./storage/uploads/avatars")
|
||
|
||
// 路由注册
|
||
router.Setup(r, db, cfg)
|
||
|
||
// 启动
|
||
log.Printf("MetaZone.FAN 启动于 0.0.0.0:%s", cfg.Server.Port)
|
||
r.Run("0.0.0.0:" + cfg.Server.Port)
|
||
}
|