:toda:
This commit is contained in:
57
component/captcha.go
Normal file
57
component/captcha.go
Normal 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
105
component/jwt.go
Normal 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
76
component/template.go
Normal 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
92
component/validator.go
Normal 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
168
component/wireguard.go
Normal 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
|
||||
}()
|
||||
}
|
Reference in New Issue
Block a user