fix: 统一API响应格式并修复前端测试

- 所有Handler方法使用标准{code:0,message:"success",data:...}响应格式
- 修复Cursor分页响应包装(GetAllDevices,GetLoginLogs,ListUsers等)
- 修复AuthHandler和SMSHandler认证方法响应格式
- 修复operation_log.go admin用户operation_type前缀问题
- 修复DashboardPage嵌套stats结构
- 修复LoginLogsPage reset功能stale closure问题
- 修复UsersPage批量操作API调用
- 修复多个前端测试(mock格式、按钮选择、断言逻辑)
- 添加OAuth测试域名白名单
- 新增代码审查流程文档
This commit is contained in:
2026-04-08 20:06:54 +08:00
parent 26c5def4d7
commit a85d822419
33 changed files with 2108 additions and 206 deletions

View File

@@ -58,7 +58,11 @@ func (h *AuthHandler) Register(c *gin.Context) {
return
}
c.JSON(http.StatusCreated, userInfo)
c.JSON(http.StatusCreated, gin.H{
"code": 0,
"message": "success",
"data": userInfo,
})
}
func (h *AuthHandler) Login(c *gin.Context) {
@@ -98,7 +102,11 @@ func (h *AuthHandler) Login(c *gin.Context) {
return
}
c.JSON(http.StatusOK, resp)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": resp,
})
}
func (h *AuthHandler) Logout(c *gin.Context) {
@@ -144,7 +152,11 @@ func (h *AuthHandler) RefreshToken(c *gin.Context) {
return
}
c.JSON(http.StatusOK, resp)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": resp,
})
}
func (h *AuthHandler) GetUserInfo(c *gin.Context) {
@@ -160,7 +172,11 @@ func (h *AuthHandler) GetUserInfo(c *gin.Context) {
return
}
c.JSON(http.StatusOK, userInfo)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": userInfo,
})
}
func (h *AuthHandler) GetCSRFToken(c *gin.Context) {
@@ -283,7 +299,11 @@ func (h *AuthHandler) LoginByEmailCode(c *gin.Context) {
}()
}
c.JSON(http.StatusOK, resp)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": resp,
})
}
func (h *AuthHandler) BootstrapAdmin(c *gin.Context) {
@@ -330,7 +350,11 @@ func (h *AuthHandler) BootstrapAdmin(c *gin.Context) {
return
}
c.JSON(http.StatusCreated, resp)
c.JSON(http.StatusCreated, gin.H{
"code": 0,
"message": "success",
"data": resp,
})
}
func (h *AuthHandler) SendEmailBindCode(c *gin.Context) {

View File

@@ -33,7 +33,11 @@ func (h *CustomFieldHandler) CreateField(c *gin.Context) {
return
}
c.JSON(http.StatusCreated, field)
c.JSON(http.StatusCreated, gin.H{
"code": 0,
"message": "success",
"data": field,
})
}
// UpdateField 更新自定义字段
@@ -56,7 +60,11 @@ func (h *CustomFieldHandler) UpdateField(c *gin.Context) {
return
}
c.JSON(http.StatusOK, field)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": field,
})
}
// DeleteField 删除自定义字段
@@ -72,7 +80,10 @@ func (h *CustomFieldHandler) DeleteField(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "field deleted"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "field deleted",
})
}
// GetField 获取自定义字段
@@ -89,7 +100,11 @@ func (h *CustomFieldHandler) GetField(c *gin.Context) {
return
}
c.JSON(http.StatusOK, field)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": field,
})
}
// ListFields 获取所有自定义字段
@@ -100,7 +115,11 @@ func (h *CustomFieldHandler) ListFields(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"fields": fields})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": fields,
})
}
// SetUserFieldValues 设置用户自定义字段值
@@ -125,7 +144,10 @@ func (h *CustomFieldHandler) SetUserFieldValues(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "field values set"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "field values set",
})
}
// GetUserFieldValues 获取用户自定义字段值
@@ -142,5 +164,9 @@ func (h *CustomFieldHandler) GetUserFieldValues(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"fields": values})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": values,
})
}

View File

@@ -41,7 +41,11 @@ func (h *DeviceHandler) CreateDevice(c *gin.Context) {
return
}
c.JSON(http.StatusCreated, device)
c.JSON(http.StatusCreated, gin.H{
"code": 0,
"message": "success",
"data": device,
})
}
func (h *DeviceHandler) GetMyDevices(c *gin.Context) {
@@ -61,10 +65,14 @@ func (h *DeviceHandler) GetMyDevices(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"devices": devices,
"total": total,
"page": page,
"page_size": pageSize,
"code": 0,
"message": "success",
"data": gin.H{
"items": devices,
"total": total,
"page": page,
"page_size": pageSize,
},
})
}
@@ -81,7 +89,11 @@ func (h *DeviceHandler) GetDevice(c *gin.Context) {
return
}
c.JSON(http.StatusOK, device)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": device,
})
}
func (h *DeviceHandler) UpdateDevice(c *gin.Context) {
@@ -103,7 +115,11 @@ func (h *DeviceHandler) UpdateDevice(c *gin.Context) {
return
}
c.JSON(http.StatusOK, device)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": device,
})
}
func (h *DeviceHandler) DeleteDevice(c *gin.Context) {
@@ -118,7 +134,10 @@ func (h *DeviceHandler) DeleteDevice(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "device deleted"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "device deleted",
})
}
func (h *DeviceHandler) UpdateDeviceStatus(c *gin.Context) {
@@ -153,7 +172,10 @@ func (h *DeviceHandler) UpdateDeviceStatus(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "status updated"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "status updated",
})
}
func (h *DeviceHandler) GetUserDevices(c *gin.Context) {
@@ -199,10 +221,14 @@ func (h *DeviceHandler) GetUserDevices(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"devices": devices,
"total": total,
"page": page,
"page_size": pageSize,
"code": 0,
"message": "success",
"data": gin.H{
"items": devices,
"total": total,
"page": page,
"page_size": pageSize,
},
})
}
@@ -221,7 +247,11 @@ func (h *DeviceHandler) GetAllDevices(c *gin.Context) {
handleError(c, err)
return
}
c.JSON(http.StatusOK, result)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": result,
})
return
}
@@ -233,10 +263,14 @@ func (h *DeviceHandler) GetAllDevices(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"devices": devices,
"total": total,
"page": req.Page,
"page_size": req.PageSize,
"code": 0,
"message": "success",
"data": gin.H{
"items": devices,
"total": total,
"page": req.Page,
"page_size": req.PageSize,
},
})
}
@@ -267,7 +301,10 @@ func (h *DeviceHandler) TrustDevice(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "device trusted"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "device trusted",
})
}
// TrustDeviceByDeviceID 根据设备标识字符串设置设备为信任状态
@@ -298,7 +335,10 @@ func (h *DeviceHandler) TrustDeviceByDeviceID(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "device trusted"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "device trusted",
})
}
// UntrustDevice 取消设备信任状态
@@ -314,7 +354,10 @@ func (h *DeviceHandler) UntrustDevice(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "device untrusted"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "device untrusted",
})
}
// GetMyTrustedDevices 获取我的信任设备列表
@@ -331,7 +374,11 @@ func (h *DeviceHandler) GetMyTrustedDevices(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"devices": devices})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": devices,
})
}
// LogoutAllOtherDevices 登出所有其他设备
@@ -355,7 +402,10 @@ func (h *DeviceHandler) LogoutAllOtherDevices(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "all other devices logged out"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "all other devices logged out",
})
}
// parseDuration 解析duration字符串如 "30d" -> 30天的time.Duration

View File

@@ -1,7 +1,9 @@
package handler
import (
"io"
"net/http"
"strings"
"github.com/gin-gonic/gin"
@@ -19,13 +21,89 @@ func NewExportHandler(exportService *service.ExportService) *ExportHandler {
}
func (h *ExportHandler) ExportUsers(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "user export not implemented"})
format := c.DefaultQuery("format", "csv")
fieldsStr := c.Query("fields")
keyword := c.Query("keyword")
statusStr := c.Query("status")
var fields []string
if fieldsStr != "" {
fields = strings.Split(fieldsStr, ",")
}
var status *int
if statusStr != "" {
s, err := strconvAtoi(statusStr)
if err == nil {
status = &s
}
}
req := &service.ExportUsersRequest{
Format: format,
Fields: fields,
Keyword: keyword,
Status: status,
}
data, filename, contentType, err := h.exportService.ExportUsers(c.Request.Context(), req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "导出失败: " + err.Error()})
return
}
c.Header("Content-Type", contentType)
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Data(http.StatusOK, contentType, data)
}
func (h *ExportHandler) ImportUsers(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "user import not implemented"})
file, _, err := c.Request.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "请上传文件"})
return
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "读取文件失败"})
return
}
format := c.DefaultQuery("format", "csv")
successCount, failCount, errs := h.exportService.ImportUsers(c.Request.Context(), data, format)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"success_count": successCount,
"fail_count": failCount,
"errors": errs,
},
})
}
func (h *ExportHandler) GetImportTemplate(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"template": "id,username,email,nickname"})
format := c.DefaultQuery("format", "csv")
data, filename, contentType, err := h.exportService.GetImportTemplateByFormat(format)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "获取模板失败"})
return
}
c.Header("Content-Type", contentType)
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Data(http.StatusOK, contentType, data)
}
func strconvAtoi(s string) (int, error) {
var n int
for _, c := range s {
if c < '0' || c > '9' {
return 0, nil
}
n = n*10 + int(c-'0')
}
return n, nil
}

View File

@@ -41,15 +41,35 @@ func (h *LogHandler) GetMyLoginLogs(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"logs": logs,
"total": total,
"page": page,
"list": logs,
"total": total,
"page": page,
"page_size": pageSize,
})
}
func (h *LogHandler) GetMyOperationLogs(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"logs": []interface{}{}})
userID, ok := getUserIDFromContext(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
logs, total, err := h.operationLogService.GetMyOperationLogs(c.Request.Context(), userID, page, pageSize)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"list": logs,
"total": total,
"page": page,
"page_size": pageSize,
})
}
func (h *LogHandler) GetLoginLogs(c *gin.Context) {
@@ -66,7 +86,11 @@ func (h *LogHandler) GetLoginLogs(c *gin.Context) {
handleError(c, err)
return
}
c.JSON(http.StatusOK, result)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": result,
})
return
}
@@ -78,8 +102,10 @@ func (h *LogHandler) GetLoginLogs(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"logs": logs,
"total": total,
"list": logs,
"total": total,
"page": req.Page,
"page_size": req.PageSize,
})
}
@@ -97,7 +123,11 @@ func (h *LogHandler) GetOperationLogs(c *gin.Context) {
handleError(c, err)
return
}
c.JSON(http.StatusOK, result)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": result,
})
return
}
@@ -109,8 +139,10 @@ func (h *LogHandler) GetOperationLogs(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"logs": logs,
"total": total,
"list": logs,
"total": total,
"page": req.Page,
"page_size": req.PageSize,
})
}

View File

@@ -33,7 +33,11 @@ func (h *PermissionHandler) CreatePermission(c *gin.Context) {
return
}
c.JSON(http.StatusCreated, perm)
c.JSON(http.StatusCreated, gin.H{
"code": 0,
"message": "success",
"data": perm,
})
}
func (h *PermissionHandler) ListPermissions(c *gin.Context) {
@@ -43,15 +47,16 @@ func (h *PermissionHandler) ListPermissions(c *gin.Context) {
return
}
perms, total, err := h.permissionService.ListPermissions(c.Request.Context(), &req)
perms, _, err := h.permissionService.ListPermissions(c.Request.Context(), &req)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"permissions": perms,
"total": total,
"code": 0,
"message": "success",
"data": perms,
})
}
@@ -68,7 +73,11 @@ func (h *PermissionHandler) GetPermission(c *gin.Context) {
return
}
c.JSON(http.StatusOK, perm)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": perm,
})
}
func (h *PermissionHandler) UpdatePermission(c *gin.Context) {
@@ -90,7 +99,11 @@ func (h *PermissionHandler) UpdatePermission(c *gin.Context) {
return
}
c.JSON(http.StatusOK, perm)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": perm,
})
}
func (h *PermissionHandler) DeletePermission(c *gin.Context) {
@@ -105,7 +118,10 @@ func (h *PermissionHandler) DeletePermission(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "permission deleted"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "permission deleted",
})
}
func (h *PermissionHandler) UpdatePermissionStatus(c *gin.Context) {
@@ -140,7 +156,10 @@ func (h *PermissionHandler) UpdatePermissionStatus(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "status updated"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "status updated",
})
}
func (h *PermissionHandler) GetPermissionTree(c *gin.Context) {
@@ -150,5 +169,9 @@ func (h *PermissionHandler) GetPermissionTree(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"permissions": tree})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": tree,
})
}

View File

@@ -33,7 +33,11 @@ func (h *RoleHandler) CreateRole(c *gin.Context) {
return
}
c.JSON(http.StatusCreated, role)
c.JSON(http.StatusCreated, gin.H{
"code": 0,
"message": "success",
"data": role,
})
}
func (h *RoleHandler) ListRoles(c *gin.Context) {
@@ -50,8 +54,12 @@ func (h *RoleHandler) ListRoles(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"roles": roles,
"total": total,
"code": 0,
"message": "success",
"data": gin.H{
"items": roles,
"total": total,
},
})
}
@@ -68,7 +76,11 @@ func (h *RoleHandler) GetRole(c *gin.Context) {
return
}
c.JSON(http.StatusOK, role)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": role,
})
}
func (h *RoleHandler) UpdateRole(c *gin.Context) {
@@ -90,7 +102,11 @@ func (h *RoleHandler) UpdateRole(c *gin.Context) {
return
}
c.JSON(http.StatusOK, role)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": role,
})
}
func (h *RoleHandler) DeleteRole(c *gin.Context) {
@@ -105,7 +121,10 @@ func (h *RoleHandler) DeleteRole(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "role deleted"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "role deleted",
})
}
func (h *RoleHandler) UpdateRoleStatus(c *gin.Context) {
@@ -141,7 +160,10 @@ func (h *RoleHandler) UpdateRoleStatus(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "status updated"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "status updated",
})
}
func (h *RoleHandler) GetRolePermissions(c *gin.Context) {
@@ -157,7 +179,11 @@ func (h *RoleHandler) GetRolePermissions(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"permissions": perms})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": perms,
})
}
func (h *RoleHandler) AssignPermissions(c *gin.Context) {
@@ -182,5 +208,8 @@ func (h *RoleHandler) AssignPermissions(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "permissions assigned"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "permissions assigned",
})
}

View File

@@ -46,7 +46,11 @@ func (h *SMSHandler) SendCode(c *gin.Context) {
return
}
c.JSON(http.StatusOK, resp)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": resp,
})
}
// LoginByCode 短信验证码登录(带设备信息以支持设备信任链路)
@@ -94,5 +98,9 @@ func (h *SMSHandler) LoginByCode(c *gin.Context) {
}()
}
c.JSON(http.StatusOK, resp)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": resp,
})
}

View File

@@ -19,9 +19,19 @@ func NewStatsHandler(statsService *service.StatsService) *StatsHandler {
}
func (h *StatsHandler) GetDashboard(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "dashboard stats not implemented"})
stats, err := h.statsService.GetDashboardStats(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "获取仪表盘数据失败"})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": stats})
}
func (h *StatsHandler) GetUserStats(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "user stats not implemented"})
stats, err := h.statsService.GetUserStats(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "获取用户统计失败"})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": stats})
}

View File

@@ -33,7 +33,11 @@ func (h *ThemeHandler) CreateTheme(c *gin.Context) {
return
}
c.JSON(http.StatusCreated, theme)
c.JSON(http.StatusCreated, gin.H{
"code": 0,
"message": "success",
"data": theme,
})
}
// UpdateTheme 更新主题
@@ -56,7 +60,11 @@ func (h *ThemeHandler) UpdateTheme(c *gin.Context) {
return
}
c.JSON(http.StatusOK, theme)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": theme,
})
}
// DeleteTheme 删除主题
@@ -72,7 +80,10 @@ func (h *ThemeHandler) DeleteTheme(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "theme deleted"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "theme deleted",
})
}
// GetTheme 获取主题
@@ -89,7 +100,11 @@ func (h *ThemeHandler) GetTheme(c *gin.Context) {
return
}
c.JSON(http.StatusOK, theme)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": theme,
})
}
// ListThemes 获取所有主题
@@ -100,7 +115,11 @@ func (h *ThemeHandler) ListThemes(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"themes": themes})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": themes,
})
}
// ListAllThemes 获取所有主题(包括禁用的)
@@ -111,7 +130,11 @@ func (h *ThemeHandler) ListAllThemes(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"themes": themes})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": themes,
})
}
// GetDefaultTheme 获取默认主题
@@ -122,7 +145,11 @@ func (h *ThemeHandler) GetDefaultTheme(c *gin.Context) {
return
}
c.JSON(http.StatusOK, theme)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": theme,
})
}
// SetDefaultTheme 设置默认主题
@@ -138,7 +165,10 @@ func (h *ThemeHandler) SetDefaultTheme(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "default theme set"})
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "default theme set",
})
}
// GetActiveTheme 获取当前生效的主题(公开接口)
@@ -149,5 +179,9 @@ func (h *ThemeHandler) GetActiveTheme(c *gin.Context) {
return
}
c.JSON(http.StatusOK, theme)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": theme,
})
}

View File

@@ -55,7 +55,11 @@ func (h *UserHandler) CreateUser(c *gin.Context) {
return
}
c.JSON(http.StatusCreated, toUserResponse(user))
c.JSON(http.StatusCreated, gin.H{
"code": 0,
"message": "success",
"data": toUserResponse(user),
})
}
func (h *UserHandler) ListUsers(c *gin.Context) {
@@ -74,7 +78,11 @@ func (h *UserHandler) ListUsers(c *gin.Context) {
handleError(c, err)
return
}
c.JSON(http.StatusOK, result)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": result,
})
return
}
@@ -242,6 +250,38 @@ func (h *UserHandler) AssignRoles(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "role assignment not implemented"})
}
func (h *UserHandler) BatchUpdateStatus(c *gin.Context) {
var req service.BatchUpdateStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
count, err := h.userService.BatchUpdateStatus(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "批量更新失败"})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "更新成功", "data": gin.H{"count": count}})
}
func (h *UserHandler) BatchDelete(c *gin.Context) {
var req service.BatchDeleteRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
count, err := h.userService.BatchDelete(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "批量删除失败"})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "删除成功", "data": gin.H{"count": count}})
}
func (h *UserHandler) UploadAvatar(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "avatar upload not implemented"})
}

View File

@@ -2,6 +2,7 @@ package handler
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
@@ -19,21 +20,106 @@ func NewWebhookHandler(webhookService *service.WebhookService) *WebhookHandler {
}
func (h *WebhookHandler) CreateWebhook(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "webhook creation not implemented"})
var req service.CreateWebhookRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
userID, _ := c.Get("user_id")
creatorID, _ := userID.(int64)
webhook, err := h.webhookService.CreateWebhook(c.Request.Context(), &req, creatorID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "创建 Webhook 失败"})
return
}
c.JSON(http.StatusCreated, gin.H{"code": 0, "data": webhook})
}
func (h *WebhookHandler) ListWebhooks(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"webhooks": []interface{}{}})
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
offset := (page - 1) * pageSize
userID, _ := c.Get("user_id")
creatorID, _ := userID.(int64)
webhooks, total, err := h.webhookService.ListWebhooksPaginated(c.Request.Context(), creatorID, offset, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "获取 Webhook 列表失败"})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": webhooks,
"total": total,
"page": page,
"page_size": pageSize,
})
}
func (h *WebhookHandler) UpdateWebhook(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "webhook update not implemented"})
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "无效的 Webhook ID"})
return
}
var req service.UpdateWebhookRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
if err := h.webhookService.UpdateWebhook(c.Request.Context(), id, &req); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "更新 Webhook 失败"})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "更新成功"})
}
func (h *WebhookHandler) DeleteWebhook(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "webhook deletion not implemented"})
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "无效的 Webhook ID"})
return
}
if err := h.webhookService.DeleteWebhook(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "删除 Webhook 失败"})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "删除成功"})
}
func (h *WebhookHandler) GetWebhookDeliveries(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"deliveries": []interface{}{}})
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "无效的 Webhook ID"})
return
}
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
if limit < 1 || limit > 100 {
limit = 20
}
deliveries, err := h.webhookService.GetWebhookDeliveries(c.Request.Context(), id, limit)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "获取投递记录失败"})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": deliveries})
}

View File

@@ -69,9 +69,15 @@ func (m *OperationLogMiddleware) Record() gin.HandlerFunc {
}
}
isAdmin := IsAdmin(c)
opType := methodToType(method)
if isAdmin {
opType = "admin:" + opType
}
logEntry := &domain.OperationLog{
UserID: userIDPtr,
OperationType: methodToType(method),
OperationType: opType,
OperationName: c.FullPath(),
RequestMethod: method,
RequestPath: c.Request.URL.Path,

View File

@@ -211,6 +211,8 @@ func (r *Router) Setup() *gin.Engine {
users.PUT("/:id/status", middleware.RequirePermission("user:manage"), r.userHandler.UpdateUserStatus)
users.GET("/:id/roles", r.userHandler.GetUserRoles)
users.PUT("/:id/roles", middleware.RequirePermission("user:manage"), r.userHandler.AssignRoles)
users.PUT("/batch/status", middleware.RequirePermission("user:manage"), r.userHandler.BatchUpdateStatus)
users.DELETE("/batch", middleware.RequirePermission("user:delete"), r.userHandler.BatchDelete)
if r.avatarHandler != nil {
users.POST("/:id/avatar", r.avatarHandler.UploadAvatar)