diff --git a/README.md b/README.md index a35cd8f..d3b43cf 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ MetaLab 是一个正在开发中的开源技术社区平台,旨在为开发者 - ✅ **安全头**——Content-Security-Policy 等安全响应头 - ✅ **管理后台**——用户管理、角色权限控制 - ✅ **软删除**——用户数据可恢复,登录时自动恢复 -- ✅ **用户设置页**——个人资料设置 +- ✅ **用户设置页**——个人资料修改(用户名、个性签名、头像上传与裁切) +- ✅ **头像处理**——JPEG/PNG/WebP 上传,方形裁切,512×512 WebP 编码 ≤100KB - ✅ **多主题支持**——可切换的前端主题系统 ## 计划中 diff --git a/cmd/server/main.go b/cmd/server/main.go index d4856d3..349db07 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -62,6 +62,7 @@ func main() { 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) diff --git a/docs/development.md b/docs/development.md index 59d8b92..ef2a3e7 100644 --- a/docs/development.md +++ b/docs/development.md @@ -378,6 +378,37 @@ RememberMe bool `json:"remember_me"` --- +### 14. 个人设置页与头像上传 + +**涉及文件**:参考 [`docs/settings.md`](settings.md) 获取完整实现文档。 + +**功能概要**: + +| 功能 | 实现文件 | +|------|----------| +| 用户名编辑 | `auth_service.go` — `UpdateProfile()`,正则 `[\p{Han}a-zA-Z0-9_-]` 白名单 | +| 个性签名编辑 | 同上,0-128 字符 | +| 头像处理管线 | `avatar_service.go`(新文件)— 解码→裁切→CatmullRom缩放→WebP二分编码→原子写入 | +| 前端裁切弹窗 | `settings/index.html` — 固定框 + 图片平移/缩放 + 坐标映射 | +| CSS样式 | `settings.css` — 浅色主题裁切弹窗、Toast通知、输入框样式 | +| CSP放宽 | `security.go` — `img-src 'self' data:`(裁切预览用data URI) | +| 静态路由 | `main.go` — `/uploads/avatars` → `./storage/uploads/avatars` | + +**设计决策**: + +| 决策 | 选择 | 原因 | +|------|------|------| +| 用户名校验 | 白名单 regex | 比黑名单更安全,直接排除 `<>&"'` 等 XSS 向量 | +| 字符计数 | `utf8.RuneCountInString` | 确保与 PostgreSQL `varchar(16)` 语义一致 | +| 头像编码 | WebP + 二分查找质量 | 目标 ≤100KB,兼顾质量与体积 | +| 原子写入 | `.tmp` → `os.Rename` | 防止写入中断产生损坏文件 | +| 裁切方式 | 固定框 + 图片平移/缩放 | 框大小不变,zoom使框覆盖更小区域,真正支持精确定位 | +| GIF支持 | 不支持 | 裁切后无法保持动画,无实际用途 | +| 最小分辨率 | 128×128 | 防止马赛克图被拉升到 512×512 | +| CSP data: URI | 显式允许 img-src | 裁切预览使用 FileReader → data URI | + +--- + ## 二、解决方案建议 ### 2.1 PostgreSQL 密码认证(问题一记录) @@ -505,7 +536,7 @@ config.yaml # 主配置(服务/数据库/JWT 含 remember_expire/BCry | ~~**P0**~~ | ~~登出功能~~ | ✅ 已完成 | `POST /api/auth/logout` → 清除三个 Cookie + 跳转首页 | | **P1** | 首页完善 | 待做 | 当前仅有 Hero 区域,需添加帖子列表、话题导航等 | | **P1** | 帖子系统 | 待做 | CRUD + 话题分类 + Markdown 编辑 | -| **P1** | 用户设置页 | 待做 | `/settings` — 修改用户名/头像/密码/个人简介 | +| **P1** | 用户设置页 | ✅ 已完成 | `/settings/profile`、`/settings/account` — 用户名修改、个性签名、头像上传与裁切 | | **P2** | 管理后台 | 待做 | `/admin` — 用户管理、内容审核、主题切换 | | ~~**P2**~~ | ~~刷新令牌~~ | ✅ 已完成 | 记住我 + access/refresh 双 Token + 自动续期 + 登出 | | **P3** | 多主题支持 | 待做 | `MetaLab-2026` → `MetaLab-2027` 切换 | diff --git a/docs/settings.md b/docs/settings.md new file mode 100644 index 0000000..4dd3113 --- /dev/null +++ b/docs/settings.md @@ -0,0 +1,195 @@ +# 个人设置功能 + +> 实现日期:2026-05-27 + +## 概述 + +个人设置是用户修改公开资料和头像的功能模块,包含三个子功能: + +1. **用户名编辑** — 输入框替换静态文本,白名单字符 + XSS 过滤 +2. **个性签名编辑** — Textarea 控件,128 字符上限 +3. **头像上传与裁切** — 完整图片处理管线,WebP 编码 ≤100KB + +## 页面结构 + +``` +/settings/profile — 个人资料(用户名、头像、个性签名) +/settings/account — 账号信息(只读:UID、邮箱、状态、注册时间) +``` + +左侧导航栏切换 `profile` / `account` 两个 tab,通过 `:tab` 路由参数区分。 + +## 用户名编辑 + +### 规则 + +| 项目 | 规格 | +|------|------| +| 允许字符 | 中文、英文大小写、数字、下划线(`_`)、连字符(`-`) | +| 前后端正则 | Go: `^[\p{Han}a-zA-Z0-9_-]+$` / JS: `^[\u4e00-\u9fffa-zA-Z0-9_-]+$` | +| 长度限制 | 1-16 字符(Unicode `utf8.RuneCountInString`) | +| XSS 防护 | 白名单排除 `<>&"'` 等 HTML 特殊字符;Go `html/template` 自动转义 | +| 去重 | 变更时检查 `ExistsByUsername`,冲突返回 409 | + +### API + +``` +PUT /api/settings/profile +Content-Type: application/json +{ "username": "...", "bio": "..." } + +成功: { "success": true, "message": "个人资料已更新" } +冲突: 409 { "message": "该用户名已被占用" } +无效: 400 { "message": "用户名格式无效..." } +``` + +## 个性签名编辑 + +| 项目 | 规格 | +|------|------| +| 控件 | `