282 lines
6.7 KiB
Go
282 lines
6.7 KiB
Go
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"
|
||
"time"
|
||
"wireguard-ui/component"
|
||
"wireguard-ui/http/param"
|
||
"wireguard-ui/http/response"
|
||
"wireguard-ui/http/vo"
|
||
"wireguard-ui/model"
|
||
"wireguard-ui/script"
|
||
"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
|
||
}
|
||
|
||
go func() {
|
||
if err := script.New().GenerateConfig(); err != nil {
|
||
log.Errorf("执行脚本失败")
|
||
}
|
||
}()
|
||
|
||
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
|
||
}
|
||
|
||
go func() {
|
||
if err := script.New().GenerateConfig(); err != nil {
|
||
log.Errorf("执行脚本失败")
|
||
}
|
||
}()
|
||
|
||
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
|
||
}
|
||
|
||
for i, v := range data {
|
||
if v.Keys != nil {
|
||
// 获取客户端链接信息
|
||
peer, err := component.Wireguard().GetClientByPublicKey(v.Keys.PublicKey)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
var ipAllocation string
|
||
for _, iaip := range peer.AllowedIPs {
|
||
ipAllocation += iaip.String() + ","
|
||
}
|
||
data[i].DataTraffic = &vo.DataTraffic{
|
||
Online: time.Since(peer.LastHandshakeTime).Minutes() < 3,
|
||
ReceiveBytes: utils.FlowCalculation().Parse(peer.TransmitBytes),
|
||
TransmitBytes: utils.FlowCalculation().Parse(peer.ReceiveBytes),
|
||
ConnectEndpoint: ipAllocation,
|
||
LastHandAt: peer.LastHandshakeTime.Format("2006-01-02 15:04:05"),
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
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...)
|
||
|
||
clientIPS := ips
|
||
serverIPS := serverInfo.Address
|
||
response.R(c).OkWithData(map[string]any{
|
||
"clientIPS": clientIPS,
|
||
"serverIPS": serverIPS,
|
||
})
|
||
}
|
||
|
||
// 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()
|
||
}
|
||
|
||
}
|