diff --git a/proxyserver/main.go b/proxyserver/main.go index 1b0b9f9c..46d17f42 100644 --- a/proxyserver/main.go +++ b/proxyserver/main.go @@ -91,23 +91,20 @@ func main() { routes...)) } + Backends = append(Backends, NewBackend(mux, + fmt.Sprintf("http://%s:%d", "localhost", 9501), + "/api/v1/namespaces/kubernetes-dashboard")) + // 定义用于服务Vue dist文件夹的静态文件服务器 fs := http.FileServer(http.Dir(vueBuild)) indexHtmlPath := vueBuild + "/index.html" mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/api/") { - r.ParseMultipartForm(100 << 20) - // if err != nil { - // logx.Error(err) - // } - // 对/api开头的请求进行反向代理 proxy := httputil.NewSingleHostReverseProxy(apiURL) proxy.ServeHTTP(w, r) - return - } else { // 根据请求路径判断是服务静态文件或者是返回index.html idx := strings.Index(r.URL.Path[1:], "/") diff --git a/server/auth/etc/auth.yaml b/server/auth/etc/auth.yaml index 1713f5c4..76fecec0 100644 --- a/server/auth/etc/auth.yaml +++ b/server/auth/etc/auth.yaml @@ -2,6 +2,7 @@ Name: auth Host: localhost Port: 9980 ReplicaId: 10 +HomePage: "http://www.fusen.3718.cn" MainAddress: "https://server.fusen.3718.cn:9900" WebsocketAddr: "https://server.fusen.3718.cn:9914" SourceMysql: "fsreaderwriter:XErSYmLELKMnf3Dh@tcp(fusen.cdmigcvz3rle.us-east-2.rds.amazonaws.com:3306)/fusen" diff --git a/server/auth/internal/config/config.go b/server/auth/internal/config/config.go index c8b3c6d1..55576d81 100644 --- a/server/auth/internal/config/config.go +++ b/server/auth/internal/config/config.go @@ -12,6 +12,7 @@ type Config struct { Auth types.Auth ReplicaId uint64 + HomePage string MainAddress string WebsocketAddr string diff --git a/server/auth/internal/logic/email_manager.go b/server/auth/internal/logic/email_manager.go index 4d1bd84a..368b8fb1 100644 --- a/server/auth/internal/logic/email_manager.go +++ b/server/auth/internal/logic/email_manager.go @@ -28,7 +28,7 @@ func init() { log.Fatal(err) } - TimeLimit = check.NewTimelimit[string](EmailTaskResendTime) + TimeLimit = check.NewTimeLimit[string](EmailTaskResendTime) // Initialize the email manager EmailManager = &EmailSender{ @@ -53,16 +53,17 @@ func init() { } type EmailFormat struct { - TemplateName string // 模板名字 - UniqueKey string // 用于处理唯一的任务,重发都会被利用到 - TargetEmail string // 发送的目标email - CompanyName string // fs公司名 - ConfirmationLink string // fs确认连接 - SenderName string // 发送人 - SenderTitle string // 发送标题 - Extend map[string]string + TemplateName string // 模板名字 + UniqueKey string // 用于处理唯一的任务,重发都会被利用到 + TargetEmail string // 发送的目标email + CompanyName string // fs公司名 + ConfirmationLink string // fs确认连接 + SenderName string // 发送人 + SenderTitle string // 发送标题 + Extend map[string]string // 扩展参数 } +// 验证邮件格式是否符合要求 func (eformat *EmailFormat) Vaild() error { // 1. 检查模板名称 @@ -123,39 +124,47 @@ type EmailTask struct { SendTime time.Time // 处理的任务时间 } +// ProcessEmailTasks 是 EmailSender 结构体的方法,用于处理邮件任务。 func (m *EmailSender) ProcessEmailTasks() { for { + // 从 EmailTasks 通道中接收邮件任务 emailformat, ok := <-m.EmailTasks if !ok { log.Println("Email task channel closed") break } + // 验证邮件格式是否合法 err := emailformat.Vaild() if err != nil { logx.Error(err) continue } + // 加锁,避免并发修改 emailSending 字典 m.lock.Lock() + + // 检查 emailSending 字典中是否已存在相同的任务 _, isSending := m.emailSending[emailformat.UniqueKey] if isSending { m.lock.Unlock() continue } + // 将邮件任务添加到 emailSending 字典中 m.emailSending[emailformat.UniqueKey] = &EmailTask{ Email: emailformat, SendTime: time.Now().UTC(), } m.lock.Unlock() - // Acquire a token + // 获取一个信号量,限制同时发送的邮件任务数量 m.semaphore <- struct{}{} go func() { - defer func() { <-m.semaphore }() // Release a token + defer func() { <-m.semaphore }() // 释放一个信号量 + // 渲染邮件模板内容 content := RenderEmailTemplate( emailformat.TemplateName, emailformat.CompanyName, @@ -164,34 +173,41 @@ func (m *EmailSender) ProcessEmailTasks() { emailformat.SenderTitle, emailformat.Extend, ) + + // 发送邮件 err := smtp.SendMail("smtp.gmail.com:587", m.Auth, m.FromEmail, []string{emailformat.TargetEmail}, content) if err != nil { log.Printf("Failed to send email to %s: %v\n", emailformat, err) + // 重新发送邮件 m.Resend(emailformat.UniqueKey, content) } }() } } -// Resend 重发邮件 +// Resend 是 EmailSender 结构体的方法,用于重发邮件。 func (m *EmailSender) Resend(uniqueKey string, content []byte) { + // 等待一段时间后重发邮件,避免频繁重发 time.Sleep(m.ResendTimeLimit) m.lock.Lock() defer m.lock.Unlock() - // Check if the email task still exists and has not been sent successfully + + // 检查邮件任务是否仍存在并且未成功发送 if task, ok := m.emailSending[uniqueKey]; ok && task.SendTime.Add(m.ResendTimeLimit).After(time.Now().UTC()) { err := smtp.SendMail(task.Email.TargetEmail, m.Auth, m.FromEmail, []string{task.Email.TargetEmail}, content) if err != nil { log.Printf("Failed to resend email to %s: %v\n", task.Email.TargetEmail, err) } else { + // 从 emailSending 字典中删除已成功发送的邮件任务 delete(m.emailSending, uniqueKey) } } } -// ClearExpiredTasks 清除过期的邮件任务 +// ClearExpiredTasks 是 EmailSender 结构体的方法,用于清除过期的邮件任务。 func (m *EmailSender) ClearExpiredTasks() { + // 每分钟触发一次清除操作 ticker := time.NewTicker(time.Minute) defer ticker.Stop() @@ -199,6 +215,7 @@ func (m *EmailSender) ClearExpiredTasks() { <-ticker.C m.lock.Lock() + // 遍历 emailSending 字典,删除发送时间超过一定限制的过期任务 for email, task := range m.emailSending { if task.SendTime.Add(m.ResendTimeLimit).Before(time.Now().UTC()) { delete(m.emailSending, email) @@ -208,8 +225,19 @@ func (m *EmailSender) ClearExpiredTasks() { } } -func RenderEmailTemplate(templateName, companyName, confirmationLink, senderName, senderTitle string, extend map[string]string) []byte { +// RenderEmailTemplate 是一个渲染邮件模板的函数,根据给定的参数生成邮件内容。 +// 参数: +// - templateName: 邮件模板的名称 +// - companyName: 公司名称 +// - confirmationLink: 确认链接 +// - senderName: 发件人姓名 +// - senderTitle: 发件人职务 +// - extend: 扩展字段,包含其他自定义键值对的映射 +// 返回值: +// - []byte: 渲染后的邮件内容(以字节切片形式返回) +func RenderEmailTemplate(templateName, companyName, confirmationLink, senderName, senderTitle string, extend map[string]string) []byte { + // 创建一个包含邮件模板所需数据的映射 data := map[string]string{ "CompanyName": companyName, "ConfirmationLink": confirmationLink, @@ -217,16 +245,20 @@ func RenderEmailTemplate(templateName, companyName, confirmationLink, senderName "SenderTitle": senderTitle, } + // 将扩展字段中的键值对添加到数据映射中 for k, v := range extend { data[k] = v } + // 创建一个字节缓冲区,用于存储渲染后的邮件内容 var result bytes.Buffer - // result.Write([]byte("MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n")) + + // 使用给定的数据映射执行邮件模板渲染 err := tpls.ExecuteTemplate(&result, templateName, data) if err != nil { log.Fatal(err) } + // 将渲染后的邮件内容转换为字节切片并返回 return result.Bytes() } diff --git a/server/auth/internal/logic/useremailconfirmationlogic.go b/server/auth/internal/logic/useremailconfirmationlogic.go index 3cb86bc8..94330ab3 100644 --- a/server/auth/internal/logic/useremailconfirmationlogic.go +++ b/server/auth/internal/logic/useremailconfirmationlogic.go @@ -14,7 +14,6 @@ import ( "fusenapi/server/auth/internal/svc" "fusenapi/server/auth/internal/types" - "github.com/474420502/requests" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/rest/httpx" "gorm.io/gorm" @@ -57,7 +56,7 @@ func FinishRegister(svcCtx *svc.ServiceContext, user *gmodel.FsUser, token *auth event.Data = wevent.DataEmailRegister{ JwtToken: jwtToken, } - err = CommonNotify(svcCtx.Config.WebsocketAddr, token.Wid, event) + err = wevent.CommonNotify(svcCtx.Config.WebsocketAddr, token.Wid, event) if err != nil { // logx.Error(err, token.TraceId) return err @@ -66,34 +65,6 @@ func FinishRegister(svcCtx *svc.ServiceContext, user *gmodel.FsUser, token *auth return nil } -func CommonNotify(WebsocketAddr, wid string, event *wevent.WebsocketEvent) error { - - reqWebsocketAddr := fmt.Sprintf("%s/api/websocket/common_notify", WebsocketAddr) - tp := requests.Post(reqWebsocketAddr) - tp.SetBodyJson(requests.M{ - "wid": wid, - "data": event, - }) - - wresp, err := tp.Execute() - if err != nil { - // logx.Error(err, token.TraceId) - return err - } - - result := wresp.Json() - - if !result.Get("code").Exists() { - return fmt.Errorf("send %s is error", reqWebsocketAddr) - } - - if result.Get("code").Int() != 200 { - return fmt.Errorf("%s", result.String()) - } - - return nil -} - func (l *UserEmailConfirmationLogic) UserEmailConfirmation(req *types.RequestEmailConfirmation, userinfo *auth.UserInfo) (resp *basic.Response) { // 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data) // userinfo 传入值时, 一定不为null @@ -176,7 +147,7 @@ func (l *UserEmailConfirmationLogic) UserEmailConfirmation(req *types.RequestEma } event := wevent.NewWebsocketEventSuccess(wevent.UserResetToken, rt.TraceId) - err = CommonNotify(l.svcCtx.Config.MainAddress, rt.Wid, event) + err = wevent.CommonNotify(l.svcCtx.Config.MainAddress, rt.Wid, event) if err != nil { logx.Error(err, rt.TraceId) return resp.SetStatus(basic.CodeResetPasswordErr, err.Error()) diff --git a/server/auth/internal/logic/userresetpasswordhtmllogic.go b/server/auth/internal/logic/userresetpasswordhtmllogic.go index 50496ebe..a56d40c7 100644 --- a/server/auth/internal/logic/userresetpasswordhtmllogic.go +++ b/server/auth/internal/logic/userresetpasswordhtmllogic.go @@ -50,7 +50,7 @@ func (l *UserResetPasswordHtmlLogic) UserResetPasswordHtml(req *types.RequestUse func (l *UserResetPasswordHtmlLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) { err := tpls.ExecuteTemplate(w, "reset_confirm.tpl", map[string]string{ - "HomePage": "http://www.fusen.3718.cn", + "HomePage": l.svcCtx.Config.HomePage, "ResetToken": l.ResetToken, "ResetPasswordLink": l.svcCtx.Config.MainAddress + "/api/auth/reset/password", }) diff --git a/server/auth/internal/logic/userresetpasswordlogic.go b/server/auth/internal/logic/userresetpasswordlogic.go index 7f0c943c..aa9ab591 100644 --- a/server/auth/internal/logic/userresetpasswordlogic.go +++ b/server/auth/internal/logic/userresetpasswordlogic.go @@ -51,7 +51,7 @@ func (l *UserResetPasswordLogic) UserResetPassword(req *types.RequestUserResetPa } if time.Since(rt.CreateAt) > 30*time.Minute { - return resp.SetStatusWithMessage(basic.CodeOAuthConfirmationTimeoutErr, "Verification links expire after 30 minute.") + return resp.SetStatusWithMessage(basic.CodeOAuthConfirmationTimeoutErr, "verification links expire after 30 minute.") } err = l.svcCtx.AllModels.FsUser.Transaction(l.ctx, func(tx *gorm.DB) error { @@ -67,7 +67,7 @@ func (l *UserResetPasswordLogic) UserResetPassword(req *types.RequestUserResetPa } if *user.PasswordHash == req.NewPassword { - return fmt.Errorf("the password is the same as the old one. It needs to be changed") + return fmt.Errorf("the password is the same as the old one. it needs to be changed") } return tx.Where("id = ?", rt.UserId).Update("PasswordHash", req.NewPassword).Error @@ -79,7 +79,7 @@ func (l *UserResetPasswordLogic) UserResetPassword(req *types.RequestUserResetPa } event := wevent.NewWebsocketEventSuccess(wevent.UserResetToken, rt.TraceId) - err = CommonNotify(l.svcCtx.Config.WebsocketAddr, rt.Wid, event) + err = wevent.CommonNotify(l.svcCtx.Config.WebsocketAddr, rt.Wid, event) if err != nil { logx.Error(err, rt.TraceId) return resp.SetStatusWithMessage(basic.CodeResetPasswordErr, err.Error()) diff --git a/utils/basic/basic.go b/utils/basic/basic.go index 2af8be79..8ce30032 100644 --- a/utils/basic/basic.go +++ b/utils/basic/basic.go @@ -56,7 +56,7 @@ var ( CodeUserIdNotFoundErr = &StatusResponse{5051, "user not found"} // 未找到用户 CodePasswordErr = &StatusResponse{5052, "invalid password"} // 无效密码 CodeEmailExistsErr = &StatusResponse{5053, "email exists"} // email存在 - CodeEmailTimeShortErr = &StatusResponse{5053, "email with the time of resend is too short"} // email存在 + CodeEmailTimeShortErr = &StatusResponse{5053, "email with the time of resend is too short"} // email重发的时间太短 CodeResetPasswordErr = &StatusResponse{5054, "reset password error"} // 无效密码 CodeSafeValueRangeErr = &StatusResponse{5040, "value not in range"} // 值不在范围内 diff --git a/utils/check/limit.go b/utils/check/limit.go index fdf3503e..f5e2cb3e 100644 --- a/utils/check/limit.go +++ b/utils/check/limit.go @@ -15,9 +15,9 @@ type TimeLimit[T comparable] struct { dur time.Duration } -// NewTimelimit构造函数,接收限频的时间间隔 +// NewTimeLimit构造函数,接收限频的时间间隔 // 并初始化内部字典和间隔字段 -func NewTimelimit[T comparable](dur time.Duration) *TimeLimit[T] { +func NewTimeLimit[T comparable](dur time.Duration) *TimeLimit[T] { return &TimeLimit[T]{ dict: make(map[T]struct{}), dur: dur, diff --git a/utils/wevent/base_event.go b/utils/wevent/base_event.go index 8ce58131..dd443475 100644 --- a/utils/wevent/base_event.go +++ b/utils/wevent/base_event.go @@ -1,7 +1,10 @@ package wevent import ( + "fmt" "time" + + "github.com/474420502/requests" ) // 和前端交流的事件机制 @@ -22,6 +25,34 @@ type WebsocketEvent struct { Data any `json:"data"` // 关注的数据 } +func CommonNotify(WebsocketAddr, wid string, event *WebsocketEvent) error { + + reqWebsocketAddr := fmt.Sprintf("%s/api/websocket/common_notify", WebsocketAddr) + tp := requests.Post(reqWebsocketAddr) + tp.SetBodyJson(requests.M{ + "wid": wid, + "data": event, + }) + + wresp, err := tp.Execute() + if err != nil { + // logx.Error(err, token.TraceId) + return err + } + + result := wresp.Json() + + if !result.Get("code").Exists() { + return fmt.Errorf("send %s is error", reqWebsocketAddr) + } + + if result.Get("code").Int() != 200 { + return fmt.Errorf("%s", result.String()) + } + + return nil +} + // NewWebsocketEvent 创建一个Websocket事件 func NewWebsocketEvent(etype EventType, TraceId string) *WebsocketEvent { return &WebsocketEvent{