From c7e2aaee89307e8715294c0dce550762b7bae6b7 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 8 Sep 2025 12:27:54 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E5=A2=9E=E5=8A=A0=E6=96=B0=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=B8=AD=E7=BB=A7=E8=AE=BE=E5=A4=87=202.=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/controller/device/device.go | 141 +++++++++++------ internal/controller/operation/operation.go | 144 ++++++------------ internal/controller/response.go | 41 +++++ internal/controller/user/user.go | 130 ++++++++-------- internal/model/device.go | 3 + internal/storage/repository/device.go | 20 +++ .../storage/repository/operation_history.go | 13 ++ 7 files changed, 285 insertions(+), 207 deletions(-) create mode 100644 internal/controller/response.go diff --git a/internal/controller/device/device.go b/internal/controller/device/device.go index 7e2231b..14a12e3 100644 --- a/internal/controller/device/device.go +++ b/internal/controller/device/device.go @@ -6,6 +6,7 @@ import ( "net/http" "git.huangwc.com/pig/pig-farm-controller/internal/api/middleware" + "git.huangwc.com/pig/pig-farm-controller/internal/controller" "git.huangwc.com/pig/pig-farm-controller/internal/logs" "git.huangwc.com/pig/pig-farm-controller/internal/model" "git.huangwc.com/pig/pig-farm-controller/internal/storage/repository" @@ -28,80 +29,130 @@ func NewController(deviceControlRepo repository.DeviceControlRepo, deviceRepo re } } -// ControlRequest 设备控制请求结构体 -type ControlRequest struct { +// SwitchRequest 设备控制请求结构体 +type SwitchRequest struct { ParentID *uint `json:"parent_id"` // 区域主控ID DeviceType string `json:"device_type" binding:"required,oneof=fan water_curtain"` DeviceID string `json:"device_id" binding:"required"` Action string `json:"action" binding:"required,oneof=on off"` } +// SwitchResponseData 设备控制响应数据结构体 +type SwitchResponseData struct { + DeviceType string `json:"device_type"` + DeviceID string `json:"device_id"` + Action string `json:"action"` +} + // Switch 设备控制接口 func (c *Controller) Switch(ctx *gin.Context) { // 从上下文中获取用户信息 userValue, exists := ctx.Get("user") if !exists { - ctx.JSON(http.StatusUnauthorized, gin.H{"error": "无法获取用户信息"}) + controller.SendErrorResponse(ctx, http.StatusUnauthorized, "无法获取用户信息") return } user, ok := userValue.(*middleware.AuthUser) if !ok { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "用户信息格式错误"}) + controller.SendErrorResponse(ctx, http.StatusInternalServerError, "用户信息格式错误") return } - var req ControlRequest + var req SwitchRequest if err := ctx.ShouldBindJSON(&req); err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"}) + controller.SendErrorResponse(ctx, http.StatusBadRequest, "请求参数错误") return } - // 获取区域主控设备信息(如果提供了ParentID) - var location string - if req.ParentID != nil { - parentDevice, err := c.deviceRepo.FindByID(*req.ParentID) - if err != nil { - c.logger.Error("查找区域主控设备失败: " + err.Error()) - ctx.JSON(http.StatusBadRequest, gin.H{"error": "无效的区域主控ID"}) - return - } - location = parentDevice.Name - } else { - location = "未知区域" - } - // TODO: 实际的设备控制逻辑 // 这里暂时用TODO代替具体逻辑 // 创建设备控制记录 - control := &model.DeviceControl{ - UserID: user.ID, - Location: location, - DeviceType: model.DeviceType(req.DeviceType), - DeviceID: req.DeviceID, - Action: req.Action, - Status: "success", // 假设控制成功 - Result: "设备控制成功", - } - - if err := c.deviceControlRepo.Create(control); err != nil { + if err := c.createDeviceControlRecord( + user.ID, + req.DeviceID, + req.DeviceType, + req.Action, + "success", + "设备控制成功", + ); err != nil { c.logger.Error("创建设备控制记录失败: " + err.Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "设备控制失败"}) + controller.SendErrorResponse(ctx, http.StatusInternalServerError, "设备控制失败") return } - ctx.JSON(http.StatusOK, gin.H{ - "message": "设备控制成功", - "data": map[string]interface{}{ - "id": control.ID, - "location": control.Location, - "device_type": control.DeviceType, - "device_id": control.DeviceID, - "action": control.Action, - "status": control.Status, - "result": control.Result, - "created_at": control.CreatedAt, - }, - }) + data := SwitchResponseData{ + DeviceType: req.DeviceType, + DeviceID: req.DeviceID, + Action: req.Action, + } + + controller.SendSuccessResponse(ctx, "设备控制成功", data) +} + +// createDeviceControlRecord 创建设备控制记录 +func (c *Controller) createDeviceControlRecord(userID uint, deviceID, deviceType, action, status, result string) error { + // 获取设备信息 + device, err := c.deviceRepo.FindByIDString(deviceID) + if err != nil { + return err + } + + // 构建位置信息 + var location string + switch device.Type { + case model.DeviceTypeRelay: + // 如果设备本身就是中继设备 + location = device.Name + case model.DeviceTypePigPenController, model.DeviceTypeFeedMillController: + // 如果设备本身就是区域主控设备 + if device.ParentID != nil { + // 获取中继设备 + relayDevice, err := c.deviceRepo.FindByID(*device.ParentID) + if err != nil { + location = device.Name + } else { + location = relayDevice.Name + " -> " + device.Name + } + } else { + location = device.Name + } + default: + // 如果是普通设备(风机、水帘等) + if device.ParentID != nil { + // 获取区域主控设备 + parentDevice, err := c.deviceRepo.FindByID(*device.ParentID) + if err != nil { + location = "未知区域" + } else { + // 检查区域主控设备是否有上级设备(中继设备) + if parentDevice.ParentID != nil { + // 获取中继设备 + relayDevice, err := c.deviceRepo.FindByID(*parentDevice.ParentID) + if err != nil { + location = parentDevice.Name + } else { + location = relayDevice.Name + " -> " + parentDevice.Name + } + } else { + location = parentDevice.Name + } + } + } else { + location = "未知区域" + } + } + + control := &model.DeviceControl{ + UserID: userID, + Location: location, + DeviceType: model.DeviceType(deviceType), + DeviceID: deviceID, + Action: action, + Status: status, + Result: result, + } + + return c.deviceControlRepo.Create(control) } diff --git a/internal/controller/operation/operation.go b/internal/controller/operation/operation.go index cc30f7c..a274e7e 100644 --- a/internal/controller/operation/operation.go +++ b/internal/controller/operation/operation.go @@ -7,6 +7,7 @@ import ( "strconv" "git.huangwc.com/pig/pig-farm-controller/internal/api/middleware" + "git.huangwc.com/pig/pig-farm-controller/internal/controller" "git.huangwc.com/pig/pig-farm-controller/internal/logs" "git.huangwc.com/pig/pig-farm-controller/internal/model" "git.huangwc.com/pig/pig-farm-controller/internal/storage/repository" @@ -41,24 +42,24 @@ func (c *Controller) Create(ctx *gin.Context) { // 从上下文中获取用户信息 userValue, exists := ctx.Get("user") if !exists { - ctx.JSON(http.StatusUnauthorized, gin.H{"error": "无法获取用户信息"}) + controller.SendErrorResponse(ctx, http.StatusUnauthorized, "无法获取用户信息") return } user, ok := userValue.(*middleware.AuthUser) if !ok { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "用户信息格式错误"}) + controller.SendErrorResponse(ctx, http.StatusInternalServerError, "用户信息格式错误") return } var req CreateRequest if err := ctx.ShouldBindJSON(&req); err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"}) + controller.SendErrorResponse(ctx, http.StatusBadRequest, "请求参数错误") return } // 创建操作历史记录 - history := &model.OperationHistory{ + operation := &model.OperationHistory{ UserID: user.ID, Action: req.Action, Target: req.Target, @@ -67,30 +68,13 @@ func (c *Controller) Create(ctx *gin.Context) { Result: req.Result, } - if err := c.operationHistoryRepo.Create(history); err != nil { + if err := c.operationHistoryRepo.Create(operation); err != nil { c.logger.Error("创建操作历史记录失败: " + err.Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "创建操作历史记录失败"}) + controller.SendErrorResponse(ctx, http.StatusInternalServerError, "创建操作历史记录失败") return } - ctx.JSON(http.StatusOK, gin.H{ - "message": "操作历史记录创建成功", - "data": map[string]interface{}{ - "id": history.ID, - "action": history.Action, - "target": history.Target, - "parameters": history.Parameters, - "status": history.Status, - "result": history.Result, - "created_at": history.CreatedAt, - }, - }) -} - -// ListByUserRequest 按用户查询操作历史请求结构体 -type ListByUserRequest struct { - Page int `form:"page" binding:"required"` - Limit int `form:"limit" binding:"required"` + controller.SendSuccessResponse(ctx, "操作历史记录创建成功", nil) } // ListByUser 获取当前用户的所有操作历史记录 @@ -98,115 +82,77 @@ func (c *Controller) ListByUser(ctx *gin.Context) { // 从上下文中获取用户信息 userValue, exists := ctx.Get("user") if !exists { - ctx.JSON(http.StatusUnauthorized, gin.H{"error": "无法获取用户信息"}) + controller.SendErrorResponse(ctx, http.StatusUnauthorized, "无法获取用户信息") return } user, ok := userValue.(*middleware.AuthUser) if !ok { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "用户信息格式错误"}) + controller.SendErrorResponse(ctx, http.StatusInternalServerError, "用户信息格式错误") return } - // 解析查询参数 - var req ListByUserRequest - if err := ctx.ShouldBindQuery(&req); err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "查询参数错误"}) - return - } + // 获取分页参数 + page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1")) + limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10")) - if req.Page <= 0 { - req.Page = 1 + // 确保分页参数有效 + if page < 1 { + page = 1 } - - if req.Limit <= 0 || req.Limit > 100 { - req.Limit = 10 + if limit < 1 || limit > 100 { + limit = 10 } // 计算偏移量 - offset := (req.Page - 1) * req.Limit + offset := (page - 1) * limit - // 查询用户操作历史记录 - histories, err := c.operationHistoryRepo.FindByUserID(user.ID) + // 查询操作历史记录 + operations, err := c.operationHistoryRepo.ListByUserID(user.ID, offset, limit) if err != nil { - c.logger.Error("查询操作历史记录失败: " + err.Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "查询操作历史记录失败"}) + c.logger.Error("获取操作历史记录失败: " + err.Error()) + controller.SendErrorResponse(ctx, http.StatusInternalServerError, "获取操作历史记录失败") return } - // 分页处理 - start := offset - end := start + req.Limit - if start > len(histories) { - start = len(histories) - } - if end > len(histories) { - end = len(histories) - } - - pagedHistories := histories[start:end] - - ctx.JSON(http.StatusOK, gin.H{ - "message": "查询成功", - "data": map[string]interface{}{ - "histories": pagedHistories, - "page": req.Page, - "limit": req.Limit, - "total": len(histories), - }, - }) -} - -// GetRequest 获取单个操作历史记录请求结构体 -type GetRequest struct { - ID string `uri:"id" binding:"required"` + controller.SendSuccessResponse(ctx, "获取操作历史记录成功", operations) } // Get 获取单个操作历史记录 func (c *Controller) Get(ctx *gin.Context) { + // 获取操作历史记录ID + id, err := strconv.ParseUint(ctx.Param("id"), 10, 64) + if err != nil { + controller.SendErrorResponse(ctx, http.StatusBadRequest, "无效的操作历史记录ID") + return + } + + // 查询操作历史记录 + operation, err := c.operationHistoryRepo.FindByID(uint(id)) + if err != nil { + c.logger.Error("获取操作历史记录失败: " + err.Error()) + controller.SendErrorResponse(ctx, http.StatusInternalServerError, "获取操作历史记录失败") + return + } + // 从上下文中获取用户信息 userValue, exists := ctx.Get("user") if !exists { - ctx.JSON(http.StatusUnauthorized, gin.H{"error": "无法获取用户信息"}) + controller.SendErrorResponse(ctx, http.StatusUnauthorized, "无法获取用户信息") return } user, ok := userValue.(*middleware.AuthUser) if !ok { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "用户信息格式错误"}) + controller.SendErrorResponse(ctx, http.StatusInternalServerError, "用户信息格式错误") return } - // 解析路径参数 - var req GetRequest - if err := ctx.ShouldBindUri(&req); err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "路径参数错误"}) + // 检查操作历史记录是否属于当前用户 + if operation.UserID != user.ID { + controller.SendErrorResponse(ctx, http.StatusForbidden, "无权访问该操作历史记录") return } - // 将ID转换为整数 - id, err := strconv.ParseUint(req.ID, 10, 32) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "ID格式错误"}) - return - } - - // 查询操作历史记录 - history, err := c.operationHistoryRepo.FindByID(uint(id)) - if err != nil { - c.logger.Error("查询操作历史记录失败: " + err.Error()) - ctx.JSON(http.StatusNotFound, gin.H{"error": "操作历史记录不存在"}) - return - } - - // 检查是否是当前用户的记录 - if history.UserID != user.ID { - ctx.JSON(http.StatusForbidden, gin.H{"error": "无权访问该记录"}) - return - } - - ctx.JSON(http.StatusOK, gin.H{ - "message": "查询成功", - "data": history, - }) + controller.SendSuccessResponse(ctx, "获取操作历史记录成功", operation) } diff --git a/internal/controller/response.go b/internal/controller/response.go new file mode 100644 index 0000000..fd6bdf2 --- /dev/null +++ b/internal/controller/response.go @@ -0,0 +1,41 @@ +// Package controller 提供控制器层的公共功能 +// 包含公共响应结构体和处理函数等 +package controller + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// APIResponse 通用API响应结构体 +type APIResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +// SuccessResponseData 成功响应数据结构体 +type SuccessResponseData struct { + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +// sendSuccessResponse 发送成功响应的公共函数 +func SendSuccessResponse(ctx *gin.Context, message string, data interface{}) { + response := APIResponse{ + Code: http.StatusOK, + Message: message, + Data: data, + } + ctx.JSON(http.StatusOK, response) +} + +// sendErrorResponse 发送错误响应的公共函数 +func SendErrorResponse(ctx *gin.Context, code int, message string) { + response := APIResponse{ + Code: code, + Message: message, + } + ctx.JSON(code, response) +} diff --git a/internal/controller/user/user.go b/internal/controller/user/user.go index 5d90e9d..e75f372 100644 --- a/internal/controller/user/user.go +++ b/internal/controller/user/user.go @@ -6,6 +6,7 @@ import ( "net/http" "git.huangwc.com/pig/pig-farm-controller/internal/api/middleware" + "git.huangwc.com/pig/pig-farm-controller/internal/controller" "git.huangwc.com/pig/pig-farm-controller/internal/logs" "git.huangwc.com/pig/pig-farm-controller/internal/storage/repository" "github.com/gin-gonic/gin" @@ -26,101 +27,104 @@ func NewController(userRepo repository.UserRepo) *Controller { } } -// RegisterRequest 注册请求结构体 +// RegisterRequest 用户注册请求结构体 type RegisterRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } -// RegisterResponse 注册响应结构体 -type RegisterResponse struct { - ID uint `json:"id"` - Username string `json:"username"` - CreatedAt string `json:"created_at"` -} - -// Register 用户注册 -func (c *Controller) Register(ctx *gin.Context) { - var req RegisterRequest - if err := ctx.ShouldBindJSON(&req); err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"}) - return - } - - user, err := c.userRepo.CreateUser(req.Username, req.Password) - if err != nil { - c.logger.Error("创建用户失败: " + err.Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "创建用户失败"}) - return - } - - response := RegisterResponse{ - ID: user.ID, - Username: user.Username, - CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), - } - - ctx.JSON(http.StatusOK, gin.H{ - "message": "用户创建成功", - "user": response, - }) -} - -// LoginRequest 登录请求结构体 +// LoginRequest 用户登录请求结构体 type LoginRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } -// LoginResponse 登录响应结构体 -type LoginResponse struct { - ID uint `json:"id"` - Username string `json:"username"` - Token string `json:"token"` - CreatedAt string `json:"created_at"` +// RegisterResponseData 用户注册响应数据 +type RegisterResponseData struct { + ID uint `json:"id"` + Username string `json:"username"` } -// Login 用户登录 +// LoginResponseData 用户登录响应数据 +type LoginResponseData struct { + ID uint `json:"id"` + Username string `json:"username"` + Token string `json:"token"` +} + +// Register 用户注册接口 +func (c *Controller) Register(ctx *gin.Context) { + var req RegisterRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + controller.SendErrorResponse(ctx, http.StatusBadRequest, "请求参数错误") + return + } + + // 检查用户名是否已存在 + _, err := c.userRepo.FindByUsername(req.Username) + if err == nil { + controller.SendErrorResponse(ctx, http.StatusBadRequest, "用户名已存在") + return + } + + // 对密码进行哈希处理 + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + c.logger.Error("密码哈希处理失败: " + err.Error()) + controller.SendErrorResponse(ctx, http.StatusInternalServerError, "用户注册失败") + return + } + + // 创建用户 + user, err := c.userRepo.CreateUser(req.Username, string(hashedPassword)) + if err != nil { + c.logger.Error("创建用户失败: " + err.Error()) + controller.SendErrorResponse(ctx, http.StatusInternalServerError, "用户注册失败") + return + } + + data := RegisterResponseData{ + ID: user.ID, + Username: user.Username, + } + + controller.SendSuccessResponse(ctx, "用户注册成功", data) +} + +// Login 用户登录接口 func (c *Controller) Login(ctx *gin.Context) { var req LoginRequest if err := ctx.ShouldBindJSON(&req); err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"}) + controller.SendErrorResponse(ctx, http.StatusBadRequest, "请求参数错误") return } // 查找用户 user, err := c.userRepo.FindByUsername(req.Username) if err != nil { - c.logger.Error("查找用户失败: " + err.Error()) - ctx.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"}) + controller.SendErrorResponse(ctx, http.StatusUnauthorized, "用户名或密码错误") return } // 验证密码 - err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)) - if err != nil { - ctx.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"}) + if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil { + controller.SendErrorResponse(ctx, http.StatusUnauthorized, "用户名或密码错误") return } - // 生成JWT访问令牌 - authMiddleware := middleware.NewAuthMiddleware(c.userRepo) - token, err := authMiddleware.GenerateToken(user.ID, user.Username) + // 生成JWT token + token, err := middleware.NewAuthMiddleware(c.userRepo).GenerateToken(user.ID, user.Username) if err != nil { - c.logger.Error("生成令牌失败: " + err.Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "登录失败"}) + c.logger.Error("生成JWT token失败: " + err.Error()) + controller.SendErrorResponse(ctx, http.StatusInternalServerError, "登录失败") return } - response := LoginResponse{ - ID: user.ID, - Username: user.Username, - Token: token, - CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), + data := LoginResponseData{ + ID: user.ID, + Username: user.Username, + Token: token, } - ctx.JSON(http.StatusOK, gin.H{ - "message": "登录成功", - "user": response, - }) + controller.SendSuccessResponse(ctx, "登录成功", data) } diff --git a/internal/model/device.go b/internal/model/device.go index 649ff44..5780e61 100644 --- a/internal/model/device.go +++ b/internal/model/device.go @@ -23,6 +23,9 @@ const ( // DeviceTypeFeedMillController 做料车间主控 DeviceTypeFeedMillController DeviceType = "feed_mill_controller" + + // DeviceTypeRelay 中继设备 + DeviceTypeRelay DeviceType = "relay" ) // Device 代表设备信息 diff --git a/internal/storage/repository/device.go b/internal/storage/repository/device.go index 758335c..9ad50c5 100644 --- a/internal/storage/repository/device.go +++ b/internal/storage/repository/device.go @@ -3,6 +3,8 @@ package repository import ( + "strconv" + "git.huangwc.com/pig/pig-farm-controller/internal/model" "gorm.io/gorm" ) @@ -15,6 +17,9 @@ type DeviceRepo interface { // FindByID 根据ID查找设备 FindByID(id uint) (*model.Device, error) + // FindByIDString 根据ID字符串查找设备 + FindByIDString(id string) (*model.Device, error) + // FindByParentID 根据上级设备ID查找设备 FindByParentID(parentID uint) ([]*model.Device, error) @@ -83,6 +88,21 @@ func (r *deviceRepo) FindByID(id uint) (*model.Device, error) { return &device, nil } +// FindByIDString 根据ID字符串查找设备 +func (r *deviceRepo) FindByIDString(id string) (*model.Device, error) { + deviceID, err := strconv.ParseUint(id, 10, 64) + if err != nil { + return nil, err + } + + var device model.Device + result := r.db.First(&device, deviceID) + if result.Error != nil { + return nil, result.Error + } + return &device, nil +} + // FindByParentID 根据上级设备ID查找设备 func (r *deviceRepo) FindByParentID(parentID uint) ([]*model.Device, error) { var devices []*model.Device diff --git a/internal/storage/repository/operation_history.go b/internal/storage/repository/operation_history.go index 379fabf..e4c7542 100644 --- a/internal/storage/repository/operation_history.go +++ b/internal/storage/repository/operation_history.go @@ -20,6 +20,9 @@ type OperationHistoryRepo interface { // List 获取操作历史记录列表(分页) List(offset, limit int) ([]*model.OperationHistory, error) + + // ListByUserID 根据用户ID获取操作历史记录列表(分页) + ListByUserID(userID uint, offset, limit int) ([]*model.OperationHistory, error) } // operationHistoryRepo 操作历史仓库实现 @@ -69,3 +72,13 @@ func (r *operationHistoryRepo) List(offset, limit int) ([]*model.OperationHistor } return histories, nil } + +// ListByUserID 根据用户ID获取操作历史记录列表(分页) +func (r *operationHistoryRepo) ListByUserID(userID uint, offset, limit int) ([]*model.OperationHistory, error) { + var histories []*model.OperationHistory + result := r.db.Where("user_id = ?", userID).Offset(offset).Limit(limit).Order("created_at DESC").Find(&histories) + if result.Error != nil { + return nil, result.Error + } + return histories, nil +}