mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
Fix the api filtering method
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -42,8 +41,12 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// 获取应用UUID参数(用于按应用筛选接口)
|
// 获取应用UUID参数(用于按应用筛选接口)
|
||||||
appUUID := strings.TrimSpace(r.URL.Query().Get("app_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()
|
db, err := database.GetDB()
|
||||||
@@ -61,9 +64,9 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
query = query.Where("app_uuid = ?", appUUID)
|
query = query.Where("app_uuid = ?", appUUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有搜索条件,添加搜索
|
// 如果指定了接口类型,则按接口类型筛选
|
||||||
if search != "" {
|
if apiType > 0 {
|
||||||
query = query.Where("api_key LIKE ? OR app_uuid LIKE ?", "%"+search+"%", "%"+search+"%")
|
query = query.Where("api_type = ?", apiType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取总数
|
// 获取总数
|
||||||
@@ -274,6 +277,48 @@ func APIGetAppsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(response)
|
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) {
|
func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
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")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(response)
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// API 接口表模型
|
// API 接口表模型
|
||||||
@@ -21,9 +16,6 @@ type API struct {
|
|||||||
// API类型(int型)
|
// API类型(int型)
|
||||||
APIType int `gorm:"not null;comment:API类型" json:"api_type"`
|
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表
|
// 应用UUID,关联到App表
|
||||||
AppUUID string `gorm:"size:36;not null;index;comment:关联的应用UUID" json:"app_uuid"`
|
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"`
|
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 指定表名
|
// TableName 指定表名
|
||||||
func (API) TableName() string {
|
func (API) TableName() string {
|
||||||
return "apis"
|
return "apis"
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func RegisterAdminRoutes(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("/admin/api/apis/list", adminctl.AdminAuthRequired(adminctl.APIListHandler))
|
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/update", adminctl.AdminAuthRequired(adminctl.APIUpdateHandler))
|
||||||
mux.HandleFunc("/admin/api/apis/apps", adminctl.AdminAuthRequired(adminctl.APIGetAppsHandler))
|
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))
|
mux.HandleFunc("/admin/api/apis/generate_keys", adminctl.AdminAuthRequired(adminctl.APIGenerateKeysHandler))
|
||||||
|
|
||||||
// 系统信息API(用于仪表盘定时刷新)
|
// 系统信息API(用于仪表盘定时刷新)
|
||||||
|
|||||||
@@ -17,13 +17,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<label class="layui-form-label">搜索</label>
|
<label class="layui-form-label">接口类型</label>
|
||||||
<div class="layui-input-inline">
|
<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>
|
</div>
|
||||||
<div class="layui-inline">
|
<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>
|
<button type="button" class="layui-btn layui-btn-primary" id="btnResetAPIs">重置</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,7 +39,6 @@
|
|||||||
<script type="text/html" id="tpl-apis-ops">
|
<script type="text/html" id="tpl-apis-ops">
|
||||||
<div style="white-space: nowrap;">
|
<div style="white-space: nowrap;">
|
||||||
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
|
<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>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
<script type="text/html" id="tpl-apis-status">
|
<script type="text/html" id="tpl-apis-status">
|
||||||
@@ -139,7 +139,6 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
var form = layui.form;
|
var form = layui.form;
|
||||||
var layer = layui.layer;
|
var layer = layui.layer;
|
||||||
var dropdown = layui.dropdown;
|
var dropdown = layui.dropdown;
|
||||||
|
|
||||||
// 格式化时间函数
|
// 格式化时间函数
|
||||||
function formatDateTime(dateStr) {
|
function formatDateTime(dateStr) {
|
||||||
if (!dateStr) return '-';
|
if (!dateStr) return '-';
|
||||||
@@ -212,21 +211,9 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
loading: true,
|
loading: true,
|
||||||
cols: [[
|
cols: [[
|
||||||
{ field: 'id', title: 'ID', width: 80, sort: true },
|
{ field: 'id', title: 'ID', width: 80, sort: true },
|
||||||
{ field: 'app_name', title: '应用名称', width: 180 },
|
{ field: 'app_name', title: '应用名称', mixWidth: 180 },
|
||||||
{ field: 'api_type_name', title: '接口类型', width: 120 },
|
{ field: 'api_type_name', title: '接口类型', mixWidth: 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: 'status_name',
|
field: 'status_name',
|
||||||
title: '状态',
|
title: '状态',
|
||||||
@@ -254,7 +241,13 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
width: 180,
|
width: 180,
|
||||||
templet: (d) => formatDateTime(d.created_at)
|
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();
|
loadApps();
|
||||||
|
loadAPITypes();
|
||||||
|
|
||||||
// 监听应用选择变化
|
// 监听应用选择变化
|
||||||
form.on('select(appSelect)', function(data) {
|
form.on('select(appSelect)', function(data) {
|
||||||
@@ -292,7 +311,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
apisTable.reload({
|
apisTable.reload({
|
||||||
where: {
|
where: {
|
||||||
app_uuid: currentAppUUID,
|
app_uuid: currentAppUUID,
|
||||||
search: $('input[name="search"]').val()
|
api_type: $('select[name="api_type"]').val()
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
curr: 1
|
curr: 1
|
||||||
@@ -300,14 +319,12 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 搜索功能
|
// 监听接口类型选择变化
|
||||||
$('#btnSearchAPIs').on('click', function() {
|
form.on('select(apiTypeSelect)', function(data) {
|
||||||
const search = $('input[name="search"]').val();
|
|
||||||
const appUUID = $('select[name="app_uuid"]').eq(0).val();
|
|
||||||
apisTable.reload({
|
apisTable.reload({
|
||||||
where: {
|
where: {
|
||||||
app_uuid: appUUID,
|
app_uuid: $('select[name="app_uuid"]').val(),
|
||||||
search: search
|
api_type: data.value
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
curr: 1
|
curr: 1
|
||||||
@@ -315,7 +332,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 重置搜索
|
// 重置筛选
|
||||||
$('#btnResetAPIs').on('click', function() {
|
$('#btnResetAPIs').on('click', function() {
|
||||||
$('#apiFilterForm')[0].reset();
|
$('#apiFilterForm')[0].reset();
|
||||||
form.render();
|
form.render();
|
||||||
@@ -586,37 +603,6 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
},
|
},
|
||||||
shadeClose: false
|
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user