🎨新增用户头像更换、客户端邮件通知

This commit is contained in:
coward 2024-06-13 11:28:02 +08:00
parent d1bb49c208
commit 67f394f136
12 changed files with 226 additions and 143 deletions

View File

@ -7,6 +7,6 @@ type config struct {
Database *database `yaml:"database"` Database *database `yaml:"database"`
Redis *redis `yaml:"redis"` Redis *redis `yaml:"redis"`
File *file `yaml:"file"` File *file `yaml:"file"`
Mail *mail `yaml:"mail"` Mail *mail `yaml:"email"`
Wireguard *wireguard `yaml:"wireguard"` Wireguard *wireguard `yaml:"wireguard"`
} }

5
go.mod
View File

@ -11,6 +11,8 @@ require (
github.com/go-resty/resty/v2 v2.11.0 github.com/go-resty/resty/v2 v2.11.0
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/json-iterator/go v1.1.12
github.com/mojocn/base64Captcha v1.3.6 github.com/mojocn/base64Captcha v1.3.6
github.com/redis/go-redis/v9 v9.5.1 github.com/redis/go-redis/v9 v9.5.1
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
@ -18,7 +20,6 @@ require (
golang.org/x/crypto v0.21.0 golang.org/x/crypto v0.21.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
gopkg.in/fsnotify.v1 v1.4.7 gopkg.in/fsnotify.v1 v1.4.7
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.5.4 gorm.io/driver/mysql v1.5.4
gorm.io/driver/postgres v1.5.6 gorm.io/driver/postgres v1.5.6
@ -58,7 +59,6 @@ require (
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect
github.com/lixh00/loki-client-go v1.0.1 // indirect github.com/lixh00/loki-client-go v1.0.1 // indirect
@ -99,7 +99,6 @@ require (
google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55 // indirect google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55 // indirect
google.golang.org/grpc v1.50.1 // indirect google.golang.org/grpc v1.50.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/libc v1.22.5 // indirect modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/mathutil v1.5.0 // indirect

6
go.sum
View File

@ -498,6 +498,8 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
@ -1273,8 +1275,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -1286,8 +1286,6 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=

View File

@ -202,49 +202,15 @@ func (clients) Download(c *gin.Context) {
var keys template_data.Keys var keys template_data.Keys
_ = json.Unmarshal([]byte(data.Keys), &keys) _ = json.Unmarshal([]byte(data.Keys), &keys)
setting, err := repository.System().GetServerSetting() serverSetting, err := repository.System().GetServerSetting()
if err != nil { if err != nil {
utils.GinResponse(c).FailedWithMsg("获取设置失败") utils.GinResponse(c).FailedWithMsg("获取设置失败")
return return
} }
var serverDNS []string outPath, err := utils.Wireguard().GenerateClientFile(&data, serverSetting)
if *data.UseServerDns == 1 {
serverDNS = setting.DnsServer
}
// 处理一下数据
execData := template_data.ClientConfig{
PrivateKey: keys.PrivateKey,
IpAllocation: data.IpAllocation,
MTU: setting.MTU,
DNS: strings.Join(serverDNS, ","),
PublicKey: data.Server.PublicKey,
PresharedKey: keys.PresharedKey,
AllowedIPS: data.AllowedIps,
Endpoint: setting.EndpointAddress,
ListenPort: data.Server.ListenPort,
PersistentKeepalive: setting.PersistentKeepalive,
}
// 不同环境下处理文件路径
var outPath = "/tmp/" + fmt.Sprintf("%s.conf", data.Name)
var templatePath = "./template/wg.client.conf"
if os.Getenv("GIN_MODE") != "release" {
outPath = "E:\\Workspace\\Go\\wireguard-dashboard\\template\\" + fmt.Sprintf("%s.conf", data.Name)
templatePath = "E:\\Workspace\\Go\\wireguard-dashboard\\template\\wg.client.conf"
}
// 渲染数据
parseTemplate, err := utils.Template().Parse(templatePath)
if err != nil { if err != nil {
utils.GinResponse(c).FailedWithMsg("读取模板文件失败") utils.GinResponse(c).FailedWithErr("生成失败", err)
return
}
err = utils.Template().Execute(parseTemplate, execData, outPath)
if err != nil {
utils.GinResponse(c).FailedWithMsg("文件渲染失败")
return return
} }
@ -279,49 +245,15 @@ func (clients) GenerateQrCode(c *gin.Context) {
var keys template_data.Keys var keys template_data.Keys
_ = json.Unmarshal([]byte(data.Keys), &keys) _ = json.Unmarshal([]byte(data.Keys), &keys)
setting, err := repository.System().GetServerSetting() serverSetting, err := repository.System().GetServerSetting()
if err != nil { if err != nil {
utils.GinResponse(c).FailedWithMsg("获取设置失败") utils.GinResponse(c).FailedWithMsg("获取设置失败")
return return
} }
var serverDNS []string outPath, err := utils.Wireguard().GenerateClientFile(&data, serverSetting)
if *data.UseServerDns == 1 {
serverDNS = setting.DnsServer
}
// 处理一下数据
execData := template_data.ClientConfig{
PrivateKey: keys.PrivateKey,
IpAllocation: data.IpAllocation,
MTU: setting.MTU,
DNS: strings.Join(serverDNS, ","),
PublicKey: data.Server.PublicKey,
PresharedKey: keys.PresharedKey,
AllowedIPS: data.AllowedIps,
Endpoint: setting.EndpointAddress,
ListenPort: data.Server.ListenPort,
PersistentKeepalive: setting.PersistentKeepalive,
}
// 不同环境下处理文件路径
var outPath = "/tmp/" + fmt.Sprintf("%s.conf", data.Name)
var templatePath = "./template/wg.client.conf"
if os.Getenv("GIN_MODE") != "release" {
outPath = "E:\\Workspace\\Go\\wireguard-dashboard\\template\\" + fmt.Sprintf("%s.conf", data.Name)
templatePath = "E:\\Workspace\\Go\\wireguard-dashboard\\template\\wg.client.conf"
}
// 渲染数据
parseTemplate, err := utils.Template().Parse(templatePath)
if err != nil { if err != nil {
utils.GinResponse(c).FailedWithMsg("读取模板文件失败") utils.GinResponse(c).FailedWithErr("生成失败", err)
return
}
err = utils.Template().Execute(parseTemplate, execData, outPath)
if err != nil {
utils.GinResponse(c).FailedWithMsg("文件渲染失败")
return return
} }
@ -347,6 +279,59 @@ func (clients) GenerateQrCode(c *gin.Context) {
}) })
} }
// SendEmail
// @description: 发送邮件
// @receiver clients
// @param c
func (clients) SendEmail(c *gin.Context) {
var id = c.Param("id")
if id == "" || id == "undefined" {
utils.GinResponse(c).FailedWithMsg("id不能为空")
return
}
// 先校验一下邮箱发送是否可用
if err := utils.Mail().VerifyConfig(); err != nil {
utils.GinResponse(c).FailedWithMsg(err.Error())
return
}
// 获取该客户端信息
clientInfo, err := repository.Client().GetById(id)
if err != nil {
utils.GinResponse(c).FailedWithErr("获取失败", err)
return
}
if clientInfo.Email == "" {
utils.GinResponse(c).FailedWithMsg("当前客户端未配置联系邮箱!")
return
}
serverSetting, err := repository.System().GetServerSetting()
if err != nil {
utils.GinResponse(c).FailedWithMsg("获取设置失败")
return
}
outPath, err := utils.Wireguard().GenerateClientFile(&clientInfo, serverSetting)
if err != nil {
utils.GinResponse(c).FailedWithErr("生成失败", err)
return
}
err = utils.Mail().SendMail(clientInfo.Email, fmt.Sprintf("客户端: %s", clientInfo.Name), "请查收附件", outPath)
if err != nil {
utils.GinResponse(c).FailedWithErr("发送邮件失败", err)
return
}
if err = os.Remove(outPath); err != nil {
log.Errorf("删除临时文件失败: %s", err.Error())
}
utils.GinResponse(c).OK()
}
// Status // Status
// @description: 获取客户端状态信息,链接状态等 // @description: 获取客户端状态信息,链接状态等
// @receiver clients // @receiver clients

View File

@ -1,8 +1,11 @@
package api package api
import ( import (
"encoding/base64"
"fmt"
"gitee.ltd/lxh/logger/log" "gitee.ltd/lxh/logger/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"strings"
"wireguard-dashboard/client" "wireguard-dashboard/client"
"wireguard-dashboard/component" "wireguard-dashboard/component"
"wireguard-dashboard/constant" "wireguard-dashboard/constant"
@ -157,6 +160,30 @@ func (user) Save(c *gin.Context) {
} }
} }
// 只有修改才有头像值
if p.Avatar != "" && p.ID != "" {
// 判断头像是base64开头的就需要重新上传更新
if strings.HasPrefix(p.Avatar, "data:image/png;base64,") {
avatar := strings.Replace(p.Avatar, "data:image/png;base64,", "", -1)
avatarByte, err := base64.StdEncoding.DecodeString(avatar)
if err != nil {
log.Errorf("反解析头像失败: %v", err.Error())
utils.GinResponse(c).FailedWithMsg("上传头像失败")
return
}
file, err := utils.FileSystem().UploadFile(avatarByte, ".png")
if err != nil {
log.Errorf("上传头像失败: %v", err.Error())
utils.GinResponse(c).FailedWithMsg("上传头像失败")
return
}
p.Avatar = file
}
}
if err := repository.User().Save(&entity.User{ if err := repository.User().Save(&entity.User{
Base: entity.Base{ Base: entity.Base{
Id: p.ID, Id: p.ID,
@ -250,3 +277,17 @@ func (user) DeleteUser(c *gin.Context) {
utils.GinResponse(c).OK() utils.GinResponse(c).OK()
} }
// ChangeAvatar
// @description: 切换头像
// @receiver user
// @param c
func (user) ChangeAvatar(c *gin.Context) {
avatar, err := utils.Avatar().GenerateAvatar(false)
if err != nil {
utils.GinResponse(c).FailedWithErr("生成头像失败", err)
return
}
utils.GinResponse(c).OKWithData(fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString([]byte(avatar))))
}

View File

@ -86,7 +86,7 @@ func (r user) Save(ent *entity.User) (err error) {
// 没有头像就生成一个头像 // 没有头像就生成一个头像
if ent.Avatar == "" { if ent.Avatar == "" {
ent.Avatar, _ = utils.Avatar().GenerateAvatar() ent.Avatar, _ = utils.Avatar().GenerateAvatar(true)
} }
// 创建 // 创建

View File

@ -14,6 +14,7 @@ func ClientApi(r *gin.RouterGroup) {
apiGroup.DELETE(":id", middleware.Permission(), api.Client().Delete) // 删除客户端 apiGroup.DELETE(":id", middleware.Permission(), api.Client().Delete) // 删除客户端
apiGroup.POST("download/:id", api.Client().Download) // 下载客户端配置文件 apiGroup.POST("download/:id", api.Client().Download) // 下载客户端配置文件
apiGroup.POST("generate-qrcode/:id", api.Client().GenerateQrCode) // 生成客户端二维码 apiGroup.POST("generate-qrcode/:id", api.Client().GenerateQrCode) // 生成客户端二维码
apiGroup.POST("to-email/:id", api.Client().SendEmail) //发送邮件
apiGroup.GET("status", api.Client().Status) // 获取客户端链接状态监听列表 apiGroup.GET("status", api.Client().Status) // 获取客户端链接状态监听列表
apiGroup.POST("offline/:id", api.Client().Offline) // 强制下线指定客户端 apiGroup.POST("offline/:id", api.Client().Offline) // 强制下线指定客户端
apiGroup.POST("assignIP", api.Client().AssignIPAndAllowedIP) // 分配IP apiGroup.POST("assignIP", api.Client().AssignIPAndAllowedIP) // 分配IP

View File

@ -23,5 +23,6 @@ func UserApi(r *gin.RouterGroup) {
userApi.GET("list", middleware.Permission(), api.UserApi().List) // 用户列表 userApi.GET("list", middleware.Permission(), api.UserApi().List) // 用户列表
userApi.PUT("change-status", middleware.Permission(), api.UserApi().ChangeUserState) // 变更状态 userApi.PUT("change-status", middleware.Permission(), api.UserApi().ChangeUserState) // 变更状态
userApi.DELETE("delete/:id", middleware.Permission(), api.UserApi().DeleteUser) // 删除用户 userApi.DELETE("delete/:id", middleware.Permission(), api.UserApi().DeleteUser) // 删除用户
userApi.POST("change-avatar", api.UserApi().ChangeAvatar) // 更换头像
} }
} }

View File

@ -69,7 +69,7 @@ func (s Script) CreateSuperAdmin() error {
} }
// 生成一下头像 // 生成一下头像
avatarPath, err := utils.Avatar().GenerateAvatar() avatarPath, err := utils.Avatar().GenerateAvatar(true)
if err != nil { if err != nil {
log.Errorf("生成头像失败: %v", err.Error()) log.Errorf("生成头像失败: %v", err.Error())
return err return err

View File

@ -19,7 +19,7 @@ func Avatar() avatar {
// @receiver avatar // @receiver avatar
// @return path // @return path
// @return err // @return err
func (avatar) GenerateAvatar() (path string, err error) { func (avatar) GenerateAvatar(isUpload bool) (path string, err error) {
rand.New(rand.NewSource(time.Now().UnixNano())) rand.New(rand.NewSource(time.Now().UnixNano()))
r := client.HttpClient.R() r := client.HttpClient.R()
result, err := r.Get(fmt.Sprintf("https://api.dicebear.com/7.x/croodles/png?seed=%d&scale=120&size=200&clip=true&randomizeIds=true&beard=variant01,variant02,variant03&"+ result, err := r.Get(fmt.Sprintf("https://api.dicebear.com/7.x/croodles/png?seed=%d&scale=120&size=200&clip=true&randomizeIds=true&beard=variant01,variant02,variant03&"+
@ -30,10 +30,13 @@ func (avatar) GenerateAvatar() (path string, err error) {
return "", err return "", err
} }
filePath, err := FileSystem().UploadFile(result.Body(), ".png") if isUpload {
if err != nil { path, err = FileSystem().UploadFile(result.Body(), ".png")
return "", err if err != nil {
return "", err
}
return
} }
return filePath, nil return string(result.Body()), nil
} }

View File

@ -2,77 +2,74 @@ package utils
import ( import (
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"gitee.ltd/lxh/logger/log" "github.com/jordan-wright/email"
"gopkg.in/gomail.v2"
"mime" "mime"
"net/smtp"
"net/textproto"
"path/filepath" "path/filepath"
"wireguard-dashboard/config" "wireguard-dashboard/config"
) )
type mail struct { type mail struct {
md *gomail.Dialer *email.Email
addr string
auth smtp.Auth
} }
func Mail() mail { func Mail() *mail {
mailDialer := gomail.NewDialer(config.Config.Mail.Host, config.Config.Mail.Port, config.Config.Mail.User, config.Config.Mail.Password) var m mail
mailDialer.TLSConfig = &tls.Config{ em := email.NewEmail()
InsecureSkipVerify: config.Config.Mail.SkipTls, m.Email = em
m.auth = smtp.PlainAuth("", config.Config.Mail.User, config.Config.Mail.Password, config.Config.Mail.Host)
m.addr = fmt.Sprintf("%s:%d", config.Config.Mail.Host, config.Config.Mail.Port)
return &m
}
func (m *mail) VerifyConfig() (err error) {
if m == nil {
return errors.New("邮件客户端初始化失败")
} }
return mail{mailDialer}
if m.auth == nil || m.addr == "" {
return errors.New("邮件客户端未完成初始化")
}
return nil
} }
// SendMail // SendMail
// @description: 发送普通邮件 // @description: 发送附件
// @receiver mail
// @param subject
// @param toAddress
// @param content
// @return err
func (m mail) SendMail(subject, toAddress, content string) (err error) {
msg := gomail.NewMessage()
msg.SetHeader("From", msg.FormatAddress(m.md.Username, "wireguard-dashboard"))
msg.SetHeader("To", toAddress)
msg.SetHeader("Subject", subject)
msg.SetBody("text/plain", content)
if err = m.md.DialAndSend(msg); err != nil {
log.Errorf("发送普通邮件失败: %v", err.Error())
return
}
return
}
// SendMailWithAttach
// @description: 发送并携带附件
// @receiver m // @receiver m
// @param to
// @param subject // @param subject
// @param toAddress // @param attacheFilePath
// @param content
// @param attachPath
// @return err // @return err
func (m mail) SendMailWithAttach(subject, toAddress, content, attachPath string) (err error) { func (m *mail) SendMail(to, subject, content, attacheFilePath string) (err error) {
msg := gomail.NewMessage() m.From = fmt.Sprintf("wg-dashboard <%s>", config.Config.Mail.User)
msg.SetHeader("From", msg.FormatAddress(m.md.Username, "wireguard-dashboard")) m.To = []string{to}
msg.SetHeader("To", toAddress) m.Subject = subject
msg.SetHeader("Subject", subject) m.Text = []byte(content)
msg.SetBody("text/plain", content) if attacheFilePath != "" {
atch, err := m.AttachFile(attacheFilePath)
rename := m.getFileName(attachPath) if err != nil {
return fmt.Errorf("读取附件文件失败: %v", err.Error())
msg.Attach(attachPath, gomail.Rename(rename), gomail.SetHeader(map[string][]string{ }
"Content-Disposition": { emHeader := textproto.MIMEHeader{}
fmt.Sprintf(`attachment; filename="%s"`, mime.BEncoding.Encode("UTF-8", rename)), emHeader.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, mime.BEncoding.Encode("UTF-8", m.getFileName(attacheFilePath))))
}, atch.Header = emHeader
}))
if err = m.md.DialAndSend(msg); err != nil {
log.Errorf("发送普通邮件失败: %v", err.Error())
return
} }
return if config.Config.Mail.SkipTls {
return m.Send(m.addr, m.auth)
}
tlsConfig := &tls.Config{}
tlsConfig.InsecureSkipVerify = config.Config.Mail.SkipTls
tlsConfig.ServerName = config.Config.Mail.Host
return m.SendWithTLS(m.addr, m.auth, tlsConfig)
} }
// getFileName // getFileName
@ -80,6 +77,6 @@ func (m mail) SendMailWithAttach(subject, toAddress, content, attachPath string)
// @receiver m // @receiver m
// @param filePath // @param filePath
// @return string // @return string
func (m mail) getFileName(filePath string) string { func (m *mail) getFileName(filePath string) string {
return filepath.Base(filePath) return filepath.Base(filePath)
} }

View File

@ -1,13 +1,19 @@
package utils package utils
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"github.com/spf13/cast" "github.com/spf13/cast"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"math/rand" "math/rand"
"os"
"slices" "slices"
"strings" "strings"
"wireguard-dashboard/client" "wireguard-dashboard/client"
"wireguard-dashboard/model/entity"
"wireguard-dashboard/model/template_data"
"wireguard-dashboard/model/vo"
) )
type wireguard struct{} type wireguard struct{}
@ -68,6 +74,58 @@ func (w wireguard) GenerateClientIP(serverIP, rule string, assignedIPS ...string
return fmt.Sprintf("%s.%s", prefix, suffix) return fmt.Sprintf("%s.%s", prefix, suffix)
} }
// GenerateClientFile
// @description: 生成客户端临时配置文件
// @receiver w
// @param clientInfo
// @param setting
// @return tmpFilePath
// @return err
func (w wireguard) GenerateClientFile(clientInfo *entity.Client, setting *vo.ServerSetting) (tmpFilePath string, err error) {
var keys template_data.Keys
_ = json.Unmarshal([]byte(clientInfo.Keys), &keys)
var serverDNS []string
if *clientInfo.UseServerDns == 1 {
serverDNS = setting.DnsServer
}
// 处理一下数据
execData := template_data.ClientConfig{
PrivateKey: keys.PrivateKey,
IpAllocation: clientInfo.IpAllocation,
MTU: setting.MTU,
DNS: strings.Join(serverDNS, ","),
PublicKey: clientInfo.Server.PublicKey,
PresharedKey: keys.PresharedKey,
AllowedIPS: clientInfo.AllowedIps,
Endpoint: setting.EndpointAddress,
ListenPort: clientInfo.Server.ListenPort,
PersistentKeepalive: setting.PersistentKeepalive,
}
// 不同环境下处理文件路径
var outPath = "/tmp/" + fmt.Sprintf("%s.conf", clientInfo.Name)
var templatePath = "./template/wg.client.conf"
if os.Getenv("GIN_MODE") != "release" {
outPath = "E:\\Workspace\\Go\\wireguard-dashboard\\template\\" + fmt.Sprintf("%s.conf", clientInfo.Name)
templatePath = "E:\\Workspace\\Go\\wireguard-dashboard\\template\\wg.client.conf"
}
// 渲染数据
parseTemplate, err := Template().Parse(templatePath)
if err != nil {
return "", errors.New("读取模板文件失败")
}
err = Template().Execute(parseTemplate, execData, outPath)
if err != nil {
return "", errors.New("文件渲染失败")
}
return outPath, nil
}
// random // random
// @description: 随机模式 // @description: 随机模式
// @receiver w // @receiver w