proxyserver

This commit is contained in:
eson 2023-10-09 14:41:57 +08:00
parent 12a6e95a11
commit 9fdc5b4dbf
10 changed files with 94 additions and 61 deletions

View File

@ -91,23 +91,20 @@ func main() {
routes...)) routes...))
} }
Backends = append(Backends, NewBackend(mux,
fmt.Sprintf("http://%s:%d", "localhost", 9501),
"/api/v1/namespaces/kubernetes-dashboard"))
// 定义用于服务Vue dist文件夹的静态文件服务器 // 定义用于服务Vue dist文件夹的静态文件服务器
fs := http.FileServer(http.Dir(vueBuild)) fs := http.FileServer(http.Dir(vueBuild))
indexHtmlPath := vueBuild + "/index.html" indexHtmlPath := vueBuild + "/index.html"
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/api/") { if strings.HasPrefix(r.URL.Path, "/api/") {
r.ParseMultipartForm(100 << 20) r.ParseMultipartForm(100 << 20)
// if err != nil {
// logx.Error(err)
// }
// 对/api开头的请求进行反向代理 // 对/api开头的请求进行反向代理
proxy := httputil.NewSingleHostReverseProxy(apiURL) proxy := httputil.NewSingleHostReverseProxy(apiURL)
proxy.ServeHTTP(w, r) proxy.ServeHTTP(w, r)
return return
} else { } else {
// 根据请求路径判断是服务静态文件或者是返回index.html // 根据请求路径判断是服务静态文件或者是返回index.html
idx := strings.Index(r.URL.Path[1:], "/") idx := strings.Index(r.URL.Path[1:], "/")

View File

@ -2,6 +2,7 @@ Name: auth
Host: localhost Host: localhost
Port: 9980 Port: 9980
ReplicaId: 10 ReplicaId: 10
HomePage: "http://www.fusen.3718.cn"
MainAddress: "https://server.fusen.3718.cn:9900" MainAddress: "https://server.fusen.3718.cn:9900"
WebsocketAddr: "https://server.fusen.3718.cn:9914" WebsocketAddr: "https://server.fusen.3718.cn:9914"
SourceMysql: "fsreaderwriter:XErSYmLELKMnf3Dh@tcp(fusen.cdmigcvz3rle.us-east-2.rds.amazonaws.com:3306)/fusen" SourceMysql: "fsreaderwriter:XErSYmLELKMnf3Dh@tcp(fusen.cdmigcvz3rle.us-east-2.rds.amazonaws.com:3306)/fusen"

View File

@ -12,6 +12,7 @@ type Config struct {
Auth types.Auth Auth types.Auth
ReplicaId uint64 ReplicaId uint64
HomePage string
MainAddress string MainAddress string
WebsocketAddr string WebsocketAddr string

View File

@ -28,7 +28,7 @@ func init() {
log.Fatal(err) log.Fatal(err)
} }
TimeLimit = check.NewTimelimit[string](EmailTaskResendTime) TimeLimit = check.NewTimeLimit[string](EmailTaskResendTime)
// Initialize the email manager // Initialize the email manager
EmailManager = &EmailSender{ EmailManager = &EmailSender{
@ -53,16 +53,17 @@ func init() {
} }
type EmailFormat struct { type EmailFormat struct {
TemplateName string // 模板名字 TemplateName string // 模板名字
UniqueKey string // 用于处理唯一的任务,重发都会被利用到 UniqueKey string // 用于处理唯一的任务,重发都会被利用到
TargetEmail string // 发送的目标email TargetEmail string // 发送的目标email
CompanyName string // fs公司名 CompanyName string // fs公司名
ConfirmationLink string // fs确认连接 ConfirmationLink string // fs确认连接
SenderName string // 发送人 SenderName string // 发送人
SenderTitle string // 发送标题 SenderTitle string // 发送标题
Extend map[string]string Extend map[string]string // 扩展参数
} }
// 验证邮件格式是否符合要求
func (eformat *EmailFormat) Vaild() error { func (eformat *EmailFormat) Vaild() error {
// 1. 检查模板名称 // 1. 检查模板名称
@ -123,39 +124,47 @@ type EmailTask struct {
SendTime time.Time // 处理的任务时间 SendTime time.Time // 处理的任务时间
} }
// ProcessEmailTasks 是 EmailSender 结构体的方法,用于处理邮件任务。
func (m *EmailSender) ProcessEmailTasks() { func (m *EmailSender) ProcessEmailTasks() {
for { for {
// 从 EmailTasks 通道中接收邮件任务
emailformat, ok := <-m.EmailTasks emailformat, ok := <-m.EmailTasks
if !ok { if !ok {
log.Println("Email task channel closed") log.Println("Email task channel closed")
break break
} }
// 验证邮件格式是否合法
err := emailformat.Vaild() err := emailformat.Vaild()
if err != nil { if err != nil {
logx.Error(err) logx.Error(err)
continue continue
} }
// 加锁,避免并发修改 emailSending 字典
m.lock.Lock() m.lock.Lock()
// 检查 emailSending 字典中是否已存在相同的任务
_, isSending := m.emailSending[emailformat.UniqueKey] _, isSending := m.emailSending[emailformat.UniqueKey]
if isSending { if isSending {
m.lock.Unlock() m.lock.Unlock()
continue continue
} }
// 将邮件任务添加到 emailSending 字典中
m.emailSending[emailformat.UniqueKey] = &EmailTask{ m.emailSending[emailformat.UniqueKey] = &EmailTask{
Email: emailformat, Email: emailformat,
SendTime: time.Now().UTC(), SendTime: time.Now().UTC(),
} }
m.lock.Unlock() m.lock.Unlock()
// Acquire a token // 获取一个信号量,限制同时发送的邮件任务数量
m.semaphore <- struct{}{} m.semaphore <- struct{}{}
go func() { go func() {
defer func() { <-m.semaphore }() // Release a token defer func() { <-m.semaphore }() // 释放一个信号量
// 渲染邮件模板内容
content := RenderEmailTemplate( content := RenderEmailTemplate(
emailformat.TemplateName, emailformat.TemplateName,
emailformat.CompanyName, emailformat.CompanyName,
@ -164,34 +173,41 @@ func (m *EmailSender) ProcessEmailTasks() {
emailformat.SenderTitle, emailformat.SenderTitle,
emailformat.Extend, emailformat.Extend,
) )
// 发送邮件
err := smtp.SendMail("smtp.gmail.com:587", m.Auth, m.FromEmail, []string{emailformat.TargetEmail}, content) err := smtp.SendMail("smtp.gmail.com:587", m.Auth, m.FromEmail, []string{emailformat.TargetEmail}, content)
if err != nil { if err != nil {
log.Printf("Failed to send email to %s: %v\n", emailformat, err) log.Printf("Failed to send email to %s: %v\n", emailformat, err)
// 重新发送邮件
m.Resend(emailformat.UniqueKey, content) m.Resend(emailformat.UniqueKey, content)
} }
}() }()
} }
} }
// Resend 重发邮件 // Resend 是 EmailSender 结构体的方法,用于重发邮件
func (m *EmailSender) Resend(uniqueKey string, content []byte) { func (m *EmailSender) Resend(uniqueKey string, content []byte) {
// 等待一段时间后重发邮件,避免频繁重发
time.Sleep(m.ResendTimeLimit) time.Sleep(m.ResendTimeLimit)
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() 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()) { 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) err := smtp.SendMail(task.Email.TargetEmail, m.Auth, m.FromEmail, []string{task.Email.TargetEmail}, content)
if err != nil { if err != nil {
log.Printf("Failed to resend email to %s: %v\n", task.Email.TargetEmail, err) log.Printf("Failed to resend email to %s: %v\n", task.Email.TargetEmail, err)
} else { } else {
// 从 emailSending 字典中删除已成功发送的邮件任务
delete(m.emailSending, uniqueKey) delete(m.emailSending, uniqueKey)
} }
} }
} }
// ClearExpiredTasks 清除过期的邮件任务 // ClearExpiredTasks 是 EmailSender 结构体的方法,用于清除过期的邮件任务
func (m *EmailSender) ClearExpiredTasks() { func (m *EmailSender) ClearExpiredTasks() {
// 每分钟触发一次清除操作
ticker := time.NewTicker(time.Minute) ticker := time.NewTicker(time.Minute)
defer ticker.Stop() defer ticker.Stop()
@ -199,6 +215,7 @@ func (m *EmailSender) ClearExpiredTasks() {
<-ticker.C <-ticker.C
m.lock.Lock() m.lock.Lock()
// 遍历 emailSending 字典,删除发送时间超过一定限制的过期任务
for email, task := range m.emailSending { for email, task := range m.emailSending {
if task.SendTime.Add(m.ResendTimeLimit).Before(time.Now().UTC()) { if task.SendTime.Add(m.ResendTimeLimit).Before(time.Now().UTC()) {
delete(m.emailSending, email) 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{ data := map[string]string{
"CompanyName": companyName, "CompanyName": companyName,
"ConfirmationLink": confirmationLink, "ConfirmationLink": confirmationLink,
@ -217,16 +245,20 @@ func RenderEmailTemplate(templateName, companyName, confirmationLink, senderName
"SenderTitle": senderTitle, "SenderTitle": senderTitle,
} }
// 将扩展字段中的键值对添加到数据映射中
for k, v := range extend { for k, v := range extend {
data[k] = v data[k] = v
} }
// 创建一个字节缓冲区,用于存储渲染后的邮件内容
var result bytes.Buffer 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) err := tpls.ExecuteTemplate(&result, templateName, data)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
// 将渲染后的邮件内容转换为字节切片并返回
return result.Bytes() return result.Bytes()
} }

View File

@ -14,7 +14,6 @@ import (
"fusenapi/server/auth/internal/svc" "fusenapi/server/auth/internal/svc"
"fusenapi/server/auth/internal/types" "fusenapi/server/auth/internal/types"
"github.com/474420502/requests"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"gorm.io/gorm" "gorm.io/gorm"
@ -57,7 +56,7 @@ func FinishRegister(svcCtx *svc.ServiceContext, user *gmodel.FsUser, token *auth
event.Data = wevent.DataEmailRegister{ event.Data = wevent.DataEmailRegister{
JwtToken: jwtToken, JwtToken: jwtToken,
} }
err = CommonNotify(svcCtx.Config.WebsocketAddr, token.Wid, event) err = wevent.CommonNotify(svcCtx.Config.WebsocketAddr, token.Wid, event)
if err != nil { if err != nil {
// logx.Error(err, token.TraceId) // logx.Error(err, token.TraceId)
return err return err
@ -66,34 +65,6 @@ func FinishRegister(svcCtx *svc.ServiceContext, user *gmodel.FsUser, token *auth
return nil 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) { func (l *UserEmailConfirmationLogic) UserEmailConfirmation(req *types.RequestEmailConfirmation, userinfo *auth.UserInfo) (resp *basic.Response) {
// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data) // 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
// userinfo 传入值时, 一定不为null // userinfo 传入值时, 一定不为null
@ -176,7 +147,7 @@ func (l *UserEmailConfirmationLogic) UserEmailConfirmation(req *types.RequestEma
} }
event := wevent.NewWebsocketEventSuccess(wevent.UserResetToken, rt.TraceId) 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 { if err != nil {
logx.Error(err, rt.TraceId) logx.Error(err, rt.TraceId)
return resp.SetStatus(basic.CodeResetPasswordErr, err.Error()) return resp.SetStatus(basic.CodeResetPasswordErr, err.Error())

View File

@ -50,7 +50,7 @@ func (l *UserResetPasswordHtmlLogic) UserResetPasswordHtml(req *types.RequestUse
func (l *UserResetPasswordHtmlLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) { func (l *UserResetPasswordHtmlLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
err := tpls.ExecuteTemplate(w, "reset_confirm.tpl", map[string]string{ err := tpls.ExecuteTemplate(w, "reset_confirm.tpl", map[string]string{
"HomePage": "http://www.fusen.3718.cn", "HomePage": l.svcCtx.Config.HomePage,
"ResetToken": l.ResetToken, "ResetToken": l.ResetToken,
"ResetPasswordLink": l.svcCtx.Config.MainAddress + "/api/auth/reset/password", "ResetPasswordLink": l.svcCtx.Config.MainAddress + "/api/auth/reset/password",
}) })

View File

@ -51,7 +51,7 @@ func (l *UserResetPasswordLogic) UserResetPassword(req *types.RequestUserResetPa
} }
if time.Since(rt.CreateAt) > 30*time.Minute { 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 { 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 { 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 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) 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 { if err != nil {
logx.Error(err, rt.TraceId) logx.Error(err, rt.TraceId)
return resp.SetStatusWithMessage(basic.CodeResetPasswordErr, err.Error()) return resp.SetStatusWithMessage(basic.CodeResetPasswordErr, err.Error())

View File

@ -56,7 +56,7 @@ var (
CodeUserIdNotFoundErr = &StatusResponse{5051, "user not found"} // 未找到用户 CodeUserIdNotFoundErr = &StatusResponse{5051, "user not found"} // 未找到用户
CodePasswordErr = &StatusResponse{5052, "invalid password"} // 无效密码 CodePasswordErr = &StatusResponse{5052, "invalid password"} // 无效密码
CodeEmailExistsErr = &StatusResponse{5053, "email exists"} // email存在 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"} // 无效密码 CodeResetPasswordErr = &StatusResponse{5054, "reset password error"} // 无效密码
CodeSafeValueRangeErr = &StatusResponse{5040, "value not in range"} // 值不在范围内 CodeSafeValueRangeErr = &StatusResponse{5040, "value not in range"} // 值不在范围内

View File

@ -15,9 +15,9 @@ type TimeLimit[T comparable] struct {
dur time.Duration 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]{ return &TimeLimit[T]{
dict: make(map[T]struct{}), dict: make(map[T]struct{}),
dur: dur, dur: dur,

View File

@ -1,7 +1,10 @@
package wevent package wevent
import ( import (
"fmt"
"time" "time"
"github.com/474420502/requests"
) )
// 和前端交流的事件机制 // 和前端交流的事件机制
@ -22,6 +25,34 @@ type WebsocketEvent struct {
Data any `json:"data"` // 关注的数据 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事件 // NewWebsocketEvent 创建一个Websocket事件
func NewWebsocketEvent(etype EventType, TraceId string) *WebsocketEvent { func NewWebsocketEvent(etype EventType, TraceId string) *WebsocketEvent {
return &WebsocketEvent{ return &WebsocketEvent{