Fix the api filtering method

This commit is contained in:
2025-10-25 20:52:50 +08:00
parent a3eebbde92
commit f41e3dac21
4 changed files with 99 additions and 148 deletions

View File

@@ -1,7 +1,6 @@
package admin
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"net/http"
@@ -42,8 +41,12 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) {
// 获取应用UUID参数用于按应用筛选接口
appUUID := strings.TrimSpace(r.URL.Query().Get("app_uuid"))
// 获取搜索参数
search := strings.TrimSpace(r.URL.Query().Get("search"))
// 获取接口类型参数(用于按接口类型筛选)
apiTypeStr := strings.TrimSpace(r.URL.Query().Get("api_type"))
var apiType int
if apiTypeStr != "" {
apiType, _ = strconv.Atoi(apiTypeStr)
}
// 构建查询
db, err := database.GetDB()
@@ -61,9 +64,9 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) {
query = query.Where("app_uuid = ?", appUUID)
}
// 如果有搜索条件,添加搜索
if search != "" {
query = query.Where("api_key LIKE ? OR app_uuid LIKE ?", "%"+search+"%", "%"+search+"%")
// 如果指定了接口类型,则按接口类型筛选
if apiType > 0 {
query = query.Where("api_type = ?", apiType)
}
// 获取总数
@@ -274,6 +277,48 @@ func APIGetAppsHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(response)
}
// APIGetTypesHandler 获取接口类型列表API处理器
func APIGetTypesHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// 构建接口类型列表
type APITypeItem struct {
Value int `json:"value"`
Name string `json:"name"`
}
var apiTypes []APITypeItem
// 获取所有有效的API类型
validTypes := []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,
models.APITypeDisableUser, models.APITypeBlackUser, models.APITypeUserDeductedTime,
}
for _, apiType := range validTypes {
apiTypes = append(apiTypes, APITypeItem{
Value: apiType,
Name: models.GetAPITypeName(apiType),
})
}
response := map[string]interface{}{
"success": true,
"data": apiTypes,
}
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)
@@ -375,64 +420,3 @@ func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) {
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)
}

View File

@@ -1,12 +1,7 @@
package models
import (
"crypto/rand"
"encoding/hex"
"strings"
"time"
"gorm.io/gorm"
)
// API 接口表模型
@@ -21,9 +16,6 @@ type API struct {
// API类型int型
APIType int `gorm:"not null;comment:API类型" json:"api_type"`
// API密钥
APIKey string `gorm:"size:255;not null;uniqueIndex;comment:API密钥唯一标识" json:"api_key"`
// 应用UUID关联到App表
AppUUID string `gorm:"size:36;not null;index;comment:关联的应用UUID" json:"app_uuid"`
@@ -55,17 +47,6 @@ type API struct {
UpdatedAt time.Time `gorm:"comment:更新时间" json:"updated_at"`
}
// BeforeCreate 在创建记录前自动生成API密钥
func (api *API) BeforeCreate(tx *gorm.DB) error {
if api.APIKey == "" {
// 生成16位大写十六进制API密钥
bytes := make([]byte, 8) // 8字节 = 16位十六进制字符
rand.Read(bytes)
api.APIKey = strings.ToUpper(hex.EncodeToString(bytes))
}
return nil
}
// TableName 指定表名
func (API) TableName() string {
return "apis"

View File

@@ -76,7 +76,7 @@ func RegisterAdminRoutes(mux *http.ServeMux) {
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/types", adminctl.AdminAuthRequired(adminctl.APIGetTypesHandler))
mux.HandleFunc("/admin/api/apis/generate_keys", adminctl.AdminAuthRequired(adminctl.APIGenerateKeysHandler))
// 系统信息API用于仪表盘定时刷新

View File

@@ -17,13 +17,14 @@
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">搜索</label>
<label class="layui-form-label">接口类型</label>
<div class="layui-input-inline">
<input type="text" name="search" placeholder="API接口/应用UUID" autocomplete="off" class="layui-input" />
<select name="api_type" lay-filter="apiTypeSelect">
<option value="">请选择接口类型</option>
</select>
</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>
@@ -38,7 +39,6 @@
<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">
@@ -139,7 +139,6 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
var form = layui.form;
var layer = layui.layer;
var dropdown = layui.dropdown;
// 格式化时间函数
function formatDateTime(dateStr) {
if (!dateStr) return '-';
@@ -212,21 +211,9 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
loading: true,
cols: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'app_name', title: '应用名称', width: 180 },
{ field: 'api_type_name', title: '接口类型', width: 120 },
{
field: 'api_key',
title: 'API接口',
minWidth: 350,
templet: (d) => {
const baseUrl = window.location.protocol + '//' + window.location.host;
const fullUrl = baseUrl + '/api/v1/' + d.api_key;
return '<span style="font-family: monospace; font-size: 12px; word-break: break-all; cursor: pointer; color: #1E9FFF; text-decoration: underline;" ' +
'onclick="copyToClipboard(\'' + fullUrl + '\')" title="点击复制接口地址">' +
fullUrl +
'</span>';
}
},
{ field: 'app_name', title: '应用名称', mixWidth: 180 },
{ field: 'api_type_name', title: '接口类型', mixWidth: 120 },
{
field: 'status_name',
title: '状态',
@@ -254,7 +241,13 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
width: 180,
templet: (d) => formatDateTime(d.created_at)
},
{ fixed: 'right', title: '操作', toolbar: '#tpl-apis-ops', width: 150 }
{
field: 'updated_at',
title: '修改时间',
width: 180,
templet: (d) => formatDateTime(d.updated_at)
},
{ fixed: 'right', title: '操作', toolbar: '#tpl-apis-ops', width: 70 }
]]
});
@@ -283,8 +276,34 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
});
}
// 初始化加载应用列表
// 加载接口类型列表到筛选器
function loadAPITypes() {
$.ajax({
url: '/admin/api/apis/types',
type: 'GET',
success: function(res) {
if (res.success && res.data) {
var typeSelect = $('select[name="api_type"]').eq(0);
// 清空现有选项(保留默认选项)
typeSelect.find('option:not(:first)').remove();
// 添加接口类型选项
res.data.forEach(function(type) {
var option = '<option value="' + type.value + '">' + type.name + '</option>';
typeSelect.append(option);
});
// 刷新下拉
form.render('select');
}
},
error: function() {
layer.msg('加载接口类型列表失败', {icon: 2});
}
});
}
// 初始化加载应用列表和接口类型列表
loadApps();
loadAPITypes();
// 监听应用选择变化
form.on('select(appSelect)', function(data) {
@@ -292,7 +311,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
apisTable.reload({
where: {
app_uuid: currentAppUUID,
search: $('input[name="search"]').val()
api_type: $('select[name="api_type"]').val()
},
page: {
curr: 1
@@ -300,14 +319,12 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
});
});
// 搜索功能
$('#btnSearchAPIs').on('click', function() {
const search = $('input[name="search"]').val();
const appUUID = $('select[name="app_uuid"]').eq(0).val();
// 监听接口类型选择变化
form.on('select(apiTypeSelect)', function(data) {
apisTable.reload({
where: {
app_uuid: appUUID,
search: search
app_uuid: $('select[name="app_uuid"]').val(),
api_type: data.value
},
page: {
curr: 1
@@ -315,7 +332,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
});
});
// 重置搜索
// 重置筛选
$('#btnResetAPIs').on('click', function() {
$('#apiFilterForm')[0].reset();
form.render();
@@ -586,37 +603,6 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
},
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);
});
}
});