:toda:
This commit is contained in:
20
http/api/api.go
Normal file
20
http/api/api.go
Normal 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
239
http/api/client.go
Normal 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
87
http/api/login.go
Normal 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
252
http/api/user.go
Normal 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()
|
||||
}
|
||||
57
http/middleware/authorization.go
Normal file
57
http/middleware/authorization.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
"wireguard-ui/component"
|
||||
"wireguard-ui/global/constant"
|
||||
"wireguard-ui/http/response"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
// Authorization
|
||||
// @description: 授权中间件
|
||||
// @return gin.HandlerFunc
|
||||
func Authorization() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" || !strings.HasPrefix(token, "Bearer ") {
|
||||
response.R(c).AuthorizationFailed("未登陆")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
userClaims, err := component.JWT().ParseToken(token)
|
||||
if err != nil {
|
||||
response.R(c).AuthorizationFailed("未登陆")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果token的颁发者与请求的站点不一致,则直接给它狗日的丢出去
|
||||
if userClaims.Issuer != utils.WebSite().GetHost(c.Request.Header.Get("Referer")) {
|
||||
response.R(c).AuthorizationFailed("未登陆")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 查询用户
|
||||
user, err := service.User().GetUserById(userClaims.ID)
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("用户不存在")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if user.Status != constant.Enabled {
|
||||
response.R(c).FailedWithError("用户状态异常,请联系管理员处理!")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息放入上下文
|
||||
c.Set("user", &user)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
40
http/param/client.go
Normal file
40
http/param/client.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package param
|
||||
|
||||
import (
|
||||
"wireguard-ui/global/constant"
|
||||
)
|
||||
|
||||
// SaveClient
|
||||
// @description: 新增/编辑客户端
|
||||
type SaveClient struct {
|
||||
Id string `json:"id" form:"id" label:"id" binding:"omitempty"` // id
|
||||
Name string `json:"name" form:"name" label:"名称" binding:"required,min=1,max=64"` // 名称
|
||||
Email string `json:"email" form:"email" label:"联系邮箱" binding:"omitempty"` // 联系邮箱
|
||||
SubnetRange string `json:"subnetRange" form:"subnetRange" label:"子网范围" binding:"omitempty"` // 子网范围
|
||||
IpAllocation []string `json:"ipAllocation" form:"ipAllocation" label:"客户端IP" binding:"required,dive"` // IP地址
|
||||
AllowedIps []string `json:"allowedIps" form:"allowedIps" label:"allowedIps" binding:"omitempty,dive"` // 允许访问的IP段
|
||||
ExtraAllowedIps []string `json:"extraAllowedIps" form:"extraAllowedIps" label:"extraAllowedIps" binding:"omitempty,dive"` // 其他允许访问的IP段
|
||||
Endpoint string `json:"endpoint" form:"endpoint" label:"endpoint" binding:"omitempty"` // 服务端地址
|
||||
UseServerDns *constant.Status `json:"useServerDns" form:"useServerDns" label:"useServerDns" binding:"required,oneof=0 1"` // 是否使用服务端DNS 1 - 是 | 0 - 否
|
||||
Keys *Keys `json:"keys" form:"keys" label:"密钥信息" binding:"required"` // 密钥
|
||||
Enabled *constant.Status `json:"enabled" form:"enabled" label:"状态" binding:"required,oneof=0 1"` // 状态 1 - 启用 | 0 - 禁用
|
||||
OfflineMonitoring *constant.Status `json:"offlineMonitoring" form:"offlineMonitoring" label:"离线通知" binding:"required,oneof=0 1"` // 离线通知 1 - 启用 | 0 - 禁用
|
||||
}
|
||||
|
||||
// Keys
|
||||
// @description: 客户端密钥信息
|
||||
type Keys struct {
|
||||
PrivateKey string `json:"privateKey" form:"privateKey" label:"私钥" binding:"required"`
|
||||
PublicKey string `json:"publicKey" form:"publicKey" label:"公钥" binding:"required"`
|
||||
PresharedKey string `json:"presharedKey" form:"presharedKey" label:"共享密钥" binding:"required"`
|
||||
}
|
||||
|
||||
// ClientList
|
||||
// @description: 客户端列表
|
||||
type ClientList struct {
|
||||
Name string `json:"name" form:"name" label:"名称" binding:"omitempty"` // 客户端名称
|
||||
Email string `json:"email" form:"email" label:"邮箱" binding:"omitempty,email"` // 联系邮箱
|
||||
IpAllocation string `json:"ipAllocation" form:"ipAllocation" label:"IP范围段" binding:"omitempty"` // 客户端IP
|
||||
Enabled *int `json:"enabled" form:"enabled" label:"状态" binding:"omitempty,oneof=0 1"` // 客户端状态
|
||||
Page
|
||||
}
|
||||
10
http/param/login.go
Normal file
10
http/param/login.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package param
|
||||
|
||||
// Login
|
||||
// @description: 登陆
|
||||
type Login struct {
|
||||
Account string `json:"account" form:"account" label:"账号" binding:"required,min=2,max=20"`
|
||||
Password string `json:"password" form:"password" label:"密码" binding:"required,min=8,max=32"`
|
||||
CaptchaId string `json:"captchaId" form:"captchaId" label:"验证码ID" binding:"required"`
|
||||
CaptchaCode string `json:"captchaCode" form:"captchaCode" label:"验证码" binding:"required,len=4"`
|
||||
}
|
||||
6
http/param/request.go
Normal file
6
http/param/request.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package param
|
||||
|
||||
type Page struct {
|
||||
Current int64 `json:"current" form:"current" label:"页码数" binding:"required"`
|
||||
Size int64 `json:"size" form:"size" label:"每页数量" binging:"required"`
|
||||
}
|
||||
11
http/param/server.go
Normal file
11
http/param/server.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package param
|
||||
|
||||
type SaveServer struct {
|
||||
IPScope []string `json:"ipScope" form:"IPScope" label:"IPScope" binding:"required"`
|
||||
ListenPort uint64 `json:"listenPort" form:"listenPort" label:"listenPort" binding:"required"`
|
||||
PrivateKey string `json:"privateKey" form:"privateKey" label:"privateKey" binding:"required"`
|
||||
PublicKey string `json:"publicKey" form:"publicKey" label:"publicKey" binding:"required"`
|
||||
PostUpScript string `json:"postUpScript,omitempty" form:"postUpScript" label:"postUpScript" binding:"omitempty"`
|
||||
PreDownScript string `json:"preDownScript,omitempty" form:"preDownScript" label:"preDownScript" binding:"omitempty"`
|
||||
PostDownScript string `json:"postDownScript,omitempty" form:"postDownScript" label:"postDownScript" binding:"omitempty"`
|
||||
}
|
||||
24
http/param/user.go
Normal file
24
http/param/user.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package param
|
||||
|
||||
import "wireguard-ui/global/constant"
|
||||
|
||||
// SaveUser
|
||||
// @description: 新增/编辑用户信息
|
||||
type SaveUser struct {
|
||||
Id string `json:"id" form:"id" label:"id" binding:"omitempty"` // id
|
||||
Account string `json:"account" form:"account" label:"账户号" binding:"required_without=Id"` // 账户号
|
||||
Password string `json:"password" form:"password" label:"密码" binding:"required_without=Id"` // 密码
|
||||
Nickname string `json:"nickname" form:"nickname" label:"昵称" binding:"required,min=2"` // 昵称
|
||||
Avatar string `json:"avatar" form:"avatar" label:"头像" binding:"omitempty"` // 头像
|
||||
Contact string `json:"contact" form:"contact" label:"联系方式" binding:"omitempty"` // 联系方式
|
||||
IsAdmin *constant.UserType `json:"isAdmin" form:"isAdmin" label:"是否为管理员" binding:"required,oneof=0 1"` // 是否为管理员 0 - 否 | 1 - 是
|
||||
Status *constant.Status `json:"status" form:"status" label:"状态" binding:"required,oneof=0 1"` // 用户状态 0 - 禁用 | 1 - 启用
|
||||
}
|
||||
|
||||
// ChangePassword
|
||||
// @description: 修改密码
|
||||
type ChangePassword struct {
|
||||
OriginalPassword string `json:"originalPassword" form:"originalPassword" label:"原密码" binding:"required,min=8,max=32"` // 原密码
|
||||
NewPassword string `json:"newPassword" form:"newPassword" label:"新密码" binding:"required,min=8,max=32"` // 新密码
|
||||
ConfirmPassword string `json:"confirmPassword" form:"confirmPassword" label:"确认密码" binding:"eqfield=NewPassword"` // 确认密码
|
||||
}
|
||||
109
http/response/response.go
Normal file
109
http/response/response.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"wireguard-ui/component"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
type PageData[T any] struct {
|
||||
Current int `json:"current"` // 当前页码
|
||||
Size int `json:"size"` // 每页数量
|
||||
Total int64 `json:"total"` // 总数
|
||||
TotalPage int `json:"totalPage"` // 总页数
|
||||
Records T `json:"records"` // 返回数据
|
||||
}
|
||||
|
||||
type response struct {
|
||||
c *gin.Context
|
||||
}
|
||||
|
||||
func R(c *gin.Context) response {
|
||||
return response{c}
|
||||
}
|
||||
|
||||
func (r response) OK() {
|
||||
r.c.JSON(http.StatusOK, gin.H{
|
||||
"code": http.StatusOK,
|
||||
"message": "success",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r response) OkWithData(data any) {
|
||||
r.c.JSON(http.StatusOK, gin.H{
|
||||
"code": http.StatusOK,
|
||||
"message": "success",
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
// Paginate
|
||||
// @description: 页码数
|
||||
// @receiver r
|
||||
// @param data
|
||||
// @param total
|
||||
// @param current
|
||||
// @param size
|
||||
func (r response) Paginate(data any, total int64, current, size int64) {
|
||||
// 处理一下页码、页数量
|
||||
if current == -1 {
|
||||
current = 1
|
||||
size = total
|
||||
}
|
||||
// 计算总页码
|
||||
totalPage := utils.Paginate().Generate(total, int(size))
|
||||
// 返回结果
|
||||
r.c.JSON(http.StatusOK, map[string]any{
|
||||
"code": http.StatusOK,
|
||||
"data": &PageData[any]{Current: int(current), Size: int(size), Total: total, TotalPage: totalPage, Records: data},
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
func (r response) AuthorizationFailed(msg string) {
|
||||
if msg == "" {
|
||||
msg = "authorized failed"
|
||||
}
|
||||
r.c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": http.StatusUnauthorized,
|
||||
"message": msg,
|
||||
})
|
||||
}
|
||||
|
||||
func (r response) Failed() {
|
||||
r.c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": http.StatusBadRequest,
|
||||
"message": "failed",
|
||||
})
|
||||
}
|
||||
|
||||
func (r response) FailedWithError(err any) {
|
||||
var errStr string
|
||||
switch err.(type) {
|
||||
case error:
|
||||
errStr = err.(error).Error()
|
||||
case string:
|
||||
errStr = err.(string)
|
||||
}
|
||||
|
||||
r.c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": http.StatusBadRequest,
|
||||
"message": errStr,
|
||||
})
|
||||
}
|
||||
|
||||
func (r response) Validator(err error) {
|
||||
r.c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": http.StatusBadRequest,
|
||||
"message": component.Error(err),
|
||||
})
|
||||
}
|
||||
|
||||
func (r response) Internal() {
|
||||
r.c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": http.StatusInternalServerError,
|
||||
"message": "server error",
|
||||
})
|
||||
}
|
||||
23
http/router/client.go
Normal file
23
http/router/client.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-ui/http/api"
|
||||
"wireguard-ui/http/middleware"
|
||||
)
|
||||
|
||||
// ClientApi
|
||||
// @description: 登陆相关API
|
||||
// @param r
|
||||
func ClientApi(r *gin.RouterGroup) {
|
||||
client := r.Group("client", middleware.Authorization())
|
||||
{
|
||||
client.POST("", api.Client().Save) // 新增/编辑客户端
|
||||
client.DELETE("/:id", api.Client().Delete) // 删除客户端
|
||||
client.GET("/list", api.Client().List) // 客户端列表
|
||||
client.POST("/generate-keys", api.Client().GenerateKeys) // 生成客户端密钥
|
||||
client.POST("/generate-ip", api.Client().GenerateIP) // 生成客户端IP
|
||||
client.GET("/download/:id/:type", api.Client().Download) // 下载客户端配置文件
|
||||
}
|
||||
|
||||
}
|
||||
18
http/router/login.go
Normal file
18
http/router/login.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-ui/http/api"
|
||||
)
|
||||
|
||||
// LoginApi
|
||||
// @description: 登陆相关API
|
||||
// @param r
|
||||
func LoginApi(r *gin.RouterGroup) {
|
||||
login := r.Group("/login")
|
||||
{
|
||||
login.GET("/captcha", api.Login().Captcha) // 获取登陆验证码
|
||||
login.POST("", api.Login().Login) // 登陆
|
||||
}
|
||||
|
||||
}
|
||||
35
http/router/root.go
Normal file
35
http/router/root.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Option func(engine *gin.RouterGroup)
|
||||
|
||||
var options []Option
|
||||
|
||||
func includeRouters(opts ...Option) {
|
||||
options = append(options, opts...)
|
||||
}
|
||||
|
||||
func InitRouter() *gin.Engine {
|
||||
r := gin.New()
|
||||
// 开启IP 追踪
|
||||
r.ForwardedByClientIP = true
|
||||
// 将请求打印至控制台
|
||||
r.Use(gin.Logger())
|
||||
|
||||
for _, opt := range options {
|
||||
opt(r.Group("api"))
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func Rooters() {
|
||||
includeRouters(
|
||||
LoginApi,
|
||||
UserApi,
|
||||
ClientApi,
|
||||
)
|
||||
}
|
||||
23
http/router/user.go
Normal file
23
http/router/user.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-ui/http/api"
|
||||
"wireguard-ui/http/middleware"
|
||||
)
|
||||
|
||||
// UserApi
|
||||
// @description: 用户相关API
|
||||
// @param r
|
||||
func UserApi(r *gin.RouterGroup) {
|
||||
userApi := r.Group("user", middleware.Authorization())
|
||||
{
|
||||
userApi.GET("/info", api.User().GetLoginUser) // 获取当前登陆用户信息
|
||||
userApi.POST("", api.User().SaveUser) // 新增/编辑用户
|
||||
userApi.DELETE("/:id", api.User().Delete) // 删除用户
|
||||
userApi.GET("/list", api.User().List) // 分页列表
|
||||
userApi.PUT("/status/:id", api.User().Status) // 修改用户状态
|
||||
userApi.PUT("/change-password", api.User().ChangePassword) // 修改用户密码
|
||||
userApi.PUT("/reset-password/:id", api.User().ResetPassword) // 重置用户密码
|
||||
}
|
||||
}
|
||||
33
http/vo/client.go
Normal file
33
http/vo/client.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package vo
|
||||
|
||||
import "wireguard-ui/model"
|
||||
|
||||
// ClientItem
|
||||
// @description: 客户端信息
|
||||
type ClientItem struct {
|
||||
Id string `json:"id"` // id
|
||||
Name string `json:"name"` // 名称
|
||||
Email string `json:"email"` // 通知邮箱
|
||||
//SubnetRange string `json:"subnetRange"` // 子网范围段
|
||||
IpAllocation []string `json:"ipAllocation" gorm:"-"` // 分配的IP
|
||||
IpAllocationStr string `json:"-" gorm:"ipAllocationStr"`
|
||||
AllowedIps []string `json:"allowedIps" gorm:"-"` // 允许访问的IP
|
||||
AllowedIpsStr string `json:"-" gorm:"allowedIpsStr"`
|
||||
ExtraAllowedIps []string `json:"extraAllowedIps" gorm:"-"` // 其他允许访问的IP
|
||||
ExtraAllowedIpsStr string `json:"-" gorm:"extraAllowedIpsStr"`
|
||||
Endpoint string `json:"endpoint"` // 服务端点
|
||||
UseServerDns int `json:"useServerDns"` // 是否使用服务端DNS
|
||||
Keys *Keys `json:"keys" gorm:"-"` // 密钥等
|
||||
KeysStr string `json:"-" gorm:"keys_str"`
|
||||
CreateUser string `json:"createUser"` // 创建人
|
||||
Enabled int `json:"enabled"` // 是否启用
|
||||
OfflineMonitoring int `json:"offlineMonitoring"` // 离线通知
|
||||
CreatedAt model.JsonTime `json:"createdAt"` // 创建时间
|
||||
UpdatedAt model.JsonTime `json:"updatedAt"` // 更新时间
|
||||
}
|
||||
|
||||
type Keys struct {
|
||||
PrivateKey string `json:"privateKey"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
PresharedKey string `json:"presharedKey"`
|
||||
}
|
||||
28
http/vo/user.go
Normal file
28
http/vo/user.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package vo
|
||||
|
||||
import "wireguard-ui/global/constant"
|
||||
|
||||
// UserItem
|
||||
// @description: 用户列表的数据
|
||||
type UserItem struct {
|
||||
Id string `json:"id"`
|
||||
Account string `json:"account"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Contact string `json:"contact"`
|
||||
IsAdmin constant.UserType `json:"isAdmin"`
|
||||
Status constant.Status `json:"status"`
|
||||
}
|
||||
|
||||
// User
|
||||
// @description: 用户信息
|
||||
type User struct {
|
||||
Id string `json:"id"`
|
||||
Account string `json:"account"`
|
||||
Password string `json:"-"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Contact string `json:"contact"`
|
||||
IsAdmin constant.UserType `json:"isAdmin"`
|
||||
Status constant.Status `json:"status"`
|
||||
}
|
||||
Reference in New Issue
Block a user