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

20
http/api/api.go Normal file
View File

@@ -0,0 +1,20 @@
package api
import (
"github.com/gin-gonic/gin"
"wireguard-ui/http/response"
"wireguard-ui/http/vo"
)
// GetCurrentLoginUser
// @description: 获取当前登陆用户
// @param c
// @return *vo.User
func GetCurrentLoginUser(c *gin.Context) *vo.User {
if user, ok := c.Get("user"); ok {
return user.(*vo.User)
}
response.R(c).AuthorizationFailed("暂未登陆")
c.Abort()
return nil
}

239
http/api/client.go Normal file
View File

@@ -0,0 +1,239 @@
package api
import (
"encoding/json"
"fmt"
"gitee.ltd/lxh/logger/log"
"github.com/gin-gonic/gin"
"github.com/spf13/cast"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"os"
"strings"
"wireguard-ui/component"
"wireguard-ui/http/param"
"wireguard-ui/http/response"
"wireguard-ui/http/vo"
"wireguard-ui/model"
"wireguard-ui/service"
"wireguard-ui/utils"
)
type ClientApi struct{}
func Client() ClientApi {
return ClientApi{}
}
// Save
// @description: 新增/编辑客户端
// @param c
func (ClientApi) Save(c *gin.Context) {
var p param.SaveClient
if err := c.ShouldBind(&p); err != nil {
response.R(c).Validator(err)
return
}
var loginUser *vo.User
if loginUser = GetCurrentLoginUser(c); c.IsAborted() {
return
}
if err := service.Client().SaveClient(p, loginUser); err != nil {
response.R(c).FailedWithError(err)
return
}
response.R(c).OK()
}
// Delete
// @description: 删除客户端
// @receiver ClientApi
// @param c
func (ClientApi) Delete(c *gin.Context) {
id := c.Param("id")
if id == "" || id == "undefined" {
response.R(c).FailedWithError("id不能为空")
return
}
if err := service.Client().Delete(id); err != nil {
response.R(c).FailedWithError(err)
return
}
response.R(c).OK()
}
// List
// @description: 客户端分页列表
// @receiver ClientApi
// @param c
func (ClientApi) List(c *gin.Context) {
var p param.ClientList
if err := c.ShouldBind(&p); err != nil {
response.R(c).Validator(err)
return
}
data, total, err := service.Client().List(p)
if err != nil {
response.R(c).FailedWithError(err)
return
}
response.R(c).Paginate(data, total, p.Current, p.Size)
}
// GenerateKeys
// @description: 生成客户端密钥信息
// @receiver ClientApi
// @param c
func (ClientApi) GenerateKeys(c *gin.Context) {
// 为空,新增
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
response.R(c).FailedWithError(fmt.Errorf("生成密钥失败: %v", err.Error()))
return
}
publicKey := privateKey.PublicKey().String()
presharedKey, err := wgtypes.GenerateKey()
if err != nil {
response.R(c).FailedWithError(fmt.Errorf("生成密钥失败: %v", err.Error()))
return
}
keys := vo.Keys{
PrivateKey: privateKey.String(),
PublicKey: publicKey,
PresharedKey: presharedKey.String(),
}
response.R(c).OkWithData(keys)
}
// GenerateIP
// @description: 生成客户端IP
// @receiver ClientApi
// @param c
func (ClientApi) GenerateIP(c *gin.Context) {
// 获取一下服务端信息因为IP分配需要根据服务端的IP制定
serverInfo, err := service.Setting().GetWGServerForConfig()
if err != nil {
response.R(c).FailedWithError("获取服务端信息失败")
return
}
var assignIPS []string
// 只获取最新的一个
var clientInfo *model.Client
if err = service.Client().Order("created_at DESC").Take(&clientInfo).Error; err == nil {
// 遍历每一个ip是否可允许再分配
for _, ip := range strings.Split(clientInfo.IpAllocation, ",") {
if cast.ToInt64(utils.Network().GetIPSuffix(ip)) >= 255 {
log.Errorf("IP[%s]已无法分配新IP", ip)
continue
} else {
assignIPS = append(assignIPS, ip)
}
}
}
ips := utils.Network().GenerateIPByIPS(serverInfo.Address, assignIPS...)
response.R(c).OkWithData(ips)
}
// Download
// @description: 下载客户端配置文件
// @receiver ClientApi
// @param c
func (ClientApi) Download(c *gin.Context) {
var id = c.Param("id")
if id == "" || id == "undefined" {
response.R(c).FailedWithError("id不能为空")
return
}
var downloadType = c.Param("type")
if downloadType == "" {
response.R(c).FailedWithError("参数错误")
return
}
data, err := service.Client().GetByID(id)
if err != nil {
response.R(c).FailedWithError("获取客户端信息失败")
return
}
var keys vo.Keys
_ = json.Unmarshal([]byte(data.Keys), &keys)
globalSet, err := service.Setting().GetWGSetForConfig()
if err != nil {
response.R(c).FailedWithError("获取失败")
return
}
serverConf, err := service.Setting().GetWGServerForConfig()
if err != nil {
response.R(c).FailedWithError("获取失败")
return
}
outPath, err := component.Wireguard().GenerateClientFile(data, serverConf, globalSet)
if err != nil {
response.R(c).FailedWithError(fmt.Errorf("生成失败: %v", err.Error()))
return
}
// 根据不同下载类型执行不同逻辑
switch downloadType {
case "QRCODE": // 二维码
// 读取文件内容
fileContent, err := os.ReadFile(outPath)
if err != nil {
response.R(c).FailedWithError("读取文件失败")
return
}
png, err := utils.QRCode().GenerateQrCodeBase64(fileContent, 256)
if err != nil {
response.R(c).FailedWithError("生成二维码失败")
return
}
if err = os.Remove(outPath); err != nil {
log.Errorf("删除临时文件失败: %s", err.Error())
}
response.R(c).OkWithData(map[string]interface{}{
"qrCode": png,
})
case "FILE": // 文件
// 输出文件流
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename="+outPath)
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Connection", "keep-alive")
c.File(outPath)
if err = os.Remove(outPath); err != nil {
log.Errorf("删除临时文件失败: %s", err.Error())
}
case "EMAIL": // 邮件
if data.Email == "" {
response.R(c).FailedWithError("当前客户端并未配置通知邮箱!")
return
}
err = utils.Mail().SendMail(data.Email, fmt.Sprintf("客户端: %s", data.Name), "请查收附件", outPath)
if err != nil {
response.R(c).FailedWithError("发送邮件失败")
return
}
if err = os.Remove(outPath); err != nil {
log.Errorf("删除临时文件失败: %s", err.Error())
}
response.R(c).OK()
}
}

87
http/api/login.go Normal file
View File

@@ -0,0 +1,87 @@
package api
import (
"fmt"
"gitee.ltd/lxh/logger/log"
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
"wireguard-ui/component"
"wireguard-ui/http/param"
"wireguard-ui/http/response"
"wireguard-ui/service"
"wireguard-ui/utils"
)
type LoginApi struct{}
func Login() LoginApi {
return LoginApi{}
}
// Captcha
// @description: 获取验证码
// @receiver login
// @param c
func (LoginApi) Captcha(c *gin.Context) {
math := base64Captcha.DriverMath{Height: 120, Width: 480, Fonts: []string{"ApothecaryFont.ttf", "3Dumb.ttf"}}
mathDriver := math.ConvertFonts()
capt := base64Captcha.NewCaptcha(mathDriver, component.Captcha{})
id, base64Str, _, err := capt.Generate()
if err != nil {
response.R(c).FailedWithError(fmt.Errorf("生成验证码失败: %s", err.Error()))
return
}
response.R(c).OkWithData(map[string]any{
"id": id,
"captcha": base64Str,
})
}
// Login
// @description: 登陆
// @receiver login
// @param c
func (LoginApi) Login(c *gin.Context) {
var p param.Login
if err := c.ShouldBind(&p); err != nil {
response.R(c).Validator(err)
return
}
// 验证验证码是否正确
ok := component.Captcha{}.Verify(p.CaptchaId, p.CaptchaCode, true)
if !ok {
response.R(c).FailedWithError("验证码错误")
return
}
// 验证码正确,查询用户信息
user, err := service.User().GetUserByAccount(p.Account)
if err != nil {
response.R(c).FailedWithError("获取用户信息失败")
return
}
// 对比密码
if !utils.Password().ComparePassword(user.Password, p.Password) {
response.R(c).FailedWithError("密码错误")
return
}
// 生成token
token, expireAt, err := component.JWT().GenerateToken(user.Id)
if err != nil {
log.Errorf("用户[%s]生成token失败: %v", user.Account, err.Error())
response.R(c).FailedWithError("登陆失败!")
return
}
response.R(c).OkWithData(map[string]any{
"token": token,
"type": "Bearer",
"expireAt": expireAt,
})
}

252
http/api/user.go Normal file
View File

@@ -0,0 +1,252 @@
package api
import (
"errors"
"github.com/gin-gonic/gin"
"wireguard-ui/global/constant"
"wireguard-ui/http/param"
"wireguard-ui/http/response"
"wireguard-ui/model"
"wireguard-ui/service"
"wireguard-ui/utils"
)
type UserApi struct{}
func User() UserApi {
return UserApi{}
}
// GetLoginUser
// @description: 获取登陆用户信息
// @receiver UserApi
// @param c
func (UserApi) GetLoginUser(c *gin.Context) {
loginUser, ok := c.Get("user")
if !ok {
response.R(c).AuthorizationFailed("未登陆")
return
}
response.R(c).OkWithData(loginUser)
}
// SaveUser
// @description: 新增/编辑用户信息
// @receiver UserApi
// @param c
func (UserApi) SaveUser(c *gin.Context) {
var p param.SaveUser
if err := c.ShouldBind(&p); err != nil {
response.R(c).Validator(err)
return
}
// 如果是新增用户判断该用户是否已经存在
if p.Id == "" {
if len(p.Account) < 2 || len(p.Account) > 20 {
response.R(c).FailedWithError(errors.New("账号长度在2-20位"))
return
}
if len(p.Password) < 8 || len(p.Password) > 32 {
response.R(c).FailedWithError(errors.New("密码长度在8-32位"))
return
}
var count int64
if err := service.User().Model(&model.User{}).Where("account = ?", p.Account).Count(&count).Error; err != nil {
response.R(c).FailedWithError(err)
return
}
if count > 0 {
response.R(c).FailedWithError(errors.New("该账号已存在"))
return
}
}
userEnt := &model.User{
Base: model.Base{
Id: p.Id,
},
Account: p.Account,
Password: p.Password,
Nickname: p.Nickname,
Avatar: p.Avatar,
Contact: p.Contact,
IsAdmin: *p.IsAdmin,
Status: *p.Status,
}
if err := service.User().CreateUser(userEnt); err != nil {
response.R(c).FailedWithError(err)
return
}
response.R(c).OK()
}
// List
// @description: 用户列表
// @receiver UserApi
// @param c
func (UserApi) List(c *gin.Context) {
var p param.Page
if err := c.ShouldBind(&p); err != nil {
response.R(c).Validator(err)
return
}
data, total, err := service.User().List(p)
if err != nil {
response.R(c).FailedWithError(err)
return
}
response.R(c).Paginate(data, total, p.Current, p.Size)
}
// Delete
// @description: 删除用户
// @receiver UserApi
// @param c
func (UserApi) Delete(c *gin.Context) {
id := c.Param("id")
if id == "" || id == "undefined" {
response.R(c).FailedWithError("id不能为空")
return
}
// 是不是自己删除自己
if id == GetCurrentLoginUser(c).Id && c.IsAborted() {
response.R(c).FailedWithError("非法操作")
return
}
// 先查询一下
user, err := service.User().GetUserById(id)
if err != nil {
response.R(c).FailedWithError("获取用户信息失败")
return
}
// admin用户不能被删除
if user.Account == "admin" {
response.R(c).FailedWithError("当前用户不能被删除")
return
}
if err = service.User().Delete(id); err != nil {
response.R(c).FailedWithError("删除用户失败")
return
}
response.R(c).OK()
}
// Status
// @description: 设置用户状态
// @receiver UserApi
// @param c
func (UserApi) Status(c *gin.Context) {
id := c.Param("id")
if id == "" || id == "undefined" {
response.R(c).FailedWithError("id不能为空")
return
}
// 是不是自己删除自己
if id == GetCurrentLoginUser(c).Id && c.IsAborted() {
response.R(c).FailedWithError("非法操作")
return
}
// 先查询一下
user, err := service.User().GetUserById(id)
if err != nil {
response.R(c).FailedWithError("获取用户信息失败")
return
}
// admin用户不能被删除
if user.Account == "admin" {
response.R(c).FailedWithError("当前用户状态不可被变更")
return
}
var state = constant.Enabled
if user.Status == constant.Enabled {
state = constant.Disabled
}
if err := service.User().Status(id, state); err != nil {
response.R(c).FailedWithError(err)
return
}
response.R(c).OK()
}
// ChangePassword
// @description: 修改密码
// @receiver UserApi
// @param c
func (UserApi) ChangePassword(c *gin.Context) {
var p param.ChangePassword
if err := c.ShouldBind(&p); err != nil {
response.R(c).Validator(err)
return
}
user := GetCurrentLoginUser(c)
if user == nil {
response.R(c).FailedWithError("用户信息错误")
return
}
// 判断原密码是否对
if !utils.Password().ComparePassword(user.Password, p.OriginalPassword) {
response.R(c).FailedWithError("原密码错误")
return
}
// 修改密码
if err := service.User().ChangePassword(user.Id, p.NewPassword); err != nil {
response.R(c).FailedWithError(err)
return
}
response.R(c).OK()
}
// ResetPassword
// @description: 重置密码
// @receiver UserApi
// @param c
func (UserApi) ResetPassword(c *gin.Context) {
var id = c.Param("id")
if id == "" || id == "undefined" {
response.R(c).FailedWithError("id不能为空")
return
}
// 先查询一下
user, err := service.User().GetUserById(id)
if err != nil {
response.R(c).FailedWithError("获取用户信息失败")
return
}
if user.Status != constant.Enabled {
response.R(c).FailedWithError("当前用户不可重置密码")
return
}
// 修改密码
if err := service.User().ChangePassword(user.Id, "admin123"); err != nil {
response.R(c).FailedWithError(err)
return
}
response.R(c).OK()
}