From c3be042acb45b3c68674abc4dca7653f36357267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=96=87=E5=B3=B0?= Date: Mon, 1 Jun 2026 19:51:19 +0800 Subject: [PATCH] up --- .workbuddy/memory/2026-06-01.md | 43 +++---------- internal/web/handlers/admin.go | 63 +++++++++++++++++-- internal/web/server.go | 4 +- internal/web/templates/admin/domain_form.html | 11 +++- internal/web/templates/admin/mail_view.html | 60 ++++++++++++++++++ internal/web/templates/admin/mails.html | 2 +- 6 files changed, 140 insertions(+), 43 deletions(-) create mode 100644 internal/web/templates/admin/mail_view.html diff --git a/.workbuddy/memory/2026-06-01.md b/.workbuddy/memory/2026-06-01.md index 5cbcd2d..0000e0f 100644 --- a/.workbuddy/memory/2026-06-01.md +++ b/.workbuddy/memory/2026-06-01.md @@ -1,37 +1,8 @@ -# 2026-06-01 MailGo 项目开发完成 +# 2026-06-01 -## 完成内容 -- 完整Go邮件系统 MailGo 开发,通过2轮QA验证 -- 核心组件: SMTP(go-smtp) + IMAP(go-imap v1) + POP3(手写TCP) + Web(Gin) -- Web功能: 登录认证、收件箱、草稿箱、发件箱、撰写邮件(含附件)、设置(修改密码)、管理后台(域名/用户/DNS提示) -- 模板架构从base继承模式经3轮迭代重构为自包含+子模板模式 -- 默认管理员: admin@example.com / admin -- Windows测试路径: ./win/etc/mail_go + ./win/srv/mail_go - -## QA结果 -- Round 1: 12/15 通过,3项失败(/drafts和/settings路由缺失,/admin路径误测) -- Round 2: 12/12 全部通过,修复后回归验证完成 - -## 关键修复 -- SMTP ListenAndServeTLS 签名问题 → 创建独立TLS Server实例 -- IMAP v2 beta API不稳定 → 切换v1 -- 模板 {{template .VarName .}} 不支持 → 重构为自包含模式 -- 补充 /drafts、/settings 路由及密码修改功能 - -## 增强需求实现(v2) -- DKIM私钥自动生成: 域名创建时自动生成RSA 2048密钥对,DNS提示页显示DKIM TXT记录 -- 域名编辑: /admin/domains/:id/edit,可修改端口/TLS/重新生成DKIM -- 附件配额限制: 上传时检查用户QuotaBytes vs UsedBytes,SMTP收信也更新配额 -- 富文本编辑器: compose页集成Quill.js(CDN),支持HTML邮件(multipart/alternative) -- OAuth2/LDAP认证: 默认关闭,配置文件控制开关;internal/auth/包(provider+ldap+oauth2) -- 新增依赖: github.com/go-ldap/ldap/v3, golang.org/x/oauth2 -- go build + go vet 通过 - -## 增强需求实现(v3) -- Banlist系统: BanEntry模型+BanStore+BanMiddleware,登录失败N次ban IP,admin /admin/bans 查看/解ban -- 管理仪表盘增强: 邮件分布(INBOX/Sent/Drafts/Trash计数+大小)、今日/7日收发统计、ban计数 -- 全量邮件查看: /admin/mails 支持文件夹筛选+分页,MailStore新增ListAll/ListAllByFolder -- Config新增: Ban BanConfig (max_fail_attempts默认5, ban_duration_min默认30分钟) -- 新建5文件: ban_store.go, ban.go中间件, banned.html, admin/bans.html, admin/mails.html -- 修改13文件: models.go, db.go, config.go, stores.go, mail_store.go, auth.go, admin.go, server.go, main.go + 5个admin模板 -- go build + go vet 通过 +## MailGo v3.1 增量更新 +- **域名表单 TLS 端口自动切换**:domain_form.html 增加 JS `togglePorts()`,勾选 TLS 自动切换 SMTP 25→465、IMAP 143→993、POP3 110→995 +- **管理后台邮件可点击查看**:admin/mails.html 邮件行改为可点击跳转 `/admin/mails/:id` +- **新增 AdminViewMail handler + admin/mail_view.html 模板**:管理员可查看任意用户邮件详情 +- **新增 AdminDownloadAttachment handler + 路由**:管理员可下载任意用户附件(绕过归属校验) +- **AdminHandler 新增 storage 依赖**:`NewAdminHandler(stores, attStorage)` 签名变更 diff --git a/internal/web/handlers/admin.go b/internal/web/handlers/admin.go index f07a39b..842c5a0 100644 --- a/internal/web/handlers/admin.go +++ b/internal/web/handlers/admin.go @@ -9,6 +9,7 @@ import ( "mail_go/internal/db" "mail_go/internal/dkim" + "mail_go/internal/storage" "mail_go/internal/store" "github.com/gin-gonic/gin" @@ -17,12 +18,13 @@ import ( // AdminHandler handles admin-related routes (dashboard, domain/user management). type AdminHandler struct { - stores *store.Stores + stores *store.Stores + storage *storage.AttachmentStorage } -// NewAdminHandler creates a new AdminHandler with the given stores. -func NewAdminHandler(stores *store.Stores) *AdminHandler { - return &AdminHandler{stores: stores} +// NewAdminHandler creates a new AdminHandler with the given stores and attachment storage. +func NewAdminHandler(stores *store.Stores, attStorage *storage.AttachmentStorage) *AdminHandler { + return &AdminHandler{stores: stores, storage: attStorage} } // Dashboard renders the admin dashboard with summary statistics. @@ -630,6 +632,59 @@ func (h *AdminHandler) ListMails(c *gin.Context) { }) } +// AdminViewMail renders the detail view of a specific mail for admin. +func (h *AdminHandler) AdminViewMail(c *gin.Context) { + id, err := strconv.ParseUint(c.Param("id"), 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "无效的邮件ID") + return + } + + msg, err := h.stores.Mails.GetByID(uint(id)) + if err != nil { + c.String(http.StatusNotFound, "邮件不存在") + return + } + + // 加载附件 + attachments, _ := h.stores.Attachments.ListByMessage(uint(id)) + + currentUser, _ := c.Get("currentUser") + + c.HTML(200, "admin_mail_view", gin.H{ + "currentUser": currentUser, + "message": msg, + "attachments": attachments, + "activeFolder": "mails", + }) +} + +// AdminDownloadAttachment serves an attachment file for admin (bypasses user ownership check). +func (h *AdminHandler) AdminDownloadAttachment(c *gin.Context) { + id, err := strconv.ParseUint(c.Param("id"), 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "无效的附件ID") + return + } + + att, err := h.stores.Attachments.GetByID(uint(id)) + if err != nil { + c.String(http.StatusNotFound, "附件不存在") + return + } + + data, err := h.storage.Read(att.FilePath) + if err != nil { + c.String(http.StatusInternalServerError, "读取附件失败") + return + } + + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", att.FileName)) + c.Data(http.StatusOK, att.ContentType, data) +} + +// formIntOrDefault extracts an integer from a form field, returning the default if missing/invalid. + // formIntOrDefault extracts an integer from a form field, returning the default if missing/invalid. func formIntOrDefault(c *gin.Context, key string, defaultVal int) int { val := c.PostForm(key) diff --git a/internal/web/server.go b/internal/web/server.go index adc2aab..842776f 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -112,7 +112,7 @@ func NewWebServer(cfg config.WebConfig, stores *store.Stores, attStorage *storag func (ws *WebServer) registerRoutes() { authHandler := handlers.NewAuthHandler(ws.stores, ws.authCfg, ws.banCfg) mailHandler := handlers.NewMailHandler(ws.stores, ws.storage) - adminHandler := handlers.NewAdminHandler(ws.stores) + adminHandler := handlers.NewAdminHandler(ws.stores, ws.storage) // Apply BanMiddleware globally before public routes ws.engine.Use(middleware.BanMiddleware(ws.stores)) @@ -170,6 +170,8 @@ func (ws *WebServer) registerRoutes() { admin.GET("/users/:id/edit", adminHandler.EditUser) admin.POST("/users/:id", adminHandler.UpdateUser) admin.GET("/mails", adminHandler.ListMails) + admin.GET("/mails/:id", adminHandler.AdminViewMail) + admin.GET("/attachment/:id", adminHandler.AdminDownloadAttachment) admin.GET("/bans", adminHandler.ListBans) admin.POST("/bans/:id/unban", adminHandler.UnbanIP) admin.POST("/bans/cleanup", adminHandler.CleanupBans) diff --git a/internal/web/templates/admin/domain_form.html b/internal/web/templates/admin/domain_form.html index d918912..b7c172d 100644 --- a/internal/web/templates/admin/domain_form.html +++ b/internal/web/templates/admin/domain_form.html @@ -46,10 +46,19 @@
+

勾选后端口将自动切换为 SSL/TLS 标准端口

+ {{if .isEdit}}