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/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 } component.Wireguard().ServerControl("E:\\Workspace\\Go\\wireguard-ui\\template\\tmp\\wg0.conf") 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 } component.Wireguard().ServerControl("E:\\Workspace\\Go\\wireguard-ui\\template\\tmp\\wg0.conf") 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() } }