This commit is contained in:
coward
2024-07-05 14:41:35 +08:00
commit 1f7f57ec9f
70 changed files with 4923 additions and 0 deletions

57
component/captcha.go Normal file
View File

@@ -0,0 +1,57 @@
package component
import (
"context"
"fmt"
"os"
"strings"
"time"
"wireguard-ui/global/client"
"wireguard-ui/global/constant"
)
type Captcha struct{}
// Set
// @description: 验证码放入指定存储
// @receiver Captcha
// @param id
// @param value
// @return error
func (Captcha) Set(id string, value string) error {
return client.Redis.Set(context.Background(), fmt.Sprintf("%s:%s", constant.Captcha, id), value, 2*time.Minute).Err()
}
// Get
// @description: 获取验证码信息
// @receiver Captcha
// @param id
// @param clear
// @return string
func (Captcha) Get(id string, clear bool) string {
val, err := client.Redis.Get(context.Background(), fmt.Sprintf("%s:%s", constant.Captcha, id)).Result()
if err != nil {
return ""
}
if clear {
client.Redis.Del(context.Background(), fmt.Sprintf("%s:%s", constant.Captcha, id))
return val
}
return val
}
// Verify
// @description: 校验
// @receiver Captcha
// @param id
// @param answer
// @param clear
// @return bool
func (c Captcha) Verify(id, answer string, clear bool) bool {
if os.Getenv("GIN_MODE") != "release" {
return true
}
return strings.ToUpper(answer) == strings.ToUpper(c.Get(id, clear))
}

105
component/jwt.go Normal file
View File

@@ -0,0 +1,105 @@
package component
import (
"context"
"errors"
"fmt"
"gitee.ltd/lxh/logger/log"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"strings"
"time"
"wireguard-ui/config"
"wireguard-ui/global/client"
"wireguard-ui/global/constant"
)
// jwt密钥
const secret = "JQo7L1RYa8ArFWuj0wC9PyM3VzmDIfXZ2d5tsTOBhNgviE64bnKqGpSckxUlHey6"
type JwtComponent struct {
ID string `json:"id"`
jwt.RegisteredClaims
}
// JWT
// @description: 初始化JWT组件
// @return JwtComponent
func JWT() JwtComponent {
return JwtComponent{}
}
// GenerateToken
// @description: 生成token
// @receiver JwtComponent
// @param userId
// @return token
// @return expireTime
// @return err
func (JwtComponent) GenerateToken(userId string) (token string, expireTime *jwt.NumericDate, err error) {
timeNow := time.Now().Local()
expireTime = jwt.NewNumericDate(timeNow.Add(7 * time.Hour))
notBefore := jwt.NewNumericDate(timeNow)
issuedAt := jwt.NewNumericDate(timeNow)
claims := JwtComponent{
ID: userId,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: config.Config.Http.Endpoint, // 颁发站点
Subject: "you can you up,no can no bb", // 发布主题
ExpiresAt: expireTime, // 过期时间
NotBefore: notBefore, // token不得早于该时间
IssuedAt: issuedAt, // token颁发时间
ID: strings.ReplaceAll(uuid.NewString(), "-", ""), // 该token的id
},
}
t := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
token, err = t.SignedString([]byte(secret))
if err != nil {
log.Errorf("生成token失败: %v", err.Error())
return "", nil, errors.New("生成token失败")
}
client.Redis.Set(context.Background(), fmt.Sprintf("%s:%s", constant.UserToken, userId), token, 7*time.Hour)
return
}
// ParseToken
// @description: 解析token
// @receiver JwtComponent
// @param token
// @return *JwtComponent
// @return error
func (JwtComponent) ParseToken(token string) (*JwtComponent, error) {
tokenStr := strings.Split(token, "Bearer ")[1]
t, err := jwt.ParseWithClaims(tokenStr, &JwtComponent{}, func(token *jwt.Token) (any, error) {
return []byte(secret), nil
})
if claims, ok := t.Claims.(*JwtComponent); ok && t.Valid {
userToken, err := client.Redis.Get(context.Background(), fmt.Sprintf("%s:%s", constant.UserToken, claims.ID)).Result()
if err != nil {
log.Errorf("缓存中用户[%s]的token查找失败: %v", claims.ID, err.Error())
return nil, errors.New("token不存在")
}
if userToken != tokenStr {
log.Errorf("token不一致")
return nil, errors.New("token错误")
}
return claims, nil
} else {
return nil, err
}
}
// Logout
// @description: 退出登陆
// @receiver JwtComponent
// @param userId
// @return error
func (JwtComponent) Logout(userId string) error {
return client.Redis.Del(context.Background(), fmt.Sprintf("%s:%s", constant.UserToken, userId)).Err()
}

76
component/template.go Normal file
View File

@@ -0,0 +1,76 @@
package component
import (
"gitee.ltd/lxh/logger/log"
"html/template"
"os"
)
type TemplateComponent struct{}
func Template() TemplateComponent {
return TemplateComponent{}
}
// Execute
// @description: 渲染数据模板并生成对应文件
// @receiver t
// @param templateFilePath
// @param outFilePath
// @param data
// @return err
func (t TemplateComponent) Execute(templateFilePath, outFilePath string, data any) (err error) {
parseTemplate, err := t.ParseTemplate(templateFilePath)
if err != nil {
log.Errorf("解析模板信息失败:%v", err.Error())
return err
}
err = t.Render(parseTemplate, data, outFilePath)
if err != nil {
log.Errorf("渲染模板失败: %v", err.Error())
return err
}
return nil
}
// ParseTemplate
// @description: 解析模板
// @receiver t
// @param filepath
// @return t
// @return err
func (t TemplateComponent) ParseTemplate(filepath string) (tp *template.Template, err error) {
file, err := os.ReadFile(filepath)
if err != nil {
return
}
tp, err = template.New("wg.conf").Parse(string(file))
return
}
// Render
// @description: 渲染模板
// @receiver t
// @param tp
// @param data
// @param filepath
// @return err
func (t TemplateComponent) Render(tp *template.Template, data any, filepath string) (err error) {
wg0Conf, err := os.Create(filepath)
if err != nil {
log.Errorf("创建文件[%s]失败: %v", filepath, err.Error())
return
}
defer func() {
if err = wg0Conf.Close(); err != nil {
log.Errorf("关闭文件[%s]失败: %v", filepath, err.Error())
return
}
}()
return tp.Execute(wg0Conf, data)
}

92
component/validator.go Normal file
View File

@@ -0,0 +1,92 @@
package component
import (
"errors"
"gitee.ltd/lxh/logger/log"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
"reflect"
"strings"
)
var validatorTrans ut.Translator
func init() {
initValidatorTranslator()
}
func Error(err error) string {
var errs validator.ValidationErrors
ok := errors.As(err, &errs)
if !ok {
return err.Error()
}
errMap := errs.Translate(validatorTrans)
var errMsg string
for _, v := range errMap {
errMsg = v
}
return errMsg
}
// initValidatorTranslator
// @description: 初始化翻译机
// @receiver vli
func initValidatorTranslator() {
//修改gin框架中的Validator属性实现自定制
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 注册一个获取json tag的自定义方法
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("label"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
zhT := zh.New() //中文翻译器
enT := en.New() //英文翻译器
// 第一个参数是备用fallback的语言环境
// 后面的参数是应该支持的语言环境(支持多个)
// uni := ut.New(zhT, zhT) 也是可以的
uni := ut.New(enT, zhT, enT)
// locale 通常取决于 http 请求头的 'Accept-Language'
var ok bool
// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
validatorTrans, ok = uni.GetTranslator("zh")
if !ok {
log.Errorf("获取翻译机失败")
return
}
err := overrideTranslator(v, validatorTrans)
if err != nil {
log.Errorf("覆盖原有翻译失败: %v", err.Error())
return
}
}
}
// overrideTranslator
// @description: 覆盖原有翻译
// @param v
// @param translator
// @return error
func overrideTranslator(v *validator.Validate, translator ut.Translator) error {
err := zhTranslations.RegisterDefaultTranslations(v, translator)
if err != nil {
return err
}
return nil
}

168
component/wireguard.go Normal file
View File

@@ -0,0 +1,168 @@
package component
import (
"encoding/json"
"errors"
"fmt"
"gitee.ltd/lxh/logger/log"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"gopkg.in/fsnotify/fsnotify.v1"
"os"
"strings"
"time"
"wireguard-ui/command"
"wireguard-ui/config"
"wireguard-ui/global/client"
"wireguard-ui/model"
"wireguard-ui/template/render_data"
)
type WireguardComponent struct{}
func Wireguard() WireguardComponent {
return WireguardComponent{}
}
// GetClients
// @description: 获取所有链接的客户端信息
// @receiver w
// @return peers
// @return err
func (w WireguardComponent) GetClients() (peers []wgtypes.Peer, err error) {
device, err := client.WireguardClient.Devices()
if err != nil {
return
}
for _, v := range device {
return v.Peers, nil
}
return
}
// GetClientByPublicKey
// @description: 根据公钥获取指定客户端信息
// @receiver w
// @return peer
// @return err
func (w WireguardComponent) GetClientByPublicKey(pk string) (peer *wgtypes.Peer, err error) {
peers, err := w.GetClients()
if err != nil {
return
}
for _, v := range peers {
if v.PublicKey.String() == pk {
return &v, nil
}
}
return
}
// GenerateClientFile
// @description: 生成客户端文件
// @receiver w
// @param clientInfo
// @param server
// @param setting
// @return filePath
// @return err
func (w WireguardComponent) GenerateClientFile(clientInfo *model.Client, server *render_data.Server, setting *render_data.ServerSetting) (filePath string, err error) {
var keys render_data.Keys
_ = json.Unmarshal([]byte(clientInfo.Keys), &keys)
var serverDNS []string
if clientInfo.UseServerDns == 1 {
serverDNS = setting.DnsServer
}
// 处理一下数据
execData := render_data.ClientConfig{
PrivateKey: keys.PrivateKey,
IpAllocation: clientInfo.IpAllocation,
MTU: setting.MTU,
DNS: strings.Join(serverDNS, ","),
PublicKey: server.PublicKey,
PresharedKey: keys.PresharedKey,
AllowedIPS: clientInfo.AllowedIps,
Endpoint: setting.EndpointAddress,
ListenPort: int(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"
}
err = Template().Execute(templatePath, outPath, execData)
if err != nil {
return "", errors.New("文件渲染失败")
}
return outPath, nil
}
// ServerControl
// @description: 服务端控制
// @receiver w
// @return error
func (w WireguardComponent) ServerControl(filePath string) {
switch config.Config.Wireguard.RestartMode {
case "NOW": // 立即执行
w.watchConfigFile(filePath)
case "DELAY": // 延迟执行
time.Sleep(time.Duration(config.Config.Wireguard.DelayTime) * time.Second)
w.watchConfigFile(filePath)
}
}
// watchConfigFile
// @description: 监听并重新操作配置文件
// @receiver w
// @param filepath
func (w WireguardComponent) watchConfigFile(filepath string) {
go func() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Errorf("创建文件监控失败: %v", err.Error())
return
}
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Op == fsnotify.Write {
command.RestartWireguard(true)
}
// 打印监听事件
log.Infof("监听事件是:%s", event.String())
case _, ok := <-watcher.Errors:
if !ok {
return
}
}
}
}()
if err = watcher.Add(filepath); err != nil {
log.Errorf("添加[%s]监听失败: %v", filepath, err.Error())
return
}
<-done
}()
}