This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

View File

@@ -0,0 +1 @@
支付、通知等URL

View File

@@ -0,0 +1,30 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package api
import (
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/api/pay/alipay"
serversapi "github.com/TeaOSLab/EdgeUser/internal/web/actions/default/api/servers"
domainsapi "github.com/TeaOSLab/EdgeUser/internal/web/actions/default/api/servers/domains"
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Prefix("/api").
// pay
Post("/pay/alipay/notify", new(alipay.NotifyAction)).
// servers
Helper(helpers.NewUserMustAuth("")).
Post("/user/servers/list", new(serversapi.ServersListAction)).
Post("/user/servers/domains/add", new(domainsapi.DomainsAddAction)).
Post("/user/servers/domains/delete", new(domainsapi.DomainsDeleteAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,41 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package alipay
import (
"encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/TeaOSLab/EdgeUser/internal/remotelogs"
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/login/loginutils"
)
// NotifyAction TODO 防止cc攻击
type NotifyAction struct {
actionutils.ParentAction
}
func (this *NotifyAction) RunPost(params struct{}) {
formData, err := json.Marshal(this.Request.Form)
if err != nil {
// 提示中加入IP方便调试
remotelogs.Error("ALIPAY", "["+loginutils.RemoteIP(&this.ActionObject)+"]NotifyAction: encode form failed: "+err.Error())
this.Fail(err.Error())
return
}
// 接口会自动根据传输的订单号决定使用哪种支付方式验证
_, err = this.RPC().UserOrderRPC().NotifyUserOrderPayment(this.UserContext(), &pb.NotifyUserOrderPaymentRequest{
PayMethod: userconfigs.PayMethodAlipay,
FormData: formData,
})
if err != nil {
// 提示中加入IP方便调试
remotelogs.Error("ALIPAY", "["+loginutils.RemoteIP(&this.ActionObject)+"]NotifyAction: verify failed: "+err.Error())
this.Fail(err.Error())
return
}
this.Success()
}

View File

@@ -0,0 +1,280 @@
package api
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"time"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeUser/internal/configs"
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
)
type DomainsAddAction struct {
actionutils.ParentAction
}
func (this *DomainsAddAction) RunPost(params struct{}) {
// 获取当前登录用户ID
userId := this.UserId()
if userId <= 0 {
this.writeJSON(http.StatusUnauthorized, "请先登录", nil)
return
}
// 手动读取JSON请求体
body, err := io.ReadAll(this.Request.Body)
if err != nil {
this.writeJSON(http.StatusBadRequest, "读取请求体失败: "+err.Error(), nil)
return
}
// 解析JSON参数
var requestParams struct {
ServerId int64 `json:"serverId"`
Domains []string `json:"domains"`
}
err = json.Unmarshal(body, &requestParams)
if err != nil {
this.writeJSON(http.StatusBadRequest, "解析请求参数失败: "+err.Error(), nil)
return
}
// 参数验证
if requestParams.ServerId <= 0 {
this.writeJSON(http.StatusBadRequest, "serverId不能为空", nil)
return
}
if len(requestParams.Domains) == 0 {
this.writeJSON(http.StatusBadRequest, "domains不能为空", nil)
return
}
// 注意:权限验证由 UpdateServerWebConfigForUser 内部完成,这里不需要单独调用 CheckUserServer
// 获取Access Token
accessToken, err := this.getAccessToken(userId)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "获取访问令牌失败: "+err.Error(), nil)
return
}
// 通过HTTP调用EdgeAPI的REST接口
apiConfig, err := configs.LoadAPIConfig()
if err != nil {
this.writeJSON(http.StatusInternalServerError, "无法获取API配置: "+err.Error(), nil)
return
}
if len(apiConfig.RPCEndpoints) == 0 {
this.writeJSON(http.StatusInternalServerError, "未配置RPC端点", nil)
return
}
// 使用第一个RPC端点转换为REST端点
rpcEndpoint := apiConfig.RPCEndpoints[0]
u, err := url.Parse(rpcEndpoint)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "无效的RPC端点: "+err.Error(), nil)
return
}
// REST接口路径/ServerService/UpdateServerWebConfigForUser
restURL := u.Scheme + "://" + u.Host + "/ServerService/UpdateServerWebConfigForUser"
// 构建请求体
requestBody := map[string]interface{}{
"serverId": requestParams.ServerId,
"domains": requestParams.Domains,
"autoCreateCert": true,
}
requestJSON, err := json.Marshal(requestBody)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "构建请求失败: "+err.Error(), nil)
return
}
// 创建HTTP请求
req, err := http.NewRequest("POST", restURL, bytes.NewReader(requestJSON))
if err != nil {
this.writeJSON(http.StatusInternalServerError, "创建请求失败: "+err.Error(), nil)
return
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Edge-Access-Token", accessToken)
// 发送请求
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "调用API失败: "+err.Error(), nil)
return
}
defer resp.Body.Close()
// 读取响应
respBody, err := io.ReadAll(resp.Body)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "读取响应失败: "+err.Error(), nil)
return
}
// 解析响应
var apiResp struct {
Code int `json:"code"`
Message string `json:"message"`
Data map[string]interface{} `json:"data"`
}
err = json.Unmarshal(respBody, &apiResp)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "解析响应失败: "+err.Error(), nil)
return
}
if apiResp.Code != 200 {
this.writeJSON(apiResp.Code, apiResp.Message, nil)
return
}
// 格式化返回数据
result := make(map[string]string)
if resultData, ok := apiResp.Data["result"].(map[string]interface{}); ok {
if domains, ok := resultData["domains"].(map[string]interface{}); ok {
for domain, statusObj := range domains {
if statusMap, ok := statusObj.(map[string]interface{}); ok {
if status, ok := statusMap["status"].(string); ok {
if status == "success" {
result[domain] = "success"
} else {
reason := ""
if r, ok := statusMap["reason"].(string); ok {
reason = r
}
result[domain] = "fail: " + reason
}
}
}
}
}
}
this.writeJSON(http.StatusOK, "ok", map[string]interface{}{
"result": result,
})
}
// writeJSON 写入JSON响应
func (this *DomainsAddAction) writeJSON(code int, message string, data interface{}) {
this.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
this.ResponseWriter.WriteHeader(code)
response := map[string]interface{}{
"code": code,
"message": message,
"data": data,
}
jsonData, err := json.Marshal(response)
if err != nil {
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
this.ResponseWriter.Write([]byte(`{"code":500,"message":"marshal json failed","data":null}`))
return
}
this.ResponseWriter.Write(jsonData)
}
// getAccessToken 获取Access Token
func (this *DomainsAddAction) getAccessToken(userId int64) (string, error) {
// 获取用户的AccessKey
accessKeysResp, err := this.RPC().UserAccessKeyRPC().FindAllEnabledUserAccessKeys(this.UserContext(), &pb.FindAllEnabledUserAccessKeysRequest{
UserId: userId,
})
if err != nil {
return "", err
}
if len(accessKeysResp.UserAccessKeys) == 0 {
return "", errors.New("用户未配置AccessKey请先在设置中创建AccessKey")
}
// 使用第一个AccessKey
accessKey := accessKeysResp.UserAccessKeys[0]
// 获取EdgeAPI地址
apiConfig, err := configs.LoadAPIConfig()
if err != nil {
return "", err
}
if len(apiConfig.RPCEndpoints) == 0 {
return "", errors.New("未配置RPC端点")
}
// 调用GetAPIAccessToken接口
rpcEndpoint := apiConfig.RPCEndpoints[0]
u, err := url.Parse(rpcEndpoint)
if err != nil {
return "", err
}
restURL := u.Scheme + "://" + u.Host + "/APIAccessTokenService/getAPIAccessToken"
// 构建请求体
requestBody := map[string]interface{}{
"type": "user",
"accessKeyId": accessKey.UniqueId,
"accessKey": accessKey.Secret,
}
requestJSON, err := json.Marshal(requestBody)
if err != nil {
return "", err
}
// 创建HTTP请求
req, err := http.NewRequest("POST", restURL, bytes.NewReader(requestJSON))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
// 发送请求
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
// 读取响应
tokenRespBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
// 解析响应
var tokenResp struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
Token string `json:"token"`
ExpiresAt int64 `json:"expiresAt"`
} `json:"data"`
}
err = json.Unmarshal(tokenRespBody, &tokenResp)
if err != nil {
return "", err
}
if tokenResp.Code != 200 {
return "", errors.New(tokenResp.Message)
}
return tokenResp.Data.Token, nil
}

View File

@@ -0,0 +1,278 @@
package api
import (
"bytes"
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeUser/internal/configs"
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
"io"
"net/http"
"net/url"
"time"
)
type DomainsDeleteAction struct {
actionutils.ParentAction
}
func (this *DomainsDeleteAction) RunPost(params struct{}) {
// 获取当前登录用户ID
userId := this.UserId()
if userId <= 0 {
this.writeJSON(http.StatusUnauthorized, "请先登录", nil)
return
}
// 手动读取JSON请求体
body, err := io.ReadAll(this.Request.Body)
if err != nil {
this.writeJSON(http.StatusBadRequest, "读取请求体失败: "+err.Error(), nil)
return
}
// 解析JSON参数
var requestParams struct {
ServerId int64 `json:"serverId"`
Domains []string `json:"domains"`
}
err = json.Unmarshal(body, &requestParams)
if err != nil {
this.writeJSON(http.StatusBadRequest, "解析请求参数失败: "+err.Error(), nil)
return
}
// 参数验证
if requestParams.ServerId <= 0 {
this.writeJSON(http.StatusBadRequest, "serverId不能为空", nil)
return
}
if len(requestParams.Domains) == 0 {
this.writeJSON(http.StatusBadRequest, "domains不能为空", nil)
return
}
// 注意:权限验证由 ResetServerWebConfigForUser 内部完成,这里不需要单独调用 CheckUserServer
// 获取Access Token
accessToken, err := this.getAccessToken(userId)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "获取访问令牌失败: "+err.Error(), nil)
return
}
// 通过HTTP调用EdgeAPI的REST接口
apiConfig, err := configs.LoadAPIConfig()
if err != nil {
this.writeJSON(http.StatusInternalServerError, "无法获取API配置: "+err.Error(), nil)
return
}
if len(apiConfig.RPCEndpoints) == 0 {
this.writeJSON(http.StatusInternalServerError, "未配置RPC端点", nil)
return
}
// 使用第一个RPC端点转换为REST端点
rpcEndpoint := apiConfig.RPCEndpoints[0]
u, err := url.Parse(rpcEndpoint)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "无效的RPC端点: "+err.Error(), nil)
return
}
// REST接口路径/ServerService/ResetServerWebConfigForUser
restURL := u.Scheme + "://" + u.Host + "/ServerService/ResetServerWebConfigForUser"
// 构建请求体
requestBody := map[string]interface{}{
"serverId": requestParams.ServerId,
"domains": requestParams.Domains,
}
requestJSON, err := json.Marshal(requestBody)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "构建请求失败: "+err.Error(), nil)
return
}
// 创建HTTP请求
req, err := http.NewRequest("POST", restURL, bytes.NewReader(requestJSON))
if err != nil {
this.writeJSON(http.StatusInternalServerError, "创建请求失败: "+err.Error(), nil)
return
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Edge-Access-Token", accessToken)
// 发送请求
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "调用API失败: "+err.Error(), nil)
return
}
defer resp.Body.Close()
// 读取响应
respBody, err := io.ReadAll(resp.Body)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "读取响应失败: "+err.Error(), nil)
return
}
// 解析响应
var apiResp struct {
Code int `json:"code"`
Message string `json:"message"`
Data map[string]interface{} `json:"data"`
}
err = json.Unmarshal(respBody, &apiResp)
if err != nil {
this.writeJSON(http.StatusInternalServerError, "解析响应失败: "+err.Error(), nil)
return
}
if apiResp.Code != 200 {
this.writeJSON(apiResp.Code, apiResp.Message, nil)
return
}
// 格式化返回数据
result := make(map[string]string)
if resultData, ok := apiResp.Data["result"].(map[string]interface{}); ok {
if domains, ok := resultData["domains"].(map[string]interface{}); ok {
for domain, statusObj := range domains {
if statusMap, ok := statusObj.(map[string]interface{}); ok {
if status, ok := statusMap["status"].(string); ok {
if status == "success" {
result[domain] = "success"
} else {
reason := ""
if r, ok := statusMap["reason"].(string); ok {
reason = r
}
result[domain] = "fail: " + reason
}
}
}
}
}
}
this.writeJSON(http.StatusOK, "ok", map[string]interface{}{
"result": result,
})
}
// writeJSON 写入JSON响应
func (this *DomainsDeleteAction) writeJSON(code int, message string, data interface{}) {
this.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
this.ResponseWriter.WriteHeader(code)
response := map[string]interface{}{
"code": code,
"message": message,
"data": data,
}
jsonData, err := json.Marshal(response)
if err != nil {
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
this.ResponseWriter.Write([]byte(`{"code":500,"message":"marshal json failed","data":null}`))
return
}
this.ResponseWriter.Write(jsonData)
}
// getAccessToken 获取Access Token
func (this *DomainsDeleteAction) getAccessToken(userId int64) (string, error) {
// 获取用户的AccessKey
accessKeysResp, err := this.RPC().UserAccessKeyRPC().FindAllEnabledUserAccessKeys(this.UserContext(), &pb.FindAllEnabledUserAccessKeysRequest{
UserId: userId,
})
if err != nil {
return "", err
}
if len(accessKeysResp.UserAccessKeys) == 0 {
return "", errors.New("用户未配置AccessKey请先在设置中创建AccessKey")
}
// 使用第一个AccessKey
accessKey := accessKeysResp.UserAccessKeys[0]
// 获取EdgeAPI地址
apiConfig, err := configs.LoadAPIConfig()
if err != nil {
return "", err
}
if len(apiConfig.RPCEndpoints) == 0 {
return "", errors.New("未配置RPC端点")
}
// 调用GetAPIAccessToken接口
rpcEndpoint := apiConfig.RPCEndpoints[0]
u, err := url.Parse(rpcEndpoint)
if err != nil {
return "", err
}
restURL := u.Scheme + "://" + u.Host + "/APIAccessTokenService/getAPIAccessToken"
// 构建请求体
requestBody := map[string]interface{}{
"type": "user",
"accessKeyId": accessKey.UniqueId,
"accessKey": accessKey.Secret,
}
requestJSON, err := json.Marshal(requestBody)
if err != nil {
return "", err
}
// 创建HTTP请求
req, err := http.NewRequest("POST", restURL, bytes.NewReader(requestJSON))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
// 发送请求
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
// 读取响应
tokenRespBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
// 解析响应
var tokenResp struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
Token string `json:"token"`
ExpiresAt int64 `json:"expiresAt"`
} `json:"data"`
}
err = json.Unmarshal(tokenRespBody, &tokenResp)
if err != nil {
return "", err
}
if tokenResp.Code != 200 {
return "", errors.New(tokenResp.Message)
}
return tokenResp.Data.Token, nil
}

View File

@@ -0,0 +1,74 @@
package api
import (
"encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
"net/http"
)
type ServersListAction struct {
actionutils.ParentAction
}
func (this *ServersListAction) RunPost(params struct {
Username string `json:"username"`
}) {
// 获取当前登录用户ID
userId := this.UserId()
if userId <= 0 {
this.writeJSON(http.StatusUnauthorized, "请先登录", nil)
return
}
// 如果提供了username验证是否为当前用户可选验证
if len(params.Username) > 0 {
// 可以通过RPC获取用户信息并验证这里简化处理
// 实际使用时如果提供了username应该验证是否为当前用户
}
// 调用RPC获取用户的所有网站
resp, err := this.RPC().ServerRPC().FindAllUserServers(this.UserContext(), &pb.FindAllUserServersRequest{
UserId: userId,
})
if err != nil {
this.ErrorPage(err)
return
}
// 格式化返回数据
var servers []map[string]interface{}
for _, server := range resp.Servers {
servers = append(servers, map[string]interface{}{
"id": server.Id,
"isOn": server.IsOn,
"name": server.Name,
"firstServerName": server.FirstServerName,
})
}
this.writeJSON(http.StatusOK, "ok", map[string]interface{}{
"servers": servers,
})
}
// writeJSON 写入JSON响应
func (this *ServersListAction) writeJSON(code int, message string, data interface{}) {
this.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
this.ResponseWriter.WriteHeader(code)
response := map[string]interface{}{
"code": code,
"message": message,
"data": data,
}
jsonData, err := json.Marshal(response)
if err != nil {
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
this.ResponseWriter.Write([]byte(`{"code":500,"message":"marshal json failed","data":null}`))
return
}
this.ResponseWriter.Write(jsonData)
}