mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
New interface management
Optimize the pop-up interface
This commit is contained in:
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,7 +1,8 @@
|
|||||||
|
/config.json
|
||||||
|
/database.db
|
||||||
|
/recharge.db
|
||||||
|
logs
|
||||||
|
模板
|
||||||
.DS_Store
|
.DS_Store
|
||||||
networkDev
|
networkDev
|
||||||
node.txt
|
node.txt
|
||||||
/config.json
|
|
||||||
database.db
|
|
||||||
logs
|
|
||||||
模板
|
|
||||||
418
controllers/admin/api.go
Normal file
418
controllers/admin/api.go
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"networkDev/database"
|
||||||
|
"networkDev/models"
|
||||||
|
"networkDev/utils"
|
||||||
|
"networkDev/utils/encrypt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIFragmentHandler 接口列表页面片段处理器
|
||||||
|
func APIFragmentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
utils.RenderTemplate(w, "apis.html", map[string]interface{}{
|
||||||
|
"Title": "接口管理",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIListHandler 接口列表API处理器
|
||||||
|
func APIListHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分页参数
|
||||||
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取应用UUID参数(用于按应用筛选接口)
|
||||||
|
appUUID := strings.TrimSpace(r.URL.Query().Get("app_uuid"))
|
||||||
|
|
||||||
|
// 获取搜索参数
|
||||||
|
search := strings.TrimSpace(r.URL.Query().Get("search"))
|
||||||
|
|
||||||
|
// 构建查询
|
||||||
|
db, err := database.GetDB()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to get database connection")
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建基础查询
|
||||||
|
query := db.Model(&models.API{})
|
||||||
|
|
||||||
|
// 如果指定了应用UUID,则按应用筛选
|
||||||
|
if appUUID != "" {
|
||||||
|
query = query.Where("app_uuid = ?", appUUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有搜索条件,添加搜索
|
||||||
|
if search != "" {
|
||||||
|
query = query.Where("api_key LIKE ? OR app_uuid LIKE ?", "%"+search+"%", "%"+search+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取总数
|
||||||
|
var total int64
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to count APIs")
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分页数据
|
||||||
|
var apis []models.API
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
if err := query.Offset(offset).Limit(limit).Order("created_at DESC").Find(&apis).Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to fetch APIs")
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取关联的应用信息
|
||||||
|
var appUUIDs []string
|
||||||
|
for _, api := range apis {
|
||||||
|
appUUIDs = append(appUUIDs, api.AppUUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var apps []models.App
|
||||||
|
if len(appUUIDs) > 0 {
|
||||||
|
if err := db.Where("uuid IN ?", appUUIDs).Find(&apps).Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to fetch related apps")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建应用UUID到应用名称的映射
|
||||||
|
appMap := make(map[string]string)
|
||||||
|
for _, app := range apps {
|
||||||
|
appMap[app.UUID] = app.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建响应数据
|
||||||
|
type APIResponse struct {
|
||||||
|
models.API
|
||||||
|
AppName string `json:"app_name"`
|
||||||
|
APITypeName string `json:"api_type_name"`
|
||||||
|
StatusName string `json:"status_name"`
|
||||||
|
AlgorithmNames struct {
|
||||||
|
Submit string `json:"submit"`
|
||||||
|
Return string `json:"return"`
|
||||||
|
} `json:"algorithm_names"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseAPIs []APIResponse
|
||||||
|
for _, api := range apis {
|
||||||
|
responseAPI := APIResponse{
|
||||||
|
API: api,
|
||||||
|
AppName: appMap[api.AppUUID],
|
||||||
|
APITypeName: models.GetAPITypeName(api.APIType),
|
||||||
|
StatusName: getAPIStatusName(api.Status),
|
||||||
|
}
|
||||||
|
responseAPI.AlgorithmNames.Submit = models.GetAlgorithmName(api.SubmitAlgorithm)
|
||||||
|
responseAPI.AlgorithmNames.Return = models.GetAlgorithmName(api.ReturnAlgorithm)
|
||||||
|
responseAPIs = append(responseAPIs, responseAPI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算分页信息
|
||||||
|
totalPages := (total + int64(limit) - 1) / int64(limit)
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"apis": responseAPIs,
|
||||||
|
"total": total,
|
||||||
|
"page": page,
|
||||||
|
"limit": limit,
|
||||||
|
"total_pages": totalPages,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAPIStatusName 获取API状态名称
|
||||||
|
func getAPIStatusName(status int) string {
|
||||||
|
switch status {
|
||||||
|
case 1:
|
||||||
|
return "启用"
|
||||||
|
case 0:
|
||||||
|
return "禁用"
|
||||||
|
default:
|
||||||
|
return "未知"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIUpdateHandler 更新接口处理器
|
||||||
|
func APIUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
SubmitAlgorithm int `json:"submit_algorithm"`
|
||||||
|
ReturnAlgorithm int `json:"return_algorithm"`
|
||||||
|
SubmitPublicKey string `json:"submit_public_key"`
|
||||||
|
SubmitPrivateKey string `json:"submit_private_key"`
|
||||||
|
ReturnPublicKey string `json:"return_public_key"`
|
||||||
|
ReturnPrivateKey string `json:"return_private_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证必填字段
|
||||||
|
if req.ID == 0 {
|
||||||
|
http.Error(w, "接口ID不能为空", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Status != 0 && req.Status != 1 {
|
||||||
|
http.Error(w, "无效的状态值", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !models.IsValidAlgorithm(req.SubmitAlgorithm) || !models.IsValidAlgorithm(req.ReturnAlgorithm) {
|
||||||
|
http.Error(w, "无效的算法类型", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数据库连接
|
||||||
|
db, err := database.GetDB()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to get database connection")
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找并更新API记录
|
||||||
|
var api models.API
|
||||||
|
if err := db.First(&api, req.ID).Error; err != nil {
|
||||||
|
http.Error(w, "接口不存在", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新字段(不允许修改 APIType)
|
||||||
|
api.Status = req.Status
|
||||||
|
api.SubmitAlgorithm = req.SubmitAlgorithm
|
||||||
|
api.ReturnAlgorithm = req.ReturnAlgorithm
|
||||||
|
|
||||||
|
// 可选更新密钥/证书(当提供时)
|
||||||
|
if req.SubmitPublicKey != "" || req.SubmitPrivateKey != "" {
|
||||||
|
api.SubmitPublicKey = req.SubmitPublicKey
|
||||||
|
api.SubmitPrivateKey = req.SubmitPrivateKey
|
||||||
|
}
|
||||||
|
if req.ReturnPublicKey != "" || req.ReturnPrivateKey != "" {
|
||||||
|
api.ReturnPublicKey = req.ReturnPublicKey
|
||||||
|
api.ReturnPrivateKey = req.ReturnPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Save(&api).Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to update API")
|
||||||
|
http.Error(w, "更新接口失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": "接口更新成功",
|
||||||
|
"data": api,
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIGetAppsHandler 获取应用列表(用于接口页面的应用选择器)
|
||||||
|
func APIGetAppsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数据库连接
|
||||||
|
db, err := database.GetDB()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to get database connection")
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有应用
|
||||||
|
var apps []models.App
|
||||||
|
if err := db.Select("uuid, name").Order("created_at ASC").Find(&apps).Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to fetch apps")
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"data": apps,
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
Side string `json:"side"` // submit | return
|
||||||
|
Algorithm int `json:"algorithm"` // 与 models.Algorithm* 对应
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.Side != "submit" && req.Side != "return" {
|
||||||
|
http.Error(w, "side参数必须为submit或return", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !models.IsValidAlgorithm(req.Algorithm) {
|
||||||
|
http.Error(w, "无效的算法类型", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据算法生成密钥/证书
|
||||||
|
result := map[string]interface{}{}
|
||||||
|
|
||||||
|
switch req.Algorithm {
|
||||||
|
case models.AlgorithmNone:
|
||||||
|
// 不加密不生成任何密钥
|
||||||
|
result["public_key"] = ""
|
||||||
|
result["private_key"] = ""
|
||||||
|
case models.AlgorithmRC4:
|
||||||
|
// 生成16字节随机密钥并返回16位十六进制(大写)
|
||||||
|
bytes := make([]byte, 8)
|
||||||
|
if _, err := rand.Read(bytes); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to generate RC4 key")
|
||||||
|
http.Error(w, "生成RC4密钥失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result["public_key"] = ""
|
||||||
|
result["private_key"] = strings.ToUpper(hex.EncodeToString(bytes))
|
||||||
|
case models.AlgorithmRSA, models.AlgorithmRSADynamic:
|
||||||
|
// 生成RSA 2048密钥对,返回PEM明文字符串
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to generate RSA key pair")
|
||||||
|
http.Error(w, "生成RSA密钥失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
privBytes := x509.MarshalPKCS1PrivateKey(key)
|
||||||
|
pubBytes := x509.MarshalPKCS1PublicKey(&key.PublicKey)
|
||||||
|
privPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privBytes})
|
||||||
|
pubPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PUBLIC KEY", Bytes: pubBytes})
|
||||||
|
result["private_key"] = string(privPEM)
|
||||||
|
result["public_key"] = string(pubPEM)
|
||||||
|
case models.AlgorithmEasy:
|
||||||
|
// 生成易加密密钥对,返回逗号分隔的整数数组字符串
|
||||||
|
encryptKey, _, err := encrypt.GenerateEasyKey()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to generate Easy encryption key")
|
||||||
|
http.Error(w, "生成易加密密钥失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result["public_key"] = ""
|
||||||
|
result["private_key"] = encrypt.FormatKeyAsString(encryptKey)
|
||||||
|
default:
|
||||||
|
http.Error(w, "不支持的算法类型", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": "生成成功",
|
||||||
|
"data": result,
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func APIResetKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ID == 0 {
|
||||||
|
http.Error(w, "接口ID不能为空", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := database.GetDB()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to get database connection")
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var api models.API
|
||||||
|
if err := db.First(&api, req.ID).Error; err != nil {
|
||||||
|
http.Error(w, "接口不存在", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新的16位大写十六进制密钥
|
||||||
|
bytes := make([]byte, 8)
|
||||||
|
if _, err := rand.Read(bytes); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to generate random API key")
|
||||||
|
http.Error(w, "生成密钥失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newKey := strings.ToUpper(hex.EncodeToString(bytes))
|
||||||
|
|
||||||
|
if err := db.Model(&api).Update("api_key", newKey).Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to update API key")
|
||||||
|
http.Error(w, "更新密钥失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": "接口密钥重置成功",
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"id": api.ID,
|
||||||
|
"api_key": newKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
@@ -88,6 +88,76 @@ func AppsListHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppGetAppDataHandler 获取应用数据处理器
|
||||||
|
func AppGetAppDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取UUID参数
|
||||||
|
uuid := r.URL.Query().Get("uuid")
|
||||||
|
if uuid == "" {
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "应用UUID不能为空",
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数据库连接
|
||||||
|
db, err := database.GetDB()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to get database connection")
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "数据库连接失败",
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找应用
|
||||||
|
var app models.App
|
||||||
|
if err := db.Where("uuid = ?", uuid).First(&app).Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to find app")
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "应用不存在",
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码base64应用数据内容
|
||||||
|
var appData string
|
||||||
|
if app.AppData != "" {
|
||||||
|
decodedBytes, err := base64.StdEncoding.DecodeString(app.AppData)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to decode app data")
|
||||||
|
// 如果解码失败,返回空字符串
|
||||||
|
appData = ""
|
||||||
|
} else {
|
||||||
|
appData = string(decodedBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"code": 0,
|
||||||
|
"msg": "获取成功",
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"app_data": appData,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
// AppGetAnnouncementHandler 获取应用程序公告处理器
|
// AppGetAnnouncementHandler 获取应用程序公告处理器
|
||||||
func AppGetAnnouncementHandler(w http.ResponseWriter, r *http.Request) {
|
func AppGetAnnouncementHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
@@ -294,12 +364,73 @@ func AppCreateHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.Create(&app).Error; err != nil {
|
// 开始事务
|
||||||
|
tx := db.Begin()
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 创建应用
|
||||||
|
if err := tx.Create(&app).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
logrus.WithError(err).Error("Failed to create app")
|
logrus.WithError(err).Error("Failed to create app")
|
||||||
http.Error(w, "创建应用失败", http.StatusInternalServerError)
|
http.Error(w, "创建应用失败", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 为应用创建所有默认接口
|
||||||
|
defaultAPITypes := []int{
|
||||||
|
models.APITypeGetBulletin, // 获取程序公告
|
||||||
|
models.APITypeGetUpdateUrl, // 获取更新地址
|
||||||
|
models.APITypeCheckAppVersion, // 检测最新版本
|
||||||
|
models.APITypeGetCardInfo, // 获取卡密信息
|
||||||
|
models.APITypeSingleLogin, // 卡密登录
|
||||||
|
models.APITypeUserLogin, // 用户登录
|
||||||
|
models.APITypeUserRegin, // 用户注册
|
||||||
|
models.APITypeUserRecharge, // 用户充值
|
||||||
|
models.APITypeCardRegin, // 卡密注册
|
||||||
|
models.APITypeLogOut, // 退出登录
|
||||||
|
models.APITypeGetExpired, // 获取到期时间
|
||||||
|
models.APITypeCheckUserStatus, // 检测账号状态
|
||||||
|
models.APITypeGetAppData, // 获取程序数据
|
||||||
|
models.APITypeGetVariable, // 获取变量数据
|
||||||
|
models.APITypeUpdatePwd, // 修改账号密码
|
||||||
|
models.APITypeMacChangeBind, // 机器码转绑
|
||||||
|
models.APITypeIPChangeBind, // IP转绑
|
||||||
|
models.APITypeDisableUser, // 封停用户
|
||||||
|
models.APITypeBlackUser, // 添加黑名单
|
||||||
|
models.APITypeUserDeductedTime, // 扣除时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量创建默认接口
|
||||||
|
for _, apiType := range defaultAPITypes {
|
||||||
|
api := models.API{
|
||||||
|
APIType: apiType,
|
||||||
|
AppUUID: app.UUID,
|
||||||
|
Status: 1, // 默认启用
|
||||||
|
SubmitAlgorithm: models.AlgorithmNone, // 默认不加密
|
||||||
|
ReturnAlgorithm: models.AlgorithmNone, // 默认不加密
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Create(&api).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
logrus.WithError(err).WithField("api_type", apiType).Error("Failed to create default API")
|
||||||
|
http.Error(w, "创建默认接口失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
if err := tx.Commit().Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to commit transaction")
|
||||||
|
http.Error(w, "提交事务失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithField("app_uuid", app.UUID).Info("Successfully created app with default APIs")
|
||||||
|
|
||||||
response := map[string]interface{}{
|
response := map[string]interface{}{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "创建成功",
|
"msg": "创建成功",
|
||||||
@@ -408,13 +539,51 @@ func AppDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 开始事务
|
||||||
|
tx := db.Begin()
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 首先获取应用信息以获取UUID
|
||||||
|
var app models.App
|
||||||
|
if err := tx.First(&app, req.ID).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
logrus.WithError(err).Error("Failed to find app")
|
||||||
|
http.Error(w, "应用不存在", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除该应用的所有相关接口
|
||||||
|
if err := tx.Where("app_uuid = ?", app.UUID).Delete(&models.API{}).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
logrus.WithError(err).Error("Failed to delete related APIs")
|
||||||
|
http.Error(w, "删除相关接口失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 删除应用
|
// 删除应用
|
||||||
if err := db.Delete(&models.App{}, req.ID).Error; err != nil {
|
if err := tx.Delete(&models.App{}, req.ID).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
logrus.WithError(err).Error("Failed to delete app")
|
logrus.WithError(err).Error("Failed to delete app")
|
||||||
http.Error(w, "删除应用失败", http.StatusInternalServerError)
|
http.Error(w, "删除应用失败", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
if err := tx.Commit().Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to commit transaction")
|
||||||
|
http.Error(w, "提交事务失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"app_id": req.ID,
|
||||||
|
"app_uuid": app.UUID,
|
||||||
|
}).Info("Successfully deleted app and related APIs")
|
||||||
|
|
||||||
response := map[string]interface{}{
|
response := map[string]interface{}{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "删除成功",
|
"msg": "删除成功",
|
||||||
@@ -452,13 +621,59 @@ func AppsBatchDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量删除
|
// 开始事务
|
||||||
if err := db.Delete(&models.App{}, req.IDs).Error; err != nil {
|
tx := db.Begin()
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 首先获取要删除的应用的UUID列表
|
||||||
|
var apps []models.App
|
||||||
|
if err := tx.Where("id IN ?", req.IDs).Find(&apps).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
logrus.WithError(err).Error("Failed to find apps")
|
||||||
|
http.Error(w, "查找应用失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取UUID列表
|
||||||
|
var appUUIDs []string
|
||||||
|
for _, app := range apps {
|
||||||
|
appUUIDs = append(appUUIDs, app.UUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除这些应用的所有相关接口
|
||||||
|
if len(appUUIDs) > 0 {
|
||||||
|
if err := tx.Where("app_uuid IN ?", appUUIDs).Delete(&models.API{}).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
logrus.WithError(err).Error("Failed to delete related APIs")
|
||||||
|
http.Error(w, "删除相关接口失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除应用
|
||||||
|
if err := tx.Delete(&models.App{}, req.IDs).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
logrus.WithError(err).Error("Failed to batch delete apps")
|
logrus.WithError(err).Error("Failed to batch delete apps")
|
||||||
http.Error(w, "批量删除失败", http.StatusInternalServerError)
|
http.Error(w, "批量删除失败", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
if err := tx.Commit().Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to commit transaction")
|
||||||
|
http.Error(w, "提交事务失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"app_ids": req.IDs,
|
||||||
|
"app_uuids": appUUIDs,
|
||||||
|
}).Info("Successfully batch deleted apps and related APIs")
|
||||||
|
|
||||||
response := map[string]interface{}{
|
response := map[string]interface{}{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "批量删除成功",
|
"msg": "批量删除成功",
|
||||||
@@ -523,6 +738,108 @@ func AppsBatchUpdateStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppUpdateAppDataHandler 更新应用数据处理器
|
||||||
|
func AppUpdateAppDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析请求体
|
||||||
|
var req struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
AppData string `json:"app_data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to decode request body")
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "请求参数格式错误",
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证UUID
|
||||||
|
if req.UUID == "" {
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "应用UUID不能为空",
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证UUID格式
|
||||||
|
if _, err := uuid.Parse(req.UUID); err != nil {
|
||||||
|
logrus.WithError(err).Error("Invalid UUID format")
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "无效的UUID格式",
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数据库连接
|
||||||
|
db, err := database.GetDB()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to get database connection")
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "数据库连接失败",
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找应用
|
||||||
|
var app models.App
|
||||||
|
if err := db.Where("uuid = ?", req.UUID).First(&app).Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to find app")
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "应用不存在",
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对应用数据内容进行base64编码
|
||||||
|
encodedAppData := base64.StdEncoding.EncodeToString([]byte(req.AppData))
|
||||||
|
|
||||||
|
// 更新应用的数据内容
|
||||||
|
if err := db.Model(&app).Update("app_data", encodedAppData).Error; err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to update app data")
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "更新应用数据失败",
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"app_uuid": req.UUID,
|
||||||
|
"app_name": app.Name,
|
||||||
|
}).Info("App data updated successfully")
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"code": 0,
|
||||||
|
"msg": "应用数据更新成功",
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
// AppUpdateAnnouncementHandler 更新应用程序公告处理器
|
// AppUpdateAnnouncementHandler 更新应用程序公告处理器
|
||||||
func AppUpdateAnnouncementHandler(w http.ResponseWriter, r *http.Request) {
|
func AppUpdateAnnouncementHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
|
|||||||
170
models/api.go
170
models/api.go
@@ -1,10 +1,11 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,24 +31,24 @@ type API struct {
|
|||||||
Status int `gorm:"default:1;not null;comment:接口状态,1=启用,0=禁用" json:"status"`
|
Status int `gorm:"default:1;not null;comment:接口状态,1=启用,0=禁用" json:"status"`
|
||||||
|
|
||||||
// 接口提交算法
|
// 接口提交算法
|
||||||
// 支持的算法:0=不加密,1=RC4,2=RSA,3=RSA(动态)
|
// 支持的算法:0=不加密,1=RC4,2=RSA,3=RSA(动态),4=易加密
|
||||||
SubmitAlgorithm int `gorm:"default:0;not null;comment:提交算法,0=不加密,1=RC4,2=RSA,3=RSA动态" json:"submit_algorithm"`
|
SubmitAlgorithm int `gorm:"default:0;not null;comment:提交算法,0=不加密,1=RC4,2=RSA,3=RSA动态,4=易加密" json:"submit_algorithm"`
|
||||||
|
|
||||||
// 接口返回算法
|
// 接口返回算法
|
||||||
// 支持的算法:0=不加密,1=RC4,2=RSA,3=RSA(动态)
|
// 支持的算法:0=不加密,1=RC4,2=RSA,3=RSA(动态),4=易加密
|
||||||
ReturnAlgorithm int `gorm:"default:0;not null;comment:返回算法,0=不加密,1=RC4,2=RSA,3=RSA动态" json:"return_algorithm"`
|
ReturnAlgorithm int `gorm:"default:0;not null;comment:返回算法,0=不加密,1=RC4,2=RSA,3=RSA动态,4=易加密" json:"return_algorithm"`
|
||||||
|
|
||||||
// 提交算法公钥(base64编码存储)
|
// 提交算法公钥(明文PEM存储)
|
||||||
SubmitPublicKey string `gorm:"type:text;comment:提交算法公钥,base64编码" json:"submit_public_key"`
|
SubmitPublicKey string `gorm:"type:text;comment:提交算法公钥,明文PEM" json:"submit_public_key"`
|
||||||
|
|
||||||
// 提交算法私钥(base64编码存储)
|
// 提交算法私钥(明文PEM存储)
|
||||||
SubmitPrivateKey string `gorm:"type:text;comment:提交算法私钥,base64编码" json:"submit_private_key"`
|
SubmitPrivateKey string `gorm:"type:text;comment:提交算法私钥,明文PEM" json:"submit_private_key"`
|
||||||
|
|
||||||
// 返回算法公钥(base64编码存储)
|
// 返回算法公钥(明文PEM存储)
|
||||||
ReturnPublicKey string `gorm:"type:text;comment:返回算法公钥,base64编码" json:"return_public_key"`
|
ReturnPublicKey string `gorm:"type:text;comment:返回算法公钥,明文PEM" json:"return_public_key"`
|
||||||
|
|
||||||
// 返回算法私钥(base64编码存储)
|
// 返回算法私钥(明文PEM存储)
|
||||||
ReturnPrivateKey string `gorm:"type:text;comment:返回算法私钥,base64编码" json:"return_private_key"`
|
ReturnPrivateKey string `gorm:"type:text;comment:返回算法私钥,明文PEM" json:"return_private_key"`
|
||||||
|
|
||||||
// 时间字段
|
// 时间字段
|
||||||
CreatedAt time.Time `gorm:"comment:创建时间" json:"created_at"`
|
CreatedAt time.Time `gorm:"comment:创建时间" json:"created_at"`
|
||||||
@@ -57,8 +58,10 @@ type API struct {
|
|||||||
// BeforeCreate 在创建记录前自动生成API密钥
|
// BeforeCreate 在创建记录前自动生成API密钥
|
||||||
func (api *API) BeforeCreate(tx *gorm.DB) error {
|
func (api *API) BeforeCreate(tx *gorm.DB) error {
|
||||||
if api.APIKey == "" {
|
if api.APIKey == "" {
|
||||||
// 生成唯一的API密钥
|
// 生成16位大写十六进制API密钥
|
||||||
api.APIKey = "api_" + strings.ToUpper(uuid.New().String())
|
bytes := make([]byte, 8) // 8字节 = 16位十六进制字符
|
||||||
|
rand.Read(bytes)
|
||||||
|
api.APIKey = strings.ToUpper(hex.EncodeToString(bytes))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -68,12 +71,50 @@ func (API) TableName() string {
|
|||||||
return "apis"
|
return "apis"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API类型常量定义
|
||||||
|
const (
|
||||||
|
// 基础信息获取类API
|
||||||
|
APITypeGetBulletin = 1 // 获取程序公告
|
||||||
|
APITypeGetUpdateUrl = 2 // 获取更新地址
|
||||||
|
APITypeCheckAppVersion = 3 // 检测最新版本
|
||||||
|
APITypeGetCardInfo = 4 // 获取卡密信息
|
||||||
|
|
||||||
|
// 登录相关API
|
||||||
|
APITypeSingleLogin = 10 // 卡密登录
|
||||||
|
|
||||||
|
// 用户账号管理API
|
||||||
|
APITypeUserLogin = 20 // 用户登录
|
||||||
|
APITypeUserRegin = 21 // 用户注册
|
||||||
|
APITypeUserRecharge = 22 // 用户充值
|
||||||
|
APITypeCardRegin = 23 // 卡密注册
|
||||||
|
|
||||||
|
// 登出API
|
||||||
|
APITypeLogOut = 30 // 退出登录
|
||||||
|
|
||||||
|
// 用户状态查询API
|
||||||
|
APITypeGetExpired = 40 // 获取到期时间
|
||||||
|
APITypeCheckUserStatus = 41 // 检测账号状态
|
||||||
|
APITypeGetAppData = 42 // 获取程序数据
|
||||||
|
APITypeGetVariable = 43 // 获取变量数据
|
||||||
|
|
||||||
|
// 用户操作API
|
||||||
|
APITypeUpdatePwd = 50 // 修改账号密码
|
||||||
|
APITypeMacChangeBind = 51 // 机器码转绑
|
||||||
|
APITypeIPChangeBind = 52 // IP转绑
|
||||||
|
|
||||||
|
// 管理员操作API
|
||||||
|
APITypeDisableUser = 60 // 封停用户
|
||||||
|
APITypeBlackUser = 61 // 添加黑名单
|
||||||
|
APITypeUserDeductedTime = 62 // 扣除时间
|
||||||
|
)
|
||||||
|
|
||||||
// 算法类型常量
|
// 算法类型常量
|
||||||
const (
|
const (
|
||||||
AlgorithmNone = 0 // 不加密
|
AlgorithmNone = 0 // 不加密
|
||||||
AlgorithmRC4 = 1 // RC4
|
AlgorithmRC4 = 1 // RC4
|
||||||
AlgorithmRSA = 2 // RSA
|
AlgorithmRSA = 2 // RSA
|
||||||
AlgorithmRSADynamic = 3 // RSA(动态)
|
AlgorithmRSADynamic = 3 // RSA(动态)
|
||||||
|
AlgorithmEasy = 4 // 易加密
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetAlgorithmName 获取算法名称
|
// GetAlgorithmName 获取算法名称
|
||||||
@@ -87,6 +128,8 @@ func GetAlgorithmName(algorithm int) string {
|
|||||||
return "RSA"
|
return "RSA"
|
||||||
case AlgorithmRSADynamic:
|
case AlgorithmRSADynamic:
|
||||||
return "RSA(动态)"
|
return "RSA(动态)"
|
||||||
|
case AlgorithmEasy:
|
||||||
|
return "易加密"
|
||||||
default:
|
default:
|
||||||
return "未知算法"
|
return "未知算法"
|
||||||
}
|
}
|
||||||
@@ -94,5 +137,100 @@ func GetAlgorithmName(algorithm int) string {
|
|||||||
|
|
||||||
// IsValidAlgorithm 验证算法类型是否有效
|
// IsValidAlgorithm 验证算法类型是否有效
|
||||||
func IsValidAlgorithm(algorithm int) bool {
|
func IsValidAlgorithm(algorithm int) bool {
|
||||||
return algorithm >= AlgorithmNone && algorithm <= AlgorithmRSADynamic
|
return algorithm >= AlgorithmNone && algorithm <= AlgorithmEasy
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAPITypeName 获取API类型名称
|
||||||
|
func GetAPITypeName(apiType int) string {
|
||||||
|
switch apiType {
|
||||||
|
// 基础信息获取类API
|
||||||
|
case APITypeGetBulletin:
|
||||||
|
return "获取程序公告"
|
||||||
|
case APITypeGetUpdateUrl:
|
||||||
|
return "获取更新地址"
|
||||||
|
case APITypeCheckAppVersion:
|
||||||
|
return "检测最新版本"
|
||||||
|
case APITypeGetCardInfo:
|
||||||
|
return "获取卡密信息"
|
||||||
|
|
||||||
|
// 登录相关API
|
||||||
|
case APITypeSingleLogin:
|
||||||
|
return "卡密登录"
|
||||||
|
|
||||||
|
// 用户账号管理API
|
||||||
|
case APITypeUserLogin:
|
||||||
|
return "用户登录"
|
||||||
|
case APITypeUserRegin:
|
||||||
|
return "用户注册"
|
||||||
|
case APITypeUserRecharge:
|
||||||
|
return "用户充值"
|
||||||
|
case APITypeCardRegin:
|
||||||
|
return "卡密注册"
|
||||||
|
|
||||||
|
// 登出API
|
||||||
|
case APITypeLogOut:
|
||||||
|
return "退出登录"
|
||||||
|
|
||||||
|
// 用户状态查询API
|
||||||
|
case APITypeGetExpired:
|
||||||
|
return "获取到期时间"
|
||||||
|
case APITypeCheckUserStatus:
|
||||||
|
return "检测账号状态"
|
||||||
|
case APITypeGetAppData:
|
||||||
|
return "获取程序数据"
|
||||||
|
case APITypeGetVariable:
|
||||||
|
return "获取变量数据"
|
||||||
|
|
||||||
|
// 用户操作API
|
||||||
|
case APITypeUpdatePwd:
|
||||||
|
return "修改账号密码"
|
||||||
|
case APITypeMacChangeBind:
|
||||||
|
return "机器码转绑"
|
||||||
|
case APITypeIPChangeBind:
|
||||||
|
return "IP转绑"
|
||||||
|
|
||||||
|
// 管理员操作API
|
||||||
|
case APITypeDisableUser:
|
||||||
|
return "封停用户"
|
||||||
|
case APITypeBlackUser:
|
||||||
|
return "添加黑名单"
|
||||||
|
case APITypeUserDeductedTime:
|
||||||
|
return "扣除时间"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "未知API类型"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidAPIType 验证API类型是否有效
|
||||||
|
func IsValidAPIType(apiType int) bool {
|
||||||
|
validTypes := []int{
|
||||||
|
APITypeGetBulletin, APITypeGetUpdateUrl, APITypeCheckAppVersion, APITypeGetCardInfo,
|
||||||
|
APITypeSingleLogin,
|
||||||
|
APITypeUserLogin, APITypeUserRegin, APITypeUserRecharge, APITypeCardRegin,
|
||||||
|
APITypeLogOut,
|
||||||
|
APITypeGetExpired, APITypeCheckUserStatus, APITypeGetAppData, APITypeGetVariable,
|
||||||
|
APITypeUpdatePwd, APITypeMacChangeBind, APITypeIPChangeBind,
|
||||||
|
APITypeDisableUser, APITypeBlackUser, APITypeUserDeductedTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, validType := range validTypes {
|
||||||
|
if apiType == validType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAPITypesByCategory 根据分类获取API类型列表
|
||||||
|
func GetAPITypesByCategory() map[string][]int {
|
||||||
|
return map[string][]int{
|
||||||
|
"基础信息获取": {APITypeGetBulletin, APITypeGetUpdateUrl, APITypeCheckAppVersion, APITypeGetCardInfo},
|
||||||
|
"登录相关": {APITypeSingleLogin},
|
||||||
|
"用户账号管理": {APITypeUserLogin, APITypeUserRegin, APITypeUserRecharge, APITypeCardRegin},
|
||||||
|
"登出": {APITypeLogOut},
|
||||||
|
"用户状态查询": {APITypeGetExpired, APITypeCheckUserStatus, APITypeGetAppData, APITypeGetVariable},
|
||||||
|
"用户操作": {APITypeUpdatePwd, APITypeMacChangeBind, APITypeIPChangeBind},
|
||||||
|
"管理员操作": {APITypeDisableUser, APITypeBlackUser, APITypeUserDeductedTime},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ type App struct {
|
|||||||
// DownloadURL:下载地址
|
// DownloadURL:下载地址
|
||||||
DownloadURL string `gorm:"size:500;comment:下载地址" json:"download_url"`
|
DownloadURL string `gorm:"size:500;comment:下载地址" json:"download_url"`
|
||||||
|
|
||||||
|
// AppData:应用数据(base64编码存储)
|
||||||
|
AppData string `gorm:"type:text;comment:应用数据,base64编码存储" json:"app_data"`
|
||||||
|
|
||||||
// Announcement:程序公告内容(base64编码存储)
|
// Announcement:程序公告内容(base64编码存储)
|
||||||
Announcement string `gorm:"type:text;comment:程序公告内容,base64编码存储" json:"announcement"`
|
Announcement string `gorm:"type:text;comment:程序公告内容,base64编码存储" json:"announcement"`
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func RegisterAdminRoutes(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("/admin/user", adminctl.AdminAuthRequired(adminctl.UserFragmentHandler))
|
mux.HandleFunc("/admin/user", adminctl.AdminAuthRequired(adminctl.UserFragmentHandler))
|
||||||
mux.HandleFunc("/admin/settings", adminctl.AdminAuthRequired(adminctl.SettingsFragmentHandler))
|
mux.HandleFunc("/admin/settings", adminctl.AdminAuthRequired(adminctl.SettingsFragmentHandler))
|
||||||
mux.HandleFunc("/admin/apps", adminctl.AdminAuthRequired(adminctl.AppsFragmentHandler))
|
mux.HandleFunc("/admin/apps", adminctl.AdminAuthRequired(adminctl.AppsFragmentHandler))
|
||||||
|
mux.HandleFunc("/admin/apis", adminctl.AdminAuthRequired(adminctl.APIFragmentHandler))
|
||||||
|
|
||||||
// 个人资料API
|
// 个人资料API
|
||||||
mux.HandleFunc("/admin/api/user/profile", adminctl.AdminAuthRequired(adminctl.UserProfileQueryHandler))
|
mux.HandleFunc("/admin/api/user/profile", adminctl.AdminAuthRequired(adminctl.UserProfileQueryHandler))
|
||||||
@@ -61,6 +61,8 @@ func RegisterAdminRoutes(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("/admin/api/apps/batch_delete", adminctl.AdminAuthRequired(adminctl.AppsBatchDeleteHandler))
|
mux.HandleFunc("/admin/api/apps/batch_delete", adminctl.AdminAuthRequired(adminctl.AppsBatchDeleteHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/batch_update_status", adminctl.AdminAuthRequired(adminctl.AppsBatchUpdateStatusHandler))
|
mux.HandleFunc("/admin/api/apps/batch_update_status", adminctl.AdminAuthRequired(adminctl.AppsBatchUpdateStatusHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/reset_secret", adminctl.AdminAuthRequired(adminctl.AppResetSecretHandler))
|
mux.HandleFunc("/admin/api/apps/reset_secret", adminctl.AdminAuthRequired(adminctl.AppResetSecretHandler))
|
||||||
|
mux.HandleFunc("/admin/api/apps/get_app_data", adminctl.AdminAuthRequired(adminctl.AppGetAppDataHandler))
|
||||||
|
mux.HandleFunc("/admin/api/apps/update_app_data", adminctl.AdminAuthRequired(adminctl.AppUpdateAppDataHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/get_announcement", adminctl.AdminAuthRequired(adminctl.AppGetAnnouncementHandler))
|
mux.HandleFunc("/admin/api/apps/get_announcement", adminctl.AdminAuthRequired(adminctl.AppGetAnnouncementHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/update_announcement", adminctl.AdminAuthRequired(adminctl.AppUpdateAnnouncementHandler))
|
mux.HandleFunc("/admin/api/apps/update_announcement", adminctl.AdminAuthRequired(adminctl.AppUpdateAnnouncementHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/get_multi_config", adminctl.AdminAuthRequired(adminctl.AppGetMultiConfigHandler))
|
mux.HandleFunc("/admin/api/apps/get_multi_config", adminctl.AdminAuthRequired(adminctl.AppGetMultiConfigHandler))
|
||||||
@@ -70,6 +72,12 @@ func RegisterAdminRoutes(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("/admin/api/apps/get_register_config", adminctl.AdminAuthRequired(adminctl.AppGetRegisterConfigHandler))
|
mux.HandleFunc("/admin/api/apps/get_register_config", adminctl.AdminAuthRequired(adminctl.AppGetRegisterConfigHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/update_register_config", adminctl.AdminAuthRequired(adminctl.AppUpdateRegisterConfigHandler))
|
mux.HandleFunc("/admin/api/apps/update_register_config", adminctl.AdminAuthRequired(adminctl.AppUpdateRegisterConfigHandler))
|
||||||
|
|
||||||
|
// API接口管理API
|
||||||
|
mux.HandleFunc("/admin/api/apis/list", adminctl.AdminAuthRequired(adminctl.APIListHandler))
|
||||||
|
mux.HandleFunc("/admin/api/apis/update", adminctl.AdminAuthRequired(adminctl.APIUpdateHandler))
|
||||||
|
mux.HandleFunc("/admin/api/apis/apps", adminctl.AdminAuthRequired(adminctl.APIGetAppsHandler))
|
||||||
|
mux.HandleFunc("/admin/api/apis/reset_key", adminctl.AdminAuthRequired(adminctl.APIResetKeyHandler))
|
||||||
|
mux.HandleFunc("/admin/api/apis/generate_keys", adminctl.AdminAuthRequired(adminctl.APIGenerateKeysHandler))
|
||||||
|
|
||||||
// 系统信息API(用于仪表盘定时刷新)
|
// 系统信息API(用于仪表盘定时刷新)
|
||||||
mux.HandleFunc("/admin/api/system/info", adminctl.AdminAuthRequired(adminctl.SystemInfoHandler))
|
mux.HandleFunc("/admin/api/system/info", adminctl.AdminAuthRequired(adminctl.SystemInfoHandler))
|
||||||
|
|||||||
241
utils/encrypt/easy.go
Normal file
241
utils/encrypt/easy.go
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
package encrypt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EasyEncrypt 易加密算法结构体
|
||||||
|
type EasyEncrypt struct {
|
||||||
|
encryptKey []int // 加密密钥
|
||||||
|
decryptKey []int // 解密密钥
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEasyEncrypt 创建新的易加密实例
|
||||||
|
func NewEasyEncrypt(encryptKey, decryptKey []int) *EasyEncrypt {
|
||||||
|
return &EasyEncrypt{
|
||||||
|
encryptKey: encryptKey,
|
||||||
|
decryptKey: decryptKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateEasyKey 生成易加密密钥对
|
||||||
|
func GenerateEasyKey() ([]int, []int, error) {
|
||||||
|
// 使用crypto/rand生成随机长度(15-30位)
|
||||||
|
var lengthByte [1]byte
|
||||||
|
|
||||||
|
// 生成加密密钥长度
|
||||||
|
if _, err := rand.Read(lengthByte[:]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
encryptKeyLen := 15 + int(lengthByte[0])%16 // 15-30位随机长度
|
||||||
|
|
||||||
|
encryptKey := make([]int, encryptKeyLen)
|
||||||
|
encryptBytes := make([]byte, encryptKeyLen)
|
||||||
|
if _, err := rand.Read(encryptBytes); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
for i, b := range encryptBytes {
|
||||||
|
encryptKey[i] = int(b) // 0-255范围
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成解密密钥长度
|
||||||
|
if _, err := rand.Read(lengthByte[:]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
decryptKeyLen := 15 + int(lengthByte[0])%16 // 15-30位随机长度
|
||||||
|
|
||||||
|
decryptKey := make([]int, decryptKeyLen)
|
||||||
|
decryptBytes := make([]byte, decryptKeyLen)
|
||||||
|
if _, err := rand.Read(decryptBytes); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
for i, b := range decryptBytes {
|
||||||
|
decryptKey[i] = int(b) // 0-255范围
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptKey, decryptKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 加密函数 - 对应 UserLogin_encrypt_Up_42510
|
||||||
|
func (e *EasyEncrypt) Encrypt(input string) string {
|
||||||
|
if input == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
mKeyLen := len(e.encryptKey)
|
||||||
|
inputLen := len(input)
|
||||||
|
var result strings.Builder
|
||||||
|
|
||||||
|
for i := 0; i < inputLen; i++ {
|
||||||
|
mCode := int(input[i])
|
||||||
|
mCode = (mCode - 207) ^ e.encryptKey[i%mKeyLen]
|
||||||
|
|
||||||
|
if mCode < 0 {
|
||||||
|
mCode = -mCode
|
||||||
|
result.WriteString("-")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为16进制字符串
|
||||||
|
hexStr := strconv.FormatInt(int64(mCode), 16)
|
||||||
|
result.WriteString(hexStr)
|
||||||
|
result.WriteString(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64编码
|
||||||
|
resultStr := result.String()
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(resultStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt 解密函数 - 对应 UserLogin_decrypt_Down_42510
|
||||||
|
func (e *EasyEncrypt) Decrypt(input string) string {
|
||||||
|
if input == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64解码
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(input)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedStr := string(decoded)
|
||||||
|
mKeyLen := len(e.encryptKey)
|
||||||
|
|
||||||
|
// 按逗号分割
|
||||||
|
parts := strings.Split(decodedStr, ",")
|
||||||
|
var result strings.Builder
|
||||||
|
|
||||||
|
for i, part := range parts {
|
||||||
|
if part == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var d int
|
||||||
|
if strings.HasPrefix(part, "-") {
|
||||||
|
// 处理负数
|
||||||
|
hexStr := part[1:]
|
||||||
|
val, err := strconv.ParseInt(hexStr, 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d = -int(val)
|
||||||
|
} else {
|
||||||
|
// 处理正数
|
||||||
|
val, err := strconv.ParseInt(part, 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d = int(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密计算
|
||||||
|
decryptedChar := (d ^ e.encryptKey[i%mKeyLen]) + 207
|
||||||
|
result.WriteByte(byte(decryptedChar))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptWithKey 使用指定密钥加密
|
||||||
|
func EncryptWithKey(input string, key []int) string {
|
||||||
|
if input == "" || len(key) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
keyLen := len(key)
|
||||||
|
inputLen := len(input)
|
||||||
|
var result strings.Builder
|
||||||
|
|
||||||
|
for i := 0; i < inputLen; i++ {
|
||||||
|
mCode := int(input[i])
|
||||||
|
mCode = (mCode - 207) ^ key[i%keyLen]
|
||||||
|
|
||||||
|
if mCode < 0 {
|
||||||
|
mCode = -mCode
|
||||||
|
result.WriteString("-")
|
||||||
|
}
|
||||||
|
|
||||||
|
hexStr := strconv.FormatInt(int64(mCode), 16)
|
||||||
|
result.WriteString(hexStr)
|
||||||
|
result.WriteString(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
resultStr := result.String()
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(resultStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptWithKey 使用指定密钥解密
|
||||||
|
func DecryptWithKey(input string, key []int) string {
|
||||||
|
if input == "" || len(key) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(input)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedStr := string(decoded)
|
||||||
|
keyLen := len(key)
|
||||||
|
|
||||||
|
parts := strings.Split(decodedStr, ",")
|
||||||
|
var result strings.Builder
|
||||||
|
|
||||||
|
for i, part := range parts {
|
||||||
|
if part == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var d int
|
||||||
|
if strings.HasPrefix(part, "-") {
|
||||||
|
hexStr := part[1:]
|
||||||
|
val, err := strconv.ParseInt(hexStr, 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d = -int(val)
|
||||||
|
} else {
|
||||||
|
val, err := strconv.ParseInt(part, 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d = int(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedChar := (d ^ key[i%keyLen]) + 40
|
||||||
|
result.WriteByte(byte(decryptedChar))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatKeyAsString 将密钥数组格式化为字符串(用于存储)
|
||||||
|
func FormatKeyAsString(key []int) string {
|
||||||
|
var parts []string
|
||||||
|
for _, k := range key {
|
||||||
|
parts = append(parts, fmt.Sprintf("%d", k))
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseKeyFromString 从字符串解析密钥数组
|
||||||
|
func ParseKeyFromString(keyStr string) []int {
|
||||||
|
if keyStr == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(keyStr, ",")
|
||||||
|
var key []int
|
||||||
|
|
||||||
|
for _, part := range parts {
|
||||||
|
if val, err := strconv.Atoi(strings.TrimSpace(part)); err == nil {
|
||||||
|
key = append(key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return key
|
||||||
|
}
|
||||||
@@ -291,7 +291,13 @@ loadScript(layuijs, function () {
|
|||||||
'user-username': '用户名:用于登录的用户名,可以修改但需要保证唯一性',
|
'user-username': '用户名:用于登录的用户名,可以修改但需要保证唯一性',
|
||||||
'user-old-password': '旧密码:修改密码时需要输入当前密码进行验证,不修改密码时可留空',
|
'user-old-password': '旧密码:修改密码时需要输入当前密码进行验证,不修改密码时可留空',
|
||||||
'user-new-password': '新密码:要设置的新密码,长度至少6位,不修改密码时可留空',
|
'user-new-password': '新密码:要设置的新密码,长度至少6位,不修改密码时可留空',
|
||||||
'user-confirm-password': '确认密码:再次输入新密码进行确认,必须与新密码一致'
|
'user-confirm-password': '确认密码:再次输入新密码进行确认,必须与新密码一致',
|
||||||
|
// API接口管理相关 (apis.html)
|
||||||
|
'submit-algorithm': '提交算法:客户端向服务器提交数据时使用的加密算法<br/>• 不加密:数据明文传输,适用于内网环境<br/>• RC4:对称加密,速度快,适用于一般场景<br/>• RSA:非对称加密,安全性高,适用于敏感数据<br/>• RSA(动态):动态生成密钥的RSA加密,安全性最高<br/>• 易加密:自定义对称加密算法,使用15-30位整数密钥数组',
|
||||||
|
'submit-keys': '提交密钥:用于加密客户端提交数据的密钥<br/>• RC4:16位十六进制密钥,用于对称加密<br/>• RSA:公钥用于客户端加密,私钥用于服务器解密<br/>• 易加密:15-30位整数数组,逗号分隔<br/>• 密钥由系统自动生成,确保安全性',
|
||||||
|
'return-algorithm': '返回算法:服务器向客户端返回数据时使用的加密算法<br/>• 不加密:数据明文传输,适用于内网环境<br/>• RC4:对称加密,速度快,适用于一般场景<br/>• RSA:非对称加密,安全性高,适用于敏感数据<br/>• RSA(动态):动态生成密钥的RSA加密,安全性最高<br/>• 易加密:自定义对称加密算法,使用15-30位整数密钥数组',
|
||||||
|
'return-keys': '返回密钥:用于加密服务器返回数据的密钥<br/>• RC4:16位十六进制密钥,用于对称加密<br/>• RSA:公钥用于服务器加密,私钥用于客户端解密<br/>• 易加密:15-30位整数数组,逗号分隔<br/>• 密钥由系统自动生成,确保安全性',
|
||||||
|
'api-status': '接口状态:控制当前API接口是否可用<br/>• 启用:接口正常工作,客户端可以调用<br/>• 禁用:接口暂停服务,客户端调用将返回错误'
|
||||||
};
|
};
|
||||||
return tips[type] || '暂无说明';
|
return tips[type] || '暂无说明';
|
||||||
}
|
}
|
||||||
|
|||||||
526
web/template/admin/apis.html
Normal file
526
web/template/admin/apis.html
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
{{ define "apis.html" }}
|
||||||
|
<section>
|
||||||
|
<h2>接口管理</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="layui-card" style="margin-top:12px">
|
||||||
|
<div class="layui-card-header">筛选</div>
|
||||||
|
<div class="layui-card-body">
|
||||||
|
<form class="layui-form layui-form-pane" id="apiFilterForm" lay-filter="apiFilterForm">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">应用</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<select name="app_uuid" lay-filter="appSelect" lay-search="">
|
||||||
|
<option value="">请选择应用</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">搜索</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="search" placeholder="API密钥/应用UUID" autocomplete="off" class="layui-input" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<button type="button" class="layui-btn" id="btnSearchAPIs">查询</button>
|
||||||
|
<button type="button" class="layui-btn layui-btn-primary" id="btnResetAPIs">重置</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layui-card" style="margin-top:12px">
|
||||||
|
<div class="layui-card-header">接口列表</div>
|
||||||
|
<div class="layui-card-body">
|
||||||
|
<table id="apisTable" lay-filter="apisTableFilter"></table>
|
||||||
|
<script type="text/html" id="tpl-apis-ops">
|
||||||
|
<div style="white-space: nowrap;">
|
||||||
|
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
|
||||||
|
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="reset">重置密钥</a>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<script type="text/html" id="tpl-apis-status">
|
||||||
|
{{`{{# if(d.status === 1) { }}`}}
|
||||||
|
<span class="layui-badge layui-bg-green">启用</span>
|
||||||
|
{{`{{# } else { }}`}}
|
||||||
|
<span class="layui-badge">禁用</span>
|
||||||
|
{{`{{# } }}`}}
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 隐藏的表单弹层内容:编辑接口 -->
|
||||||
|
<div id="apiFormModal" style="display:none;padding:16px">
|
||||||
|
<form class="layui-form layui-form-pane" id="apiForm">
|
||||||
|
<input type="hidden" name="id" />
|
||||||
|
<!-- 关联应用与接口类型为固定项,移除编辑能力 -->
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="submit-algorithm">提交算法</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<select name="submit_algorithm" lay-verify="required" lay-filter="submitAlgorithm">
|
||||||
|
<option value="0">不加密</option>
|
||||||
|
<option value="1">RC4</option>
|
||||||
|
<option value="2">RSA</option>
|
||||||
|
<option value="3">RSA(动态)</option>
|
||||||
|
<option value="4">易加密</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 提交密钥/证书输入区:根据算法动态显示 -->
|
||||||
|
<div class="layui-form-item" id="submitKeysContainer" style="display:none">
|
||||||
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="submit-keys">提交密钥</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<div id="submit-rc4" style="display:none">
|
||||||
|
<input type="text" name="submit_private_key" placeholder="RC4密钥(16位十六进制,大写)" autocomplete="off" class="layui-input" readonly />
|
||||||
|
</div>
|
||||||
|
<div id="submit-rsa" style="display:none">
|
||||||
|
<textarea name="submit_public_key" placeholder="RSA 公钥(PEM 明文)" class="layui-textarea" rows="4" readonly></textarea>
|
||||||
|
<textarea name="submit_private_key" placeholder="RSA 私钥(PEM 明文)" class="layui-textarea" rows="6" style="margin-top:8px;" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
<div id="submit-easy" style="display:none">
|
||||||
|
<input type="text" name="submit_private_key" placeholder="易加密密钥(15-30位整数数组,逗号分隔)" autocomplete="off" class="layui-input" readonly />
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:8px;">
|
||||||
|
<button type="button" class="layui-btn layui-btn-sm" id="btnGenSubmitKeys">一键生成</button>
|
||||||
|
<span class="layui-word-aux" style="margin-left:8px;">密钥由系统生成,禁止手动输入</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="return-algorithm">返回算法</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<select name="return_algorithm" lay-verify="required" lay-filter="returnAlgorithm">
|
||||||
|
<option value="0">不加密</option>
|
||||||
|
<option value="1">RC4</option>
|
||||||
|
<option value="2">RSA</option>
|
||||||
|
<option value="3">RSA(动态)</option>
|
||||||
|
<option value="4">易加密</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 返回密钥/证书输入区:根据算法动态显示 -->
|
||||||
|
<div class="layui-form-item" id="returnKeysContainer" style="display:none">
|
||||||
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="return-keys">返回密钥</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<div id="return-rc4" style="display:none">
|
||||||
|
<input type="text" name="return_private_key" placeholder="RC4密钥(16位十六进制,大写)" autocomplete="off" class="layui-input" readonly />
|
||||||
|
</div>
|
||||||
|
<div id="return-rsa" style="display:none">
|
||||||
|
<textarea name="return_public_key" placeholder="RSA 公钥(PEM 明文)" class="layui-textarea" rows="4" readonly></textarea>
|
||||||
|
<textarea name="return_private_key" placeholder="RSA 私钥(PEM 明文)" class="layui-textarea" rows="6" style="margin-top:8px;" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
<div id="return-easy" style="display:none">
|
||||||
|
<input type="text" name="return_private_key" placeholder="易加密密钥(15-30位整数数组,逗号分隔)" autocomplete="off" class="layui-input" readonly />
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:8px;">
|
||||||
|
<button type="button" class="layui-btn layui-btn-sm" id="btnGenReturnKeys">一键生成</button>
|
||||||
|
<span class="layui-word-aux" style="margin-left:8px;">密钥由系统生成,禁止手动输入</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item" pane>
|
||||||
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="api-status">接口状态</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" checked>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
||||||
|
const $ = layui.$;
|
||||||
|
var table = layui.table;
|
||||||
|
var form = layui.form;
|
||||||
|
var layer = layui.layer;
|
||||||
|
var dropdown = layui.dropdown;
|
||||||
|
|
||||||
|
// 格式化时间函数
|
||||||
|
function formatDateTime(dateStr) {
|
||||||
|
if (!dateStr) return '-';
|
||||||
|
return new Date(dateStr).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前选中的应用UUID
|
||||||
|
var currentAppUUID = '';
|
||||||
|
|
||||||
|
// 初始化接口表格
|
||||||
|
var apisTable = table.render({
|
||||||
|
elem: '#apisTable',
|
||||||
|
url: '/admin/api/apis/list',
|
||||||
|
parseData: function(res) {
|
||||||
|
return {
|
||||||
|
code: res.success ? 0 : 1,
|
||||||
|
msg: res.message || '',
|
||||||
|
count: res.data ? res.data.total : 0,
|
||||||
|
data: res.data ? res.data.apis : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
pageName: 'page',
|
||||||
|
limitName: 'limit'
|
||||||
|
},
|
||||||
|
method: 'GET',
|
||||||
|
page: true,
|
||||||
|
limit: 20,
|
||||||
|
limits: [10, 20, 50, 100],
|
||||||
|
loading: true,
|
||||||
|
cols: [[
|
||||||
|
{ type: 'checkbox', width: 50 },
|
||||||
|
{ field: 'id', title: 'ID', width: 80, sort: true },
|
||||||
|
{ field: 'app_name', title: '应用名称', minWidth: 150 },
|
||||||
|
{ field: 'api_type_name', title: '接口类型', minWidth: 120 },
|
||||||
|
{
|
||||||
|
field: 'api_key',
|
||||||
|
title: 'API密钥',
|
||||||
|
minWidth: 280,
|
||||||
|
templet: (d) => '<span style="font-family: monospace; font-size: 12px;">' + d.api_key + '</span>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status_name',
|
||||||
|
title: '状态',
|
||||||
|
width: 80,
|
||||||
|
templet: (d) => {
|
||||||
|
if (d.status === 1) return '<span style="color: #5FB878;">启用</span>';
|
||||||
|
return '<span style="color: #FF5722;">禁用</span>';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'submit_algorithm',
|
||||||
|
title: '提交算法',
|
||||||
|
width: 120,
|
||||||
|
templet: (d) => d.algorithm_names ? d.algorithm_names.submit : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'return_algorithm',
|
||||||
|
title: '返回算法',
|
||||||
|
width: 120,
|
||||||
|
templet: (d) => d.algorithm_names ? d.algorithm_names.return : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'created_at',
|
||||||
|
title: '创建时间',
|
||||||
|
width: 180,
|
||||||
|
templet: (d) => formatDateTime(d.created_at)
|
||||||
|
},
|
||||||
|
{ fixed: 'right', title: '操作', toolbar: '#tpl-apis-ops', width: 180 }
|
||||||
|
]]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载应用列表到筛选器
|
||||||
|
function loadApps() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/api/apis/apps',
|
||||||
|
type: 'GET',
|
||||||
|
success: function(res) {
|
||||||
|
if (res.success && res.data) {
|
||||||
|
var filterSelect = $('select[name="app_uuid"]').eq(0);
|
||||||
|
// 清空现有选项(保留默认选项)
|
||||||
|
filterSelect.find('option:not(:first)').remove();
|
||||||
|
// 添加应用选项(不默认选中,保持“请选择应用”以显示全部接口)
|
||||||
|
res.data.forEach(function(app) {
|
||||||
|
var option = '<option value="' + app.uuid + '">' + app.name + '</option>';
|
||||||
|
filterSelect.append(option);
|
||||||
|
});
|
||||||
|
// 仅刷新下拉,不触发表格按应用过滤,默认显示全部接口
|
||||||
|
form.render('select');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
layer.msg('加载应用列表失败', {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化加载应用列表
|
||||||
|
loadApps();
|
||||||
|
|
||||||
|
// 监听应用选择变化
|
||||||
|
form.on('select(appSelect)', function(data) {
|
||||||
|
currentAppUUID = data.value;
|
||||||
|
apisTable.reload({
|
||||||
|
where: {
|
||||||
|
app_uuid: currentAppUUID,
|
||||||
|
search: $('input[name="search"]').val()
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
curr: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 搜索功能
|
||||||
|
$('#btnSearchAPIs').on('click', function() {
|
||||||
|
const search = $('input[name="search"]').val();
|
||||||
|
const appUUID = $('select[name="app_uuid"]').eq(0).val();
|
||||||
|
apisTable.reload({
|
||||||
|
where: {
|
||||||
|
app_uuid: appUUID,
|
||||||
|
search: search
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
curr: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重置搜索
|
||||||
|
$('#btnResetAPIs').on('click', function() {
|
||||||
|
$('#apiFilterForm')[0].reset();
|
||||||
|
form.render();
|
||||||
|
apisTable.reload({
|
||||||
|
where: {},
|
||||||
|
page: {
|
||||||
|
curr: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 算法联动与一键生成
|
||||||
|
function refreshSubmitKeysUI(row) {
|
||||||
|
var algo = parseInt($('select[name="submit_algorithm"]').val());
|
||||||
|
if (algo === 0) {
|
||||||
|
$('#submitKeysContainer').hide();
|
||||||
|
$('#submit-rc4').hide();
|
||||||
|
$('#submit-rsa').hide();
|
||||||
|
$('#submit-easy').hide();
|
||||||
|
$('[name="submit_public_key"],[name="submit_private_key"]').val('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('#submitKeysContainer').show();
|
||||||
|
if (algo === 1) { // RC4
|
||||||
|
$('#submit-rc4').show();
|
||||||
|
$('#submit-rsa').hide();
|
||||||
|
$('#submit-easy').hide();
|
||||||
|
if (row && row.submit_private_key) {
|
||||||
|
$('[name="submit_private_key"]').val(row.submit_private_key);
|
||||||
|
}
|
||||||
|
} else if (algo === 4) { // 易加密
|
||||||
|
$('#submit-rc4').hide();
|
||||||
|
$('#submit-rsa').hide();
|
||||||
|
$('#submit-easy').show();
|
||||||
|
if (row && row.submit_private_key) {
|
||||||
|
$('[name="submit_private_key"]').val(row.submit_private_key);
|
||||||
|
}
|
||||||
|
} else { // RSA & RSA动态
|
||||||
|
$('#submit-rc4').hide();
|
||||||
|
$('#submit-rsa').show();
|
||||||
|
$('#submit-easy').hide();
|
||||||
|
if (row && (row.submit_public_key || row.submit_private_key)) {
|
||||||
|
$('[name="submit_public_key"]').val(row.submit_public_key || '');
|
||||||
|
$('[name="submit_private_key"]').val(row.submit_private_key || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function refreshReturnKeysUI(row) {
|
||||||
|
var algo = parseInt($('select[name="return_algorithm"]').val());
|
||||||
|
if (algo === 0) {
|
||||||
|
$('#returnKeysContainer').hide();
|
||||||
|
$('#return-rc4').hide();
|
||||||
|
$('#return-rsa').hide();
|
||||||
|
$('#return-easy').hide();
|
||||||
|
$('[name="return_public_key"],[name="return_private_key"]').val('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('#returnKeysContainer').show();
|
||||||
|
if (algo === 1) { // RC4
|
||||||
|
$('#return-rc4').show();
|
||||||
|
$('#return-rsa').hide();
|
||||||
|
$('#return-easy').hide();
|
||||||
|
if (row && row.return_private_key) {
|
||||||
|
$('[name="return_private_key"]').val(row.return_private_key);
|
||||||
|
}
|
||||||
|
} else if (algo === 4) { // 易加密
|
||||||
|
$('#return-rc4').hide();
|
||||||
|
$('#return-rsa').hide();
|
||||||
|
$('#return-easy').show();
|
||||||
|
if (row && row.return_private_key) {
|
||||||
|
$('[name="return_private_key"]').val(row.return_private_key);
|
||||||
|
}
|
||||||
|
} else { // RSA & RSA动态
|
||||||
|
$('#return-rc4').hide();
|
||||||
|
$('#return-rsa').show();
|
||||||
|
$('#return-easy').hide();
|
||||||
|
if (row && (row.return_public_key || row.return_private_key)) {
|
||||||
|
$('[name="return_public_key"]').val(row.return_public_key || '');
|
||||||
|
$('[name="return_private_key"]').val(row.return_private_key || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form.on('select(submitAlgorithm)', function(data){ refreshSubmitKeysUI(); });
|
||||||
|
form.on('select(returnAlgorithm)', function(data){ refreshReturnKeysUI(); });
|
||||||
|
|
||||||
|
$('#btnGenSubmitKeys').on('click', function(){
|
||||||
|
var algo = parseInt($('select[name="submit_algorithm"]').val());
|
||||||
|
if (algo === 0) { layer.msg('请选择加密算法', {icon: 0}); return; }
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/api/apis/generate_keys',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({ side: 'submit', algorithm: algo }),
|
||||||
|
success: function(res){
|
||||||
|
if (res.success && res.data) {
|
||||||
|
if (algo === 1) { // RC4
|
||||||
|
$('[name="submit_private_key"]').val(res.data.private_key || '');
|
||||||
|
} else if (algo === 4) { // 易加密
|
||||||
|
$('[name="submit_private_key"]').val(res.data.private_key || '');
|
||||||
|
} else { // RSA
|
||||||
|
$('[name="submit_public_key"]').val(res.data.public_key || '');
|
||||||
|
$('[name="submit_private_key"]').val(res.data.private_key || '');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
layer.msg(res.message || '生成失败', {icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(){ layer.msg('生成失败', {icon: 2}); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#btnGenReturnKeys').on('click', function(){
|
||||||
|
var algo = parseInt($('select[name="return_algorithm"]').val());
|
||||||
|
if (algo === 0) { layer.msg('请选择加密算法', {icon: 0}); return; }
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/api/apis/generate_keys',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({ side: 'return', algorithm: algo }),
|
||||||
|
success: function(res){
|
||||||
|
if (res.success && res.data) {
|
||||||
|
if (algo === 1) { // RC4
|
||||||
|
$('[name="return_private_key"]').val(res.data.private_key || '');
|
||||||
|
} else if (algo === 4) { // 易加密
|
||||||
|
$('[name="return_private_key"]').val(res.data.private_key || '');
|
||||||
|
} else { // RSA
|
||||||
|
$('[name="return_public_key"]').val(res.data.public_key || '');
|
||||||
|
$('[name="return_private_key"]').val(res.data.private_key || '');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
layer.msg(res.message || '生成失败', {icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(){ layer.msg('生成失败', {icon: 2}); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 监听表格工具条
|
||||||
|
table.on('tool(apisTableFilter)', function(obj) {
|
||||||
|
var data = obj.data;
|
||||||
|
|
||||||
|
if (obj.event === 'edit') {
|
||||||
|
// 编辑接口
|
||||||
|
$('#apiForm')[0].reset();
|
||||||
|
$('input[name="id"]').val(data.id);
|
||||||
|
$('select[name="submit_algorithm"]').val(data.submit_algorithm);
|
||||||
|
$('select[name="return_algorithm"]').val(data.return_algorithm);
|
||||||
|
$('input[name="status"]').prop('checked', data.status === 1);
|
||||||
|
|
||||||
|
// 根据现有算法与密钥填充/显示输入区
|
||||||
|
refreshSubmitKeysUI(data);
|
||||||
|
refreshReturnKeysUI(data);
|
||||||
|
|
||||||
|
layer.open({
|
||||||
|
type: 1,
|
||||||
|
title: '编辑接口',
|
||||||
|
content: $('#apiFormModal'),
|
||||||
|
area: ['500px', '520px'],
|
||||||
|
btn: ['保存', '取消'],
|
||||||
|
yes: function(index, layero) {
|
||||||
|
// 手动收集表单数据
|
||||||
|
var formData = {};
|
||||||
|
$('#apiForm').find('input, select, textarea').each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
var name = $this.attr('name');
|
||||||
|
if (name) {
|
||||||
|
if ($this.attr('type') === 'checkbox') {
|
||||||
|
if ($this.attr('lay-skin') === 'switch') {
|
||||||
|
formData[name] = $this.prop('checked') ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
formData[name] = $this.prop('checked') ? $this.val() : '';
|
||||||
|
}
|
||||||
|
} else if ($this.attr('type') === 'radio') {
|
||||||
|
if ($this.prop('checked')) {
|
||||||
|
formData[name] = $this.val();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formData[name] = $this.val();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 转换数值类型
|
||||||
|
formData.submit_algorithm = parseInt(formData.submit_algorithm);
|
||||||
|
formData.return_algorithm = parseInt(formData.return_algorithm);
|
||||||
|
formData.id = parseInt(formData.id);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/api/apis/update',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(formData),
|
||||||
|
success: function(res) {
|
||||||
|
if (res.success) {
|
||||||
|
layer.msg('接口更新成功', {icon: 1});
|
||||||
|
layer.close(index);
|
||||||
|
apisTable.reload();
|
||||||
|
} else {
|
||||||
|
layer.msg(res.message || '更新失败', {icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
layer.msg('网络错误,请稍后重试', {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
btn2: function(index) {
|
||||||
|
layer.close(index);
|
||||||
|
},
|
||||||
|
success: function() {
|
||||||
|
form.render();
|
||||||
|
},
|
||||||
|
shadeClose: false
|
||||||
|
});
|
||||||
|
} else if (obj.event === 'reset') {
|
||||||
|
layer.confirm('确定重置该接口密钥吗?', {icon: 3, title: '提示'}, function(index) {
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/api/apis/reset_key',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({ id: data.id }),
|
||||||
|
success: function(res) {
|
||||||
|
if (res.success) {
|
||||||
|
layer.msg('密钥重置成功', {icon: 1});
|
||||||
|
// 更新当前行的密钥显示
|
||||||
|
if (res.data && res.data.api_key) {
|
||||||
|
obj.update({ api_key: res.data.api_key });
|
||||||
|
} else {
|
||||||
|
// 兜底刷新表格
|
||||||
|
apisTable.reload();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
layer.msg(res.message || '重置失败', {icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
let msg = '重置失败';
|
||||||
|
if (xhr.responseText) {
|
||||||
|
msg = xhr.responseText;
|
||||||
|
}
|
||||||
|
layer.msg(msg, {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layer.close(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
@@ -93,12 +93,7 @@
|
|||||||
<input type="text" name="download_url" placeholder="请输入下载地址" autocomplete="off" class="layui-input" />
|
<input type="text" name="download_url" placeholder="请输入下载地址" autocomplete="off" class="layui-input" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
|
||||||
<div class="layui-input-block">
|
|
||||||
<button type="submit" class="layui-btn" lay-submit lay-filter="appFormSubmit">提交</button>
|
|
||||||
<button type="button" class="layui-btn layui-btn-primary" id="btnCancelApp">取消</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -420,10 +415,66 @@
|
|||||||
title: '新增应用',
|
title: '新增应用',
|
||||||
content: $('#appFormModal'),
|
content: $('#appFormModal'),
|
||||||
area: ['500px', '460px'],
|
area: ['500px', '460px'],
|
||||||
btn: false,
|
btn: ['创建', '取消'],
|
||||||
|
yes: function(index, layero) {
|
||||||
|
// 手动触发表单提交验证
|
||||||
|
var formData = {};
|
||||||
|
$('#appForm').find('input, select, textarea').each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
var name = $this.attr('name');
|
||||||
|
if (name) {
|
||||||
|
if ($this.attr('type') === 'checkbox') {
|
||||||
|
if ($this.attr('lay-skin') === 'switch') {
|
||||||
|
formData[name] = $this.prop('checked') ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
formData[name] = $this.prop('checked') ? $this.val() : '';
|
||||||
|
}
|
||||||
|
} else if ($this.attr('type') === 'radio') {
|
||||||
|
if ($this.prop('checked')) {
|
||||||
|
formData[name] = $this.val();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formData[name] = $this.val();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 验证必填字段
|
||||||
|
if (!formData.name || formData.name.trim() === '') {
|
||||||
|
layer.msg('请输入应用名称', {icon: 2});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理数据类型转换
|
||||||
|
formData.download_type = parseInt(formData.download_type) || 0;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/api/apps/create',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify(formData),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(res) {
|
||||||
|
if (res.code === 0) {
|
||||||
|
layer.msg(res.msg, {icon: 1});
|
||||||
|
layer.close(index);
|
||||||
|
appsTable.reload();
|
||||||
|
} else {
|
||||||
|
layer.msg(res.msg || '操作失败', {icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
layer.msg(xhr.responseText || '操作失败', {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
btn2: function(index) {
|
||||||
|
layer.close(index);
|
||||||
|
},
|
||||||
|
success: function() {
|
||||||
|
form.render();
|
||||||
|
},
|
||||||
shadeClose: false
|
shadeClose: false
|
||||||
});
|
});
|
||||||
form.render();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听更新方式切换(保留事件监听器以备将来扩展)
|
// 监听更新方式切换(保留事件监听器以备将来扩展)
|
||||||
@@ -431,49 +482,7 @@
|
|||||||
// 下载地址字段现在始终显示,无需切换显示状态
|
// 下载地址字段现在始终显示,无需切换显示状态
|
||||||
});
|
});
|
||||||
|
|
||||||
// 表单提交
|
|
||||||
form.on('submit(appFormSubmit)', function(data) {
|
|
||||||
const isEdit = data.field.id !== '';
|
|
||||||
const url = isEdit ? '/admin/api/apps/update' : '/admin/api/apps/create';
|
|
||||||
|
|
||||||
// 转换字段类型为正确的数据类型
|
|
||||||
const formData = {
|
|
||||||
...data.field,
|
|
||||||
status: data.field.status === 'on' ? 1 : 0, // switch开关处理
|
|
||||||
download_type: parseInt(data.field.download_type) || 0,
|
|
||||||
force_update: data.field.force_update === 'on' ? 1 : 0 // switch开关处理
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果是编辑模式,确保id也是整数
|
|
||||||
if (isEdit) {
|
|
||||||
formData.id = parseInt(data.field.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: 'POST',
|
|
||||||
data: JSON.stringify(formData),
|
|
||||||
contentType: 'application/json',
|
|
||||||
success: function(res) {
|
|
||||||
if (res.code === 0) {
|
|
||||||
layer.msg(res.msg, {icon: 1});
|
|
||||||
layer.closeAll();
|
|
||||||
appsTable.reload();
|
|
||||||
} else {
|
|
||||||
layer.msg(res.msg || '操作失败', {icon: 2});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr) {
|
|
||||||
layer.msg(xhr.responseText || '操作失败', {icon: 2});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 取消按钮
|
|
||||||
$('#btnCancelApp').on('click', function() {
|
|
||||||
layer.closeAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表格工具栏事件
|
// 表格工具栏事件
|
||||||
table.on('tool(appsTableFilter)', function(obj) {
|
table.on('tool(appsTableFilter)', function(obj) {
|
||||||
@@ -498,10 +507,67 @@
|
|||||||
title: '编辑应用',
|
title: '编辑应用',
|
||||||
content: $('#appFormModal'),
|
content: $('#appFormModal'),
|
||||||
area: ['500px', '460px'],
|
area: ['500px', '460px'],
|
||||||
btn: false,
|
btn: ['保存', '取消'],
|
||||||
|
yes: function(index, layero) {
|
||||||
|
// 手动触发表单提交验证
|
||||||
|
var formData = {};
|
||||||
|
$('#appForm').find('input, select, textarea').each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
var name = $this.attr('name');
|
||||||
|
if (name) {
|
||||||
|
if ($this.attr('type') === 'checkbox') {
|
||||||
|
if ($this.attr('lay-skin') === 'switch') {
|
||||||
|
formData[name] = $this.prop('checked') ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
formData[name] = $this.prop('checked') ? $this.val() : '';
|
||||||
|
}
|
||||||
|
} else if ($this.attr('type') === 'radio') {
|
||||||
|
if ($this.prop('checked')) {
|
||||||
|
formData[name] = $this.val();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formData[name] = $this.val();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 验证必填字段
|
||||||
|
if (!formData.name || formData.name.trim() === '') {
|
||||||
|
layer.msg('请输入应用名称', {icon: 2});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理数据类型转换
|
||||||
|
formData.download_type = parseInt(formData.download_type) || 0;
|
||||||
|
formData.id = parseInt(formData.id);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/api/apps/update',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify(formData),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(res) {
|
||||||
|
if (res.code === 0) {
|
||||||
|
layer.msg(res.msg, {icon: 1});
|
||||||
|
layer.close(index);
|
||||||
|
appsTable.reload();
|
||||||
|
} else {
|
||||||
|
layer.msg(res.msg || '操作失败', {icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
layer.msg(xhr.responseText || '操作失败', {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
btn2: function(index) {
|
||||||
|
layer.close(index);
|
||||||
|
},
|
||||||
|
success: function() {
|
||||||
|
form.render();
|
||||||
|
},
|
||||||
shadeClose: false
|
shadeClose: false
|
||||||
});
|
});
|
||||||
form.render();
|
|
||||||
|
|
||||||
} else if (obj.event === 'del') {
|
} else if (obj.event === 'del') {
|
||||||
// 删除
|
// 删除
|
||||||
@@ -531,6 +597,10 @@
|
|||||||
elem: this, // 使用 this 而不是查找元素
|
elem: this, // 使用 this 而不是查找元素
|
||||||
show: true, // 外部事件触发即显示
|
show: true, // 外部事件触发即显示
|
||||||
data: [
|
data: [
|
||||||
|
{
|
||||||
|
title: '应用数据',
|
||||||
|
id: 'app_data'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '程序公告',
|
title: '程序公告',
|
||||||
id: 'announcement'
|
id: 'announcement'
|
||||||
@@ -553,7 +623,67 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
click: function(menudata, othis) {
|
click: function(menudata, othis) {
|
||||||
if (menudata.id === 'announcement') {
|
if (menudata.id === 'app_data') {
|
||||||
|
// 应用数据
|
||||||
|
// 先获取当前应用数据内容
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/api/apps/get_app_data?uuid=' + obj.data.uuid,
|
||||||
|
type: 'GET',
|
||||||
|
success: function(res) {
|
||||||
|
var currentAppData = '';
|
||||||
|
if (res.code === 0 && res.data && res.data.app_data) {
|
||||||
|
currentAppData = res.data.app_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示编辑弹窗
|
||||||
|
layer.open({
|
||||||
|
type: 1,
|
||||||
|
title: '编辑应用数据 - ' + obj.data.name,
|
||||||
|
area: ['600px', '400px'],
|
||||||
|
content: '<div style="padding: 20px;">' +
|
||||||
|
'<textarea id="appDataEditor" class="layui-textarea" placeholder="请输入应用数据内容..." style="height: 250px;">' +
|
||||||
|
currentAppData +
|
||||||
|
'</textarea>' +
|
||||||
|
'</div>',
|
||||||
|
btn: ['保存', '取消'],
|
||||||
|
yes: function(index, layero) {
|
||||||
|
var appDataContent = $('#appDataEditor').val();
|
||||||
|
|
||||||
|
// 发送更新请求
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/api/apps/update_app_data',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({
|
||||||
|
uuid: obj.data.uuid,
|
||||||
|
app_data: appDataContent
|
||||||
|
}),
|
||||||
|
success: function(res) {
|
||||||
|
if (res.code === 0) {
|
||||||
|
layer.msg('应用数据更新成功!', {
|
||||||
|
icon: 1,
|
||||||
|
time: 2000
|
||||||
|
});
|
||||||
|
layer.close(index);
|
||||||
|
} else {
|
||||||
|
layer.msg(res.msg || '更新应用数据失败', {icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
layer.msg('网络错误,请稍后重试', {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
btn2: function(index) {
|
||||||
|
layer.close(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
layer.msg('获取应用数据失败,请稍后重试', {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (menudata.id === 'announcement') {
|
||||||
// 程序公告
|
// 程序公告
|
||||||
// 先获取当前公告内容
|
// 先获取当前公告内容
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
<a href="javascript:;">应用管理</a>
|
<a href="javascript:;">应用管理</a>
|
||||||
<dl class="layui-nav-child">
|
<dl class="layui-nav-child">
|
||||||
<dd><a data-path="apps" href="javascript:;">应用列表</a></dd>
|
<dd><a data-path="apps" href="javascript:;">应用列表</a></dd>
|
||||||
|
<dd><a data-path="apis" href="javascript:;">接口列表</a></dd>
|
||||||
</dl>
|
</dl>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user