package handler import ( "fmt" "net/http" "strconv" "time" "github.com/gin-gonic/gin" "github.com/user-management-system/internal/api/middleware" "github.com/user-management-system/internal/domain" "github.com/user-management-system/internal/pagination" "github.com/user-management-system/internal/service" ) // DeviceHandler handles device management requests type DeviceHandler struct { deviceService *service.DeviceService } // NewDeviceHandler creates a new DeviceHandler func NewDeviceHandler(deviceService *service.DeviceService) *DeviceHandler { return &DeviceHandler{deviceService: deviceService} } func (h *DeviceHandler) currentActor(c *gin.Context) (int64, bool, bool) { userID, ok := getUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"}) return 0, false, false } return userID, middleware.IsAdmin(c), true } // CreateDevice 创建设备 // @Summary 创建设备记录 // @Description 当前用户创建设备记录 // @Tags 设备管理 // @Accept json // @Produce json // @Security BearerAuth // @Param request body service.CreateDeviceRequest true "设备信息" // @Success 201 {object} Response{data=domain.Device} "设备创建成功" // @Failure 401 {object} Response "未认证" // @Router /api/v1/devices [post] func (h *DeviceHandler) CreateDevice(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"}) return } var req service.CreateDeviceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()}) return } device, err := h.deviceService.CreateDevice(c.Request.Context(), userID, &req) if err != nil { handleError(c, err) return } c.JSON(http.StatusCreated, gin.H{ "code": 0, "message": "success", "data": device, }) } // GetMyDevices 获取我的设备列表 // @Summary 获取当前用户的设备列表 // @Description 获取当前用户的所有设备记录 // @Tags 设备管理 // @Produce json // @Security BearerAuth // @Param page query int false "页码" // @Param page_size query int false "每页数量" // @Success 200 {object} Response{data=DeviceListResponse} "设备列表" // @Failure 401 {object} Response "未认证" // @Router /api/v1/devices [get] func (h *DeviceHandler) GetMyDevices(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"}) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", strconv.Itoa(pagination.DefaultPageSize))) if pageSize < 1 || pageSize > pagination.MaxPageSize { pageSize = pagination.DefaultPageSize } devices, total, err := h.deviceService.GetUserDevices(c.Request.Context(), userID, page, pageSize) if err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": gin.H{ "items": devices, "total": total, "page": page, "page_size": pageSize, }, }) } // GetDevice 获取设备详情 // @Summary 获取设备详情 // @Description 根据ID获取设备详细信息 // @Tags 设备管理 // @Produce json // @Security BearerAuth // @Param id path int true "设备ID" // @Success 200 {object} Response{data=domain.Device} "设备信息" // @Failure 404 {object} Response "设备不存在" // @Router /api/v1/devices/{id} [get] func (h *DeviceHandler) GetDevice(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid device id"}) return } actorUserID, isAdmin, ok := h.currentActor(c) if !ok { return } device, err := h.deviceService.GetDeviceForActor(c.Request.Context(), actorUserID, id, isAdmin) if err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": device, }) } // UpdateDevice 更新设备 // @Summary 更新设备信息 // @Description 更新设备的基本信息 // @Tags 设备管理 // @Accept json // @Produce json // @Security BearerAuth // @Param id path int true "设备ID" // @Param request body service.UpdateDeviceRequest true "更新信息" // @Success 200 {object} Response{data=domain.Device} "更新成功" // @Failure 400 {object} Response "请求参数错误" // @Failure 404 {object} Response "设备不存在" // @Router /api/v1/devices/{id} [put] func (h *DeviceHandler) UpdateDevice(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid device id"}) return } var req service.UpdateDeviceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()}) return } actorUserID, isAdmin, ok := h.currentActor(c) if !ok { return } device, err := h.deviceService.UpdateDeviceForActor(c.Request.Context(), actorUserID, id, isAdmin, &req) if err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": device, }) } // DeleteDevice 删除设备 // @Summary 删除设备 // @Description 删除设备记录 // @Tags 设备管理 // @Produce json // @Security BearerAuth // @Param id path int true "设备ID" // @Success 200 {object} Response "删除成功" // @Failure 404 {object} Response "设备不存在" // @Router /api/v1/devices/{id} [delete] func (h *DeviceHandler) DeleteDevice(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid device id"}) return } actorUserID, isAdmin, ok := h.currentActor(c) if !ok { return } if err := h.deviceService.DeleteDeviceForActor(c.Request.Context(), actorUserID, id, isAdmin); err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "device deleted", }) } // UpdateDeviceStatus 更新设备状态 // @Summary 更新设备状态 // @Description 更新设备状态(active/inactive) // @Tags 设备管理 // @Accept json // @Produce json // @Security BearerAuth // @Param id path int true "设备ID" // @Param request body UpdateDeviceStatusRequest true "状态信息" // @Success 200 {object} Response "状态更新成功" // @Failure 400 {object} Response "无效的状态值" // @Failure 404 {object} Response "设备不存在" // @Router /api/v1/devices/{id}/status [put] func (h *DeviceHandler) UpdateDeviceStatus(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid device id"}) return } var req struct { Status string `json:"status" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()}) return } var status domain.DeviceStatus switch req.Status { case "active", "1": status = domain.DeviceStatusActive case "inactive", "0": status = domain.DeviceStatusInactive default: c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid status"}) return } actorUserID, isAdmin, ok := h.currentActor(c) if !ok { return } if err := h.deviceService.UpdateDeviceStatusForActor(c.Request.Context(), actorUserID, id, isAdmin, status); err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "status updated", }) } // GetUserDevices 获取指定用户的设备列表 // @Summary 获取用户设备列表 // @Description 获取指定用户的设备列表(仅本人或管理员) // @Tags 设备管理 // @Produce json // @Security BearerAuth // @Param id path int true "用户ID" // @Param page query int false "页码" // @Param page_size query int false "每页数量" // @Success 200 {object} Response{data=DeviceListResponse} "设备列表" // @Failure 403 {object} Response "无权限" // @Router /api/v1/users/{id}/devices [get] func (h *DeviceHandler) GetUserDevices(c *gin.Context) { // IDOR 修复:检查当前用户是否有权限查看指定用户的设备 currentUserID, ok := getUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"}) return } // 检查是否为管理员 isAdmin := middleware.IsAdmin(c) userIDParam := c.Param("id") userID, err := strconv.ParseInt(userIDParam, 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid user id"}) return } // 非管理员只能查看自己的设备 if !isAdmin && userID != currentUserID { c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "无权访问该用户的设备列表"}) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", strconv.Itoa(pagination.DefaultPageSize))) if pageSize < 1 || pageSize > pagination.MaxPageSize { pageSize = pagination.DefaultPageSize } devices, total, err := h.deviceService.GetUserDevices(c.Request.Context(), userID, page, pageSize) if err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": gin.H{ "items": devices, "total": total, "page": page, "page_size": pageSize, }, }) } // GetAllDevices 获取所有设备列表 // @Summary 获取所有设备列表 // @Description 获取所有设备列表(仅管理员),支持游标分页和偏移分页 // @Tags 设备管理 // @Produce json // @Security BearerAuth // @Param cursor query string false "游标分页游标" // @Param size query int false "每页数量(游标模式)" // @Param page query int false "页码" // @Param page_size query int false "每页数量" // @Success 200 {object} Response{data=DeviceListResponse} "设备列表" // @Failure 403 {object} Response "无权限" // @Router /api/v1/admin/devices [get] func (h *DeviceHandler) GetAllDevices(c *gin.Context) { var req service.GetAllDevicesRequest if err := c.ShouldBindQuery(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()}) return } // Use cursor-based pagination when cursor is provided if req.Cursor != "" || req.Size > 0 { result, err := h.deviceService.GetAllDevicesCursor(c.Request.Context(), &req) if err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": result, }) return } // Fallback to legacy offset-based pagination devices, total, err := h.deviceService.GetAllDevices(c.Request.Context(), &req) if err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": gin.H{ "items": devices, "total": total, "page": req.Page, "page_size": req.PageSize, }, }) } // TrustDeviceRequest 信任设备请求 type TrustDeviceRequest struct { TrustDuration string `json:"trust_duration"` // 信任持续时间,如 "30d" 表示30天 } // TrustDevice 设置设备为信任设备 // @Summary 设置设备为信任设备 // @Description 将指定设备设置为信任设备,在信任期内免二次验证 // @Tags 设备管理 // @Accept json // @Produce json // @Security BearerAuth // @Param id path int true "设备ID" // @Param request body TrustDeviceRequest true "信任配置" // @Success 200 {object} Response "设置成功" // @Failure 404 {object} Response "设备不存在" // @Router /api/v1/devices/{id}/trust [post] func (h *DeviceHandler) TrustDevice(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid device id"}) return } var req TrustDeviceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()}) return } // 解析信任持续时间 trustDuration := parseDuration(req.TrustDuration) actorUserID, isAdmin, ok := h.currentActor(c) if !ok { return } if err := h.deviceService.TrustDeviceForActor(c.Request.Context(), actorUserID, id, isAdmin, trustDuration); err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "device trusted", }) } // TrustDeviceByDeviceID 根据设备标识设置设备为信任状态 // @Summary 根据设备标识设置信任 // @Description 根据设备唯一标识字符串设置设备为信任状态 // @Tags 设备管理 // @Accept json // @Produce json // @Security BearerAuth // @Param deviceId path string true "设备唯一标识" // @Param request body TrustDeviceRequest true "信任配置" // @Success 200 {object} Response "设置成功" // @Failure 401 {object} Response "未认证" // @Router /api/v1/devices/trust/{deviceId} [post] func (h *DeviceHandler) TrustDeviceByDeviceID(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"}) return } deviceID := c.Param("deviceId") if deviceID == "" { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid device id"}) return } var req TrustDeviceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()}) return } // 解析信任持续时间 trustDuration := parseDuration(req.TrustDuration) if err := h.deviceService.TrustDeviceByDeviceID(c.Request.Context(), userID, deviceID, trustDuration); err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "device trusted", }) } // UntrustDevice 取消设备信任状态 // @Summary 取消设备信任 // @Description 取消设备的信任状态 // @Tags 设备管理 // @Produce json // @Security BearerAuth // @Param id path int true "设备ID" // @Success 200 {object} Response "取消成功" // @Failure 404 {object} Response "设备不存在" // @Router /api/v1/devices/{id}/trust [delete] func (h *DeviceHandler) UntrustDevice(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid device id"}) return } actorUserID, isAdmin, ok := h.currentActor(c) if !ok { return } if err := h.deviceService.UntrustDeviceForActor(c.Request.Context(), actorUserID, id, isAdmin); err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "device untrusted", }) } // GetMyTrustedDevices 获取我的信任设备列表 // @Summary 获取信任设备列表 // @Description 获取当前用户的信任设备列表 // @Tags 设备管理 // @Produce json // @Security BearerAuth // @Success 200 {object} Response{data=[]domain.Device} "信任设备列表" // @Failure 401 {object} Response "未认证" // @Router /api/v1/devices/trusted [get] func (h *DeviceHandler) GetMyTrustedDevices(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"}) return } devices, err := h.deviceService.GetTrustedDevices(c.Request.Context(), userID) if err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": devices, }) } // LogoutAllOtherDevices 登出所有其他设备 // @Summary 登出其他设备 // @Description 登出当前用户除指定设备外的所有其他设备 // @Tags 设备管理 // @Produce json // @Security BearerAuth // @Param X-Device-ID header string true "当前设备ID" // @Success 200 {object} Response "登出成功" // @Failure 400 {object} Response "无效的设备ID" // @Failure 401 {object} Response "未认证" // @Router /api/v1/devices/logout-others [post] func (h *DeviceHandler) LogoutAllOtherDevices(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"}) return } // 从请求中获取当前设备ID currentDeviceIDStr := c.GetHeader("X-Device-ID") currentDeviceID, err := strconv.ParseInt(currentDeviceIDStr, 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid current device id"}) return } if err := h.deviceService.LogoutAllOtherDevices(c.Request.Context(), userID, currentDeviceID); err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "all other devices logged out", }) } // parseDuration 解析duration字符串,如 "30d" -> 30天的time.Duration func parseDuration(s string) time.Duration { if s == "" { return 0 } // 简单实现,支持 d(天)和h(小时) var d int var h int _, _ = d, h switch s[len(s)-1] { case 'd': d = 1 _, _ = fmt.Sscanf(s[:len(s)-1], "%d", &d) return time.Duration(d) * 24 * time.Hour case 'h': _, _ = fmt.Sscanf(s[:len(s)-1], "%d", &h) return time.Duration(h) * time.Hour } return 0 }