diff --git a/backend/docs/demand/2026.1.13-用户端设备汇总.md b/backend/docs/demand/2026.1.13-用户端设备汇总.md new file mode 100644 index 0000000..65b3949 --- /dev/null +++ b/backend/docs/demand/2026.1.13-用户端设备汇总.md @@ -0,0 +1,179 @@ +# 用户端设备汇总接口设计文档 + +## 需求概述 + +提供用户端设备汇总统计接口,返回当前登录客户的设备总数、在线设备数、离线设备数。 + +## 接口路径 + +``` +GET /api/cu/devices/summary +``` + +## 认证要求 + +- 需要 Bearer Token 认证(C端用户JWT) +- 基于当前登录用户的客户ID进行统计 + +## 请求参数 + +无查询参数 + +## 响应数据结构 + +### 成功响应 + +```json +{ + "code": "000000", + "message": "成功", + "data": { + "total_devices": 100, + "online_devices": 85, + "offline_devices": 15 + } +} +``` + +### 响应字段说明 + +| 字段 | 类型 | 必填 | 描述 | +|------|------|------|------| +| total_devices | int64 | 是 | 设备总数(活跃许可证数) | +| online_devices | int64 | 是 | 在线设备数 | +| offline_devices | int64 | 是 | 离线设备数 | + +### 在线状态判断逻辑 + +参考现有代码实现: +- 通过 `last_heartbeat` 时间戳判断设备是否在线 +- 心跳超时时间从配置获取,默认5分钟(300秒) +- 如果 `last_heartbeat + heartbeat_timeout > 当前时间`,则设备在线 + +### 业务规则 + +- 统计范围:当前登录用户的客户ID下的所有活跃设备(`status = 'active'` 且 `deleted_at IS NULL`) +- 权限控制:只能查看自己客户ID下的设备数据 +- 数据一致性:`total_devices = online_devices + offline_devices` + +## 错误响应 + +| 错误码 | HTTP状态码 | 描述 | +|--------|------------|------| +| 500011 | 401 | 认证令牌无效 | +| 500012 | 401 | 认证令牌已过期 | +| 500010 | 401 | 认证令牌缺失 | +| 900004 | 500 | 服务器内部错误 | + +## Swagger 文档 + +```go +// GetDeviceSummary 获取设备汇总统计 +// @Summary 获取设备汇总统计 +// @Description 获取当前登录用户的设备汇总统计信息(总数、在线数、离线数) +// @Tags 客户设备管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} models.APIResponse{data=models.DeviceSummaryResponse} "获取成功" +// @Failure 401 {object} models.ErrorResponse "未认证" +// @Failure 500 {object} models.ErrorResponse "服务器内部错误" +// @Router /api/cu/devices/summary [get] +``` + +## 技术实现方案 + +### 1. 新增响应模型(models/license.go) + +```go +// DeviceSummaryResponse 设备汇总响应结构 +type DeviceSummaryResponse struct { + TotalDevices int64 `json:"total_devices"` // 设备总数 + OnlineDevices int64 `json:"online_devices"` // 在线设备数 + OfflineDevices int64 `json:"offline_devices"` // 离线设备数 +} +``` + +### 2. 服务层接口扩展(service/interfaces.go) + +在 `CuDeviceService` 接口中添加: + +```go +// 获取设备汇总统计 +GetDeviceSummary(ctx context.Context, customerID string) (*models.DeviceSummaryResponse, error) +``` + +### 3. Repository层接口扩展(repository/interfaces.go) + +在 `LicenseRepository` 接口中添加: + +```go +// GetCustomerDeviceSummary 获取客户设备汇总统计 +GetCustomerDeviceSummary(ctx context.Context, customerID string) (*models.DeviceSummaryResponse, error) +``` + +### 4. 处理器扩展(handlers/cu_device_handler.go) + +添加新的处理器方法: + +```go +func (h *CuDeviceHandler) GetDeviceSummary(c *gin.Context) { + // 从JWT Token中获取用户ID和客户ID + claims := c.MustGet("cu_user").(*utils.CuClaims) + + // 调用服务层 + result, err := h.cuDeviceService.GetDeviceSummary(c.Request.Context(), claims.CustomerID) + if err != nil { + // err已经是i18n.I18nError,直接使用 + i18nErr, ok := err.(*i18n.I18nError) + if ok { + c.JSON(i18nErr.HttpCode, models.ErrorResponse{ + Code: i18nErr.Code, + Message: i18nErr.Message, + Timestamp: getCurrentTimestamp(), + }) + return + } + // 如果不是i18n错误,使用通用错误 + lang := middleware.GetLanguage(c) + status, errCode, message := i18n.NewI18nErrorResponse("900004", lang, err.Error()) + c.JSON(status, models.ErrorResponse{ + Code: errCode, + Message: message + ": " + err.Error(), + Timestamp: getCurrentTimestamp(), + }) + return + } + + lang := middleware.GetLanguage(c) + successMessage := i18n.GetI18nErrorMessage("000000", lang) + c.JSON(http.StatusOK, models.APIResponse{ + Code: "000000", + Message: successMessage, + Data: result, + }) +} +``` + +### 5. 路由配置(routes/router.go) + +在 cuAuth 路由组中添加: + +```go +cuAuth.GET("/devices/summary", cuDeviceHandler.GetDeviceSummary) +``` + +## 数据库查询逻辑 + +基于现有 `licenses` 表结构,统计逻辑: + +1. **设备总数**:查询 `customer_id = ? AND status = 'active' AND deleted_at IS NULL` 的记录数 +2. **在线设备数**:在总数基础上,筛选 `last_heartbeat > (NOW() - INTERVAL heartbeat_timeout SECOND)` 的记录数 +3. **离线设备数**:总数减去在线设备数 + +## 性能优化建议 + +- 建议对该接口使用缓存,避免频繁的数据库查询 +- 可以使用 Redis 缓存,缓存时间建议为 1-5 分钟 +- 缓存键设计:`device_summary:{customer_id}` + diff --git a/backend/docs/demand/2026.1.13-用户订单汇总接口.md b/backend/docs/demand/2026.1.13-用户订单汇总接口.md new file mode 100644 index 0000000..462c939 --- /dev/null +++ b/backend/docs/demand/2026.1.13-用户订单汇总接口.md @@ -0,0 +1,167 @@ +# 用户订单汇总接口设计文档 + +## 需求概述 + +提供用户订单汇总统计接口,返回当前登录客户的订单总数、待支付订单数、已支付订单数。 + +## 接口路径 + +``` +GET /api/cu/orders/summary +``` + +## 认证要求 + +- 需要 Bearer Token 认证(C端用户JWT) +- 基于当前登录用户的客户ID进行统计 + +## 请求参数 + +无查询参数 + +## 响应数据结构 + +### 成功响应 + +```json +{ + "code": "000000", + "message": "成功", + "data": { + "total_orders": 100, + "pending_orders": 15, + "paid_orders": 85 + } +} +``` + +### 响应字段说明 + +| 字段 | 类型 | 必填 | 描述 | +|------|------|------|------| +| total_orders | int64 | 是 | 订单总数 | +| pending_orders | int64 | 是 | 待支付订单数 | +| paid_orders | int64 | 是 | 已支付订单数 | + +## 业务规则 + +- 统计范围:当前登录用户的客户ID下的所有订单(包含已删除订单) +- 订单状态: + - `pending`: 待支付订单 + - `paid`: 已支付订单 +- 数据一致性:`total_orders = pending_orders + paid_orders` + +## 错误响应 + +| 错误码 | HTTP状态码 | 描述 | +|--------|------------|------| +| 500011 | 401 | 认证令牌无效 | +| 500012 | 401 | 认证令牌已过期 | +| 500010 | 401 | 认证令牌缺失 | +| 900004 | 500 | 服务器内部错误 | + +## Swagger 文档 + +```go +// GetOrderSummary 获取订单汇总统计 +// @Summary 获取订单汇总统计 +// @Description 获取当前登录用户的订单汇总统计信息(总数、待支付数、已支付数) +// @Tags 客户订单管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} models.APIResponse{data=models.OrderSummaryResponse} "获取成功" +// @Failure 401 {object} models.ErrorResponse "未认证" +// @Failure 500 {object} models.ErrorResponse "服务器内部错误" +// @Router /api/cu/orders/summary [get] +``` + +## 技术实现方案 + +### 1. 新增响应模型(models/cu_order.go) + +```go +// OrderSummaryResponse 订单汇总响应结构 +type OrderSummaryResponse struct { + TotalOrders int64 `json:"total_orders"` // 订单总数 + PendingOrders int64 `json:"pending_orders"` // 待支付订单数 + PaidOrders int64 `json:"paid_orders"` // 已支付订单数 +} +``` + +### 2. 服务层接口扩展(service/interfaces.go) + +在 `CuOrderService` 接口中添加: + +```go +// 获取订单汇总统计 +GetOrderSummary(ctx context.Context, customerID string) (*models.OrderSummaryResponse, error) +``` + +### 3. Repository层接口扩展(repository/interfaces.go) + +在 `CuOrderRepository` 接口中添加: + +```go +// GetCustomerOrderSummary 获取客户订单汇总统计 +GetCustomerOrderSummary(ctx context.Context, customerID string) (*models.OrderSummaryResponse, error) +``` + +### 4. 处理器扩展(handlers/cu_order_handler.go) + +添加新的处理器方法: + +```go +func (h *CuOrderHandler) GetOrderSummary(c *gin.Context) { + // 从JWT Token中获取用户ID和客户ID + claims := c.MustGet("cu_user").(*utils.CuClaims) + + // 调用服务层 + result, err := h.cuOrderService.GetOrderSummary(c.Request.Context(), claims.CustomerID) + if err != nil { + // err已经是i18n.I18nError,直接使用 + i18nErr, ok := err.(*i18n.I18nError) + if ok { + c.JSON(i18nErr.HttpCode, models.ErrorResponse{ + Code: i18nErr.Code, + Message: i18nErr.Message, + Timestamp: getCurrentTimestamp(), + }) + return + } + // 如果不是i18n错误,使用通用错误 + lang := middleware.GetLanguage(c) + status, errCode, message := i18n.NewI18nErrorResponse("900004", lang, err.Error()) + c.JSON(status, models.ErrorResponse{ + Code: errCode, + Message: message + ": " + err.Error(), + Timestamp: getCurrentTimestamp(), + }) + return + } + + lang := middleware.GetLanguage(c) + successMessage := i18n.GetI18nErrorMessage("000000", lang) + c.JSON(http.StatusOK, models.APIResponse{ + Code: "000000", + Message: successMessage, + Data: result, + }) +} +``` + +### 5. 路由配置(routes/router.go) + +在 cuAuth 路由组中添加: + +```go +cuAuth.GET("/orders/summary", cuOrderHandler.GetOrderSummary) +``` + +## 数据库查询逻辑 + +基于现有 `cu_orders` 表结构,统计逻辑: + +1. **订单总数**:查询 `customer_id = ?` 的记录数(包含所有状态) +2. **待支付订单数**:查询 `customer_id = ? AND status = 'pending'` 的记录数 +3. **已支付订单数**:查询 `customer_id = ? AND status = 'paid'` 的记录数 \ No newline at end of file diff --git a/backend/docs/prompt.md b/backend/docs/prompt.md new file mode 100644 index 0000000..a08197f --- /dev/null +++ b/backend/docs/prompt.md @@ -0,0 +1,5 @@ +# 提示词 + +## 接口设计提示词 + +请参考至少三个当前已经实现的接口,熟悉接口设计规范,然后设计新的接口。 \ No newline at end of file diff --git a/backend/internal/api/handlers/cu_device_handler.go b/backend/internal/api/handlers/cu_device_handler.go index 676ae62..4de2073 100644 --- a/backend/internal/api/handlers/cu_device_handler.go +++ b/backend/internal/api/handlers/cu_device_handler.go @@ -152,3 +152,51 @@ func (h *CuDeviceHandler) UnbindDevice(c *gin.Context) { Data: nil, }) } + +// GetDeviceSummary 获取设备汇总统计 +// @Summary 获取设备汇总统计 +// @Description 获取当前登录用户的设备汇总统计信息(总数、在线数、离线数) +// @Tags 客户设备管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} models.APIResponse{data=models.DeviceSummaryResponse} "获取成功" +// @Failure 401 {object} models.ErrorResponse "未认证" +// @Failure 500 {object} models.ErrorResponse "服务器内部错误" +// @Router /api/cu/devices/summary [get] +func (h *CuDeviceHandler) GetDeviceSummary(c *gin.Context) { + // 从JWT Token中获取用户ID和客户ID + claims := c.MustGet("cu_user").(*utils.CuClaims) + + // 调用服务层 + result, err := h.cuDeviceService.GetDeviceSummary(c.Request.Context(), claims.CustomerID) + if err != nil { + // err已经是i18n.I18nError,直接使用 + i18nErr, ok := err.(*i18n.I18nError) + if ok { + c.JSON(i18nErr.HttpCode, models.ErrorResponse{ + Code: i18nErr.Code, + Message: i18nErr.Message, + Timestamp: getCurrentTimestamp(), + }) + return + } + // 如果不是i18n错误,使用通用错误 + lang := middleware.GetLanguage(c) + status, errCode, message := i18n.NewI18nErrorResponse("900004", lang, err.Error()) + c.JSON(status, models.ErrorResponse{ + Code: errCode, + Message: message + ": " + err.Error(), + Timestamp: getCurrentTimestamp(), + }) + return + } + + lang := middleware.GetLanguage(c) + successMessage := i18n.GetI18nErrorMessage("000000", lang) + c.JSON(http.StatusOK, models.APIResponse{ + Code: "000000", + Message: successMessage, + Data: result, + }) +} diff --git a/backend/internal/api/handlers/cu_order_handler.go b/backend/internal/api/handlers/cu_order_handler.go index 4f8b5c0..e5d3337 100644 --- a/backend/internal/api/handlers/cu_order_handler.go +++ b/backend/internal/api/handlers/cu_order_handler.go @@ -8,6 +8,7 @@ import ( "license-manager/internal/models" "license-manager/internal/service" "license-manager/pkg/i18n" + "license-manager/pkg/utils" "github.com/gin-gonic/gin" ) @@ -430,3 +431,51 @@ func (h *CuOrderHandler) GetUserOrders(c *gin.Context) { Timestamp: getCurrentTimestamp(), }) } + +// GetOrderSummary 获取订单汇总统计 +// @Summary 获取订单汇总统计 +// @Description 获取当前登录用户的订单汇总统计信息(总数、待支付数、已支付数) +// @Tags 客户订单管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} models.APIResponse{data=models.OrderSummaryResponse} "获取成功" +// @Failure 401 {object} models.ErrorResponse "未认证" +// @Failure 500 {object} models.ErrorResponse "服务器内部错误" +// @Router /api/cu/orders/summary [get] +func (h *CuOrderHandler) GetOrderSummary(c *gin.Context) { + // 从JWT Token中获取用户ID和客户ID + claims := c.MustGet("cu_user").(*utils.CuClaims) + + // 调用服务层 + result, err := h.cuOrderService.GetOrderSummary(c.Request.Context(), claims.CustomerID) + if err != nil { + // err已经是i18n.I18nError,直接使用 + i18nErr, ok := err.(*i18n.I18nError) + if ok { + c.JSON(i18nErr.HttpCode, models.ErrorResponse{ + Code: i18nErr.Code, + Message: i18nErr.Message, + Timestamp: getCurrentTimestamp(), + }) + return + } + // 如果不是i18n错误,使用通用错误 + lang := middleware.GetLanguage(c) + status, errCode, message := i18n.NewI18nErrorResponse("900004", lang, err.Error()) + c.JSON(status, models.ErrorResponse{ + Code: errCode, + Message: message + ": " + err.Error(), + Timestamp: getCurrentTimestamp(), + }) + return + } + + lang := middleware.GetLanguage(c) + successMessage := i18n.GetI18nErrorMessage("000000", lang) + c.JSON(http.StatusOK, models.APIResponse{ + Code: "000000", + Message: successMessage, + Data: result, + }) +} diff --git a/backend/internal/api/routes/router.go b/backend/internal/api/routes/router.go index 7541a81..510ec94 100644 --- a/backend/internal/api/routes/router.go +++ b/backend/internal/api/routes/router.go @@ -217,6 +217,7 @@ func SetupRouter() *gin.Engine { cuAuth.POST("/orders", cuOrderHandler.CreateOrder) cuAuth.GET("/orders/:order_id", cuOrderHandler.GetOrder) cuAuth.GET("/orders", cuOrderHandler.GetUserOrders) + cuAuth.GET("/orders/summary", cuOrderHandler.GetOrderSummary) // 支付管理 cuAuth.GET("/payment/:payment_no/status", paymentHandler.GetPaymentStatus) @@ -227,6 +228,7 @@ func SetupRouter() *gin.Engine { // 设备管理 cuAuth.GET("/devices", cuDeviceHandler.GetDevices) + cuAuth.GET("/devices/summary", cuDeviceHandler.GetDeviceSummary) cuAuth.DELETE("/devices/:id", cuDeviceHandler.UnbindDevice) } } diff --git a/backend/internal/models/cu_order.go b/backend/internal/models/cu_order.go index e10693a..62a53dd 100644 --- a/backend/internal/models/cu_order.go +++ b/backend/internal/models/cu_order.go @@ -111,3 +111,10 @@ type CuOrderListResponse struct { Page int `json:"page"` PageSize int `json:"page_size"` } + +// OrderSummaryResponse 订单汇总响应结构 +type OrderSummaryResponse struct { + TotalOrders int64 `json:"total_orders"` // 订单总数 + PendingOrders int64 `json:"pending_orders"` // 待支付订单数 + PaidOrders int64 `json:"paid_orders"` // 已支付订单数 +} diff --git a/backend/internal/models/license.go b/backend/internal/models/license.go index 6010755..e1d5438 100644 --- a/backend/internal/models/license.go +++ b/backend/internal/models/license.go @@ -218,3 +218,10 @@ type DeviceListResponse struct { Page int `json:"page"` // 当前页码 PageSize int `json:"page_size"` // 每页数量 } + +// DeviceSummaryResponse 设备汇总响应结构 +type DeviceSummaryResponse struct { + TotalDevices int64 `json:"total_devices"` // 设备总数 + OnlineDevices int64 `json:"online_devices"` // 在线设备数 + OfflineDevices int64 `json:"offline_devices"` // 离线设备数 +} diff --git a/backend/internal/repository/cu_order_repository.go b/backend/internal/repository/cu_order_repository.go index 1bdbfc1..a88b42f 100644 --- a/backend/internal/repository/cu_order_repository.go +++ b/backend/internal/repository/cu_order_repository.go @@ -1,6 +1,7 @@ package repository import ( + "context" "license-manager/internal/models" "gorm.io/gorm" @@ -12,6 +13,7 @@ type CuOrderRepository interface { GetByOrderNo(orderNo string) (*models.CuOrder, error) GetByCuUserID(cuUserID string, offset, limit int) ([]*models.CuOrder, int64, error) GetByCustomerID(customerID string, offset, limit int) ([]*models.CuOrder, int64, error) + GetCustomerOrderSummary(ctx context.Context, customerID string) (*models.OrderSummaryResponse, error) Update(order *models.CuOrder) error Delete(id string) error CheckTrialOrderExists(cuUserID string, currentMonth string) (bool, error) @@ -111,3 +113,39 @@ func (r *cuOrderRepository) CheckTrialOrderExists(cuUserID string, currentMonth return count > 0, err } + +// GetCustomerOrderSummary 获取客户订单汇总统计 +func (r *cuOrderRepository) GetCustomerOrderSummary(ctx context.Context, customerID string) (*models.OrderSummaryResponse, error) { + // 查询订单总数 + var totalOrders int64 + err := r.db.Model(&models.CuOrder{}). + Where("customer_id = ?", customerID). + Count(&totalOrders).Error + if err != nil { + return nil, err + } + + // 查询待支付订单数 + var pendingOrders int64 + err = r.db.Model(&models.CuOrder{}). + Where("customer_id = ? AND status = ?", customerID, "pending"). + Count(&pendingOrders).Error + if err != nil { + return nil, err + } + + // 查询已支付订单数 + var paidOrders int64 + err = r.db.Model(&models.CuOrder{}). + Where("customer_id = ? AND status = ?", customerID, "paid"). + Count(&paidOrders).Error + if err != nil { + return nil, err + } + + return &models.OrderSummaryResponse{ + TotalOrders: totalOrders, + PendingOrders: pendingOrders, + PaidOrders: paidOrders, + }, nil +} diff --git a/backend/internal/repository/interfaces.go b/backend/internal/repository/interfaces.go index aed3dca..4540a20 100644 --- a/backend/internal/repository/interfaces.go +++ b/backend/internal/repository/interfaces.go @@ -126,6 +126,9 @@ type LicenseRepository interface { // GetCustomerDeviceList 查询客户设备列表(关联授权码信息) GetCustomerDeviceList(ctx context.Context, customerID string, req *models.DeviceListRequest) (*models.DeviceListResponse, error) + // GetCustomerDeviceSummary 获取客户设备汇总统计 + GetCustomerDeviceSummary(ctx context.Context, customerID string) (*models.DeviceSummaryResponse, error) + // DeleteLicenseByID 根据ID删除许可证(物理删除,用于设备解绑) DeleteLicenseByID(ctx context.Context, id string) error @@ -170,4 +173,4 @@ type PaymentRepository interface { // GetExpiredPayments 获取过期的支付订单 GetExpiredPayments(ctx context.Context) ([]*models.Payment, error) -} \ No newline at end of file +} diff --git a/backend/internal/repository/license_repository.go b/backend/internal/repository/license_repository.go index 5a8988b..f9dc988 100644 --- a/backend/internal/repository/license_repository.go +++ b/backend/internal/repository/license_repository.go @@ -387,6 +387,44 @@ func (r *licenseRepository) DeleteLicenseByID(ctx context.Context, id string) er return r.db.Unscoped().Delete(&models.License{}, "id = ?", id).Error } +// GetCustomerDeviceSummary 获取客户设备汇总统计 +func (r *licenseRepository) GetCustomerDeviceSummary(ctx context.Context, customerID string) (*models.DeviceSummaryResponse, error) { + // 获取心跳超时配置(秒) + cfg := config.GetConfig() + heartbeatTimeoutSeconds := cfg.License.HeartbeatTimeout + if heartbeatTimeoutSeconds <= 0 { + heartbeatTimeoutSeconds = 300 // 默认5分钟 + } + + // 查询设备总数 + var totalDevices int64 + err := r.db.Model(&models.License{}). + Where("customer_id = ? AND status = ? AND deleted_at IS NULL", customerID, "active"). + Count(&totalDevices).Error + if err != nil { + return nil, err + } + + // 查询在线设备数 + var onlineDevices int64 + err = r.db.Model(&models.License{}). + Where("customer_id = ? AND status = ? AND deleted_at IS NULL AND last_heartbeat > ?", + customerID, "active", time.Now().Add(-time.Duration(heartbeatTimeoutSeconds)*time.Second)). + Count(&onlineDevices).Error + if err != nil { + return nil, err + } + + // 计算离线设备数 + offlineDevices := totalDevices - onlineDevices + + return &models.DeviceSummaryResponse{ + TotalDevices: totalDevices, + OnlineDevices: onlineDevices, + OfflineDevices: offlineDevices, + }, nil +} + // CheckLicenseBelongsToCustomer 检查许可证是否属于指定客户 func (r *licenseRepository) CheckLicenseBelongsToCustomer(ctx context.Context, licenseID, customerID string) (bool, error) { var count int64 diff --git a/backend/internal/service/cu_device_service.go b/backend/internal/service/cu_device_service.go index b9cdb75..339c00d 100644 --- a/backend/internal/service/cu_device_service.go +++ b/backend/internal/service/cu_device_service.go @@ -36,6 +36,19 @@ func (s *cuDeviceService) GetDeviceList(ctx context.Context, customerID string, return s.licenseRepo.GetCustomerDeviceList(ctx, customerID, req) } +// GetDeviceSummary 获取设备汇总统计 +func (s *cuDeviceService) GetDeviceSummary(ctx context.Context, customerID string) (*models.DeviceSummaryResponse, error) { + lang := pkgcontext.GetLanguageFromContext(ctx) + + // 参数验证 + if customerID == "" { + return nil, i18n.NewI18nError("900001", lang) // 业务错误,不覆盖多语言message + } + + // 委托给Repository层进行数据访问 + return s.licenseRepo.GetCustomerDeviceSummary(ctx, customerID) +} + // UnbindDevice 解绑设备 func (s *cuDeviceService) UnbindDevice(ctx context.Context, customerID, licenseID string) error { lang := pkgcontext.GetLanguageFromContext(ctx) diff --git a/backend/internal/service/cu_order_service.go b/backend/internal/service/cu_order_service.go index 548ea1c..17d818f 100644 --- a/backend/internal/service/cu_order_service.go +++ b/backend/internal/service/cu_order_service.go @@ -20,6 +20,7 @@ type CuOrderService interface { CreatePendingOrder(ctx context.Context, cuUserID, customerID string, req *models.CuOrderCreateRequest) (*models.CuOrder, error) GetOrder(ctx context.Context, orderID, cuUserID string) (*models.CuOrder, error) GetUserOrders(ctx context.Context, cuUserID string, offset, limit int) ([]*models.CuOrder, int64, error) + GetOrderSummary(ctx context.Context, customerID string) (*models.OrderSummaryResponse, error) CalculatePrice(ctx context.Context, packageID string, licenseCount int) (*PriceCalculationResult, error) UpdateOrderStatus(ctx context.Context, orderID string, status string) error } @@ -356,6 +357,19 @@ func (s *cuOrderService) GetUserOrders(ctx context.Context, cuUserID string, off return orders, total, nil } +// GetOrderSummary 获取订单汇总统计 +func (s *cuOrderService) GetOrderSummary(ctx context.Context, customerID string) (*models.OrderSummaryResponse, error) { + lang := pkgcontext.GetLanguageFromContext(ctx) + + // 参数验证 + if customerID == "" { + return nil, i18n.NewI18nError("900001", lang) // 业务错误,不覆盖多语言message + } + + // 委托给Repository层进行数据访问 + return s.repo.GetCustomerOrderSummary(ctx, customerID) +} + func (s *cuOrderService) CalculatePrice(ctx context.Context, packageID string, licenseCount int) (*PriceCalculationResult, error) { lang := pkgcontext.GetLanguageFromContext(ctx) diff --git a/backend/internal/service/interfaces.go b/backend/internal/service/interfaces.go index bbfac76..8aeb781 100644 --- a/backend/internal/service/interfaces.go +++ b/backend/internal/service/interfaces.go @@ -79,7 +79,9 @@ type CuDeviceService interface { // 获取设备列表 GetDeviceList(ctx context.Context, customerID string, req *models.DeviceListRequest) (*models.DeviceListResponse, error) + // 获取设备汇总统计 + GetDeviceSummary(ctx context.Context, customerID string) (*models.DeviceSummaryResponse, error) + // 解绑设备 UnbindDevice(ctx context.Context, customerID, licenseID string) error } - diff --git a/backend/pkg/utils/sms.go b/backend/pkg/utils/sms.go index 562bef4..03dd980 100644 --- a/backend/pkg/utils/sms.go +++ b/backend/pkg/utils/sms.go @@ -40,7 +40,7 @@ const ( SMSLimitKeyPrefix = "sms:limit" SMSCodeExpireTime = 5 * time.Minute SMSLimitTimeWindow = time.Hour - SMSLimitMaxCount = 25 + SMSLimitMaxCount = 5 ) // SMSService 短信服务接口