Files
lijiaoqiao/supply-api/internal/audit/handler/audit_handler.go

184 lines
5.3 KiB
Go
Raw Normal View History

package handler
import (
"encoding/json"
"net/http"
"strconv"
"lijiaoqiao/supply-api/internal/audit/model"
"lijiaoqiao/supply-api/internal/audit/service"
)
// AuditHandler HTTP处理器
type AuditHandler struct {
svc *service.AuditService
}
// NewAuditHandler 创建审计处理器
func NewAuditHandler(svc *service.AuditService) *AuditHandler {
return &AuditHandler{svc: svc}
}
// CreateEventRequest 创建事件请求
type CreateEventRequest struct {
EventName string `json:"event_name"`
EventCategory string `json:"event_category"`
EventSubCategory string `json:"event_sub_category"`
OperatorID int64 `json:"operator_id"`
TenantID int64 `json:"tenant_id"`
ObjectType string `json:"object_type"`
ObjectID int64 `json:"object_id"`
Action string `json:"action"`
IdempotencyKey string `json:"idempotency_key,omitempty"`
SourceIP string `json:"source_ip,omitempty"`
Success bool `json:"success"`
ResultCode string `json:"result_code,omitempty"`
}
// ErrorResponse 错误响应
type ErrorResponse struct {
Error string `json:"error"`
Code string `json:"code,omitempty"`
Details string `json:"details,omitempty"`
}
// ListEventsResponse 事件列表响应
type ListEventsResponse struct {
Events []*model.AuditEvent `json:"events"`
Total int64 `json:"total"`
Offset int `json:"offset"`
Limit int `json:"limit"`
}
// CreateEvent 处理POST /api/v1/audit/events
// @Summary 创建审计事件
// @Description 创建新的审计事件,支持幂等
// @Tags audit
// @Accept json
// @Produce json
// @Param event body CreateEventRequest true "事件信息"
// @Success 201 {object} service.CreateEventResult
// @Success 200 {object} service.CreateEventResult "幂等重复"
// @Success 409 {object} service.CreateEventResult "幂等冲突"
// @Failure 400 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/audit/events [post]
func (h *AuditHandler) CreateEvent(w http.ResponseWriter, r *http.Request) {
var req CreateEventRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "INVALID_REQUEST", "invalid request body: "+err.Error())
return
}
// 验证必填字段
if req.EventName == "" {
writeError(w, http.StatusBadRequest, "MISSING_FIELD", "event_name is required")
return
}
if req.EventCategory == "" {
writeError(w, http.StatusBadRequest, "MISSING_FIELD", "event_category is required")
return
}
event := &model.AuditEvent{
EventName: req.EventName,
EventCategory: req.EventCategory,
EventSubCategory: req.EventSubCategory,
OperatorID: req.OperatorID,
TenantID: req.TenantID,
ObjectType: req.ObjectType,
ObjectID: req.ObjectID,
Action: req.Action,
IdempotencyKey: req.IdempotencyKey,
SourceIP: req.SourceIP,
Success: req.Success,
ResultCode: req.ResultCode,
}
result, err := h.svc.CreateEvent(r.Context(), event)
if err != nil {
writeError(w, http.StatusInternalServerError, "CREATE_FAILED", err.Error())
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(result.StatusCode)
json.NewEncoder(w).Encode(result)
}
// ListEvents 处理GET /api/v1/audit/events
// @Summary 查询审计事件
// @Description 查询审计事件列表,支持分页和过滤
// @Tags audit
// @Produce json
// @Param tenant_id query int false "租户ID"
// @Param category query string false "事件类别"
// @Param event_name query string false "事件名称"
// @Param offset query int false "偏移量" default(0)
// @Param limit query int false "限制数量" default(100)
// @Success 200 {object} ListEventsResponse
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/audit/events [get]
func (h *AuditHandler) ListEvents(w http.ResponseWriter, r *http.Request) {
filter := &service.EventFilter{}
// 解析查询参数
if tenantIDStr := r.URL.Query().Get("tenant_id"); tenantIDStr != "" {
tenantID, err := strconv.ParseInt(tenantIDStr, 10, 64)
if err == nil {
filter.TenantID = tenantID
}
}
if category := r.URL.Query().Get("category"); category != "" {
filter.Category = category
}
if eventName := r.URL.Query().Get("event_name"); eventName != "" {
filter.EventName = eventName
}
if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" {
offset, err := strconv.Atoi(offsetStr)
if err == nil {
filter.Offset = offset
}
}
if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
limit, err := strconv.Atoi(limitStr)
if err == nil && limit > 0 && limit <= 1000 {
filter.Limit = limit
}
}
if filter.Limit == 0 {
filter.Limit = 100
}
events, total, err := h.svc.ListEventsWithFilter(r.Context(), filter)
if err != nil {
writeError(w, http.StatusInternalServerError, "QUERY_FAILED", err.Error())
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(ListEventsResponse{
Events: events,
Total: total,
Offset: filter.Offset,
Limit: filter.Limit,
})
}
// writeError 写入错误响应
func writeError(w http.ResponseWriter, status int, code, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(ErrorResponse{
Error: message,
Code: code,
Details: "",
})
}