七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s

This commit is contained in:
2026-07-25 17:06:58 +08:00
parent ad9f613dd6
commit 73f5125d3d
249 changed files with 17137 additions and 877 deletions

View File

@@ -77,6 +77,24 @@ func (h *AccountHandler) Update(c *fiber.Ctx) error {
return response.Success(c, account)
}
// BindWeCom 绑定账号与企业微信应用可见成员。
// PUT /api/admin/accounts/:id/wecom-binding
func (h *AccountHandler) BindWeCom(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil || id == 0 {
return errors.New(errors.CodeInvalidParam, "无效的账号 ID")
}
var request dto.BindAccountWeComRequest
if err := c.BodyParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.service.BindWeCom(c.UserContext(), uint(id), request)
if err != nil {
return err
}
return response.Success(c, result)
}
// Delete 删除账号
// DELETE /api/admin/accounts/:id
func (h *AccountHandler) Delete(c *fiber.Ctx) error {

View File

@@ -10,6 +10,7 @@ import (
"github.com/gofiber/fiber/v2"
dto "github.com/break/junhong_cmp_fiber/internal/model/dto"
packageExpiryQuery "github.com/break/junhong_cmp_fiber/internal/query/packageexpiry"
assetService "github.com/break/junhong_cmp_fiber/internal/service/asset"
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
deviceService "github.com/break/junhong_cmp_fiber/internal/service/device"
@@ -17,8 +18,10 @@ import (
pollingSvc "github.com/break/junhong_cmp_fiber/internal/service/polling"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/logger"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/response"
"go.uber.org/zap"
)
// AssetHandler 资产管理处理器
@@ -33,6 +36,7 @@ type AssetHandler struct {
assetLifecycleService AssetLifecycleService
exchangeTraceQuery AssetExchangeTraceResolver
observationSeries cardObservationApp.BestEffortSeriesDispatcher
packageExpiryQuery *packageExpiryQuery.Query
}
// SetObservationSeriesDispatcher 注入后台实时状态的观测序列端口。
@@ -40,6 +44,11 @@ func (h *AssetHandler) SetObservationSeriesDispatcher(dispatcher cardObservation
h.observationSeries = dispatcher
}
// SetPackageExpiryQuery 注入临期资产分页查询。
func (h *AssetHandler) SetPackageExpiryQuery(query *packageExpiryQuery.Query) {
h.packageExpiryQuery = query
}
// AssetExchangeTraceResolver 定义资产详情换货链路读取用例。
type AssetExchangeTraceResolver interface {
Resolve(ctx context.Context, assetType string, assetID uint) (*dto.AssetExchangeTrace, error)
@@ -105,6 +114,27 @@ func (h *AssetHandler) Resolve(c *fiber.Ctx) error {
return response.Success(c, result)
}
// ListExpiring 查询当前权限范围内的临期资产列表和数量汇总。
// GET /api/admin/expiring-assets
func (h *AssetHandler) ListExpiring(c *fiber.Ctx) error {
var request dto.ExpiringAssetListRequest
if err := c.QueryParser(&request); err != nil {
logger.GetAppLogger().Warn("临期资产列表参数解析失败",
zap.String("method", c.Method()), zap.String("path", c.Path()), zap.Error(err))
return errors.New(errors.CodeInvalidParam)
}
if h.packageExpiryQuery == nil {
return errors.New(errors.CodeInternalError, "套餐临期查询未配置")
}
result, err := h.packageExpiryQuery.List(c.UserContext(), request)
if err != nil {
return err
}
return response.Success(c, dto.ExpiringAssetListResponse{
Items: result.Items, Total: result.Total, Page: result.Page, Size: result.Size, Summary: result.Summary,
})
}
// RealtimeStatus 获取资产实时状态
// GET /api/admin/assets/:identifier/realtime-status
func (h *AssetHandler) RealtimeStatus(c *fiber.Ctx) error {
@@ -463,6 +493,10 @@ func (h *AssetHandler) UpdatePollingStatus(c *fiber.Ctx) error {
// UpdateRealnamePolicy 更新资产实名认证策略
// PATCH /api/admin/assets/:identifier/realname-mode
func (h *AssetHandler) UpdateRealnamePolicy(c *fiber.Ctx) error {
if middleware.GetUserTypeFromContext(c.UserContext()) == constants.UserTypeEnterprise {
return errors.New(errors.CodeForbidden, "企业账号无权修改资产实名认证策略")
}
identifier := c.Params("identifier")
if identifier == "" {
return errors.New(errors.CodeInvalidParam)

View File

@@ -0,0 +1,92 @@
package admin
import (
"strconv"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
batchOrderSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_package_batch_order"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
// AssetPackageBatchOrderHandler 资产套餐批量订购 Handler。
type AssetPackageBatchOrderHandler struct {
service *batchOrderSvc.Service
validator *validator.Validate
}
// NewAssetPackageBatchOrderHandler 创建资产套餐批量订购 Handler。
func NewAssetPackageBatchOrderHandler(service *batchOrderSvc.Service, validator *validator.Validate) *AssetPackageBatchOrderHandler {
return &AssetPackageBatchOrderHandler{service: service, validator: validator}
}
// Create 创建资产套餐批量订购任务。
// POST /api/admin/asset-package-batch-orders
func (h *AssetPackageBatchOrderHandler) Create(c *fiber.Ctx) error {
var req dto.CreateAssetPackageBatchOrderRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if err := h.validator.Struct(&req); err != nil {
return errors.New(errors.CodeInvalidParam)
}
userType := middleware.GetUserTypeFromContext(c.UserContext())
if req.PaymentMethod == model.PaymentMethodOffline && userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
return errors.New(errors.CodeForbidden, "只有平台可以使用线下支付")
}
if req.PaymentMethod == model.PaymentMethodWallet && userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform && userType != constants.UserTypeAgent {
return errors.New(errors.CodeForbidden, "无权创建批量订购任务")
}
result, err := h.service.Create(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
// List 查询资产套餐批量订购任务列表。
// GET /api/admin/asset-package-batch-orders
func (h *AssetPackageBatchOrderHandler) List(c *fiber.Ctx) error {
if !canAccessAssetPackageBatchOrders(middleware.GetUserTypeFromContext(c.UserContext())) {
return errors.New(errors.CodeForbidden, "无权查询批量订购任务")
}
var req dto.ListAssetPackageBatchOrderRequest
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if err := h.validator.Struct(&req); err != nil {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.service.List(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
// Get 查询资产套餐批量订购任务详情。
// GET /api/admin/asset-package-batch-orders/:id
func (h *AssetPackageBatchOrderHandler) Get(c *fiber.Ctx) error {
if !canAccessAssetPackageBatchOrders(middleware.GetUserTypeFromContext(c.UserContext())) {
return errors.New(errors.CodeForbidden, "无权查询批量订购任务")
}
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil || id == 0 {
return errors.New(errors.CodeInvalidParam, "任务ID无效")
}
result, err := h.service.GetByID(c.UserContext(), uint(id))
if err != nil {
return err
}
return response.Success(c, result)
}
func canAccessAssetPackageBatchOrders(userType int) bool {
return userType == constants.UserTypeSuperAdmin || userType == constants.UserTypePlatform || userType == constants.UserTypeAgent
}

View File

@@ -35,6 +35,20 @@ func (h *DeviceHandler) List(c *fiber.Ctx) error {
return response.SuccessWithPagination(c, result.List, result.Total, result.Page, result.PageSize)
}
// BatchUpdateRealnamePolicy 批量更新设备实名认证策略。
// POST /api/admin/devices/batch-update-realname-policy
func (h *DeviceHandler) BatchUpdateRealnamePolicy(c *fiber.Ctx) error {
var req dto.BatchUpdateAssetRealnamePolicyRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.service.BatchUpdateRealnamePolicy(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *DeviceHandler) Delete(c *fiber.Ctx) error {
userType := middleware.GetUserTypeFromContext(c.UserContext())
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
@@ -226,26 +240,6 @@ func (h *DeviceHandler) GetGatewaySlots(c *fiber.Ctx) error {
return response.Success(c, resp)
}
// SetSpeedLimit 设置设备限速
// PUT /api/admin/devices/by-identifier/:identifier/speed-limit
func (h *DeviceHandler) SetSpeedLimit(c *fiber.Ctx) error {
identifier := c.Params("identifier")
if identifier == "" {
return errors.New(errors.CodeInvalidParam, "设备标识符不能为空")
}
var req dto.SetSpeedLimitRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if err := h.service.GatewaySetSpeedLimit(c.UserContext(), identifier, &req); err != nil {
return err
}
return response.Success(c, nil)
}
// SetWiFi 设置设备 WiFi
// PUT /api/admin/devices/by-identifier/:identifier/wifi
func (h *DeviceHandler) SetWiFi(c *fiber.Ctx) error {

View File

@@ -46,6 +46,20 @@ func (h *DeviceImportHandler) Import(c *fiber.Ctx) error {
return response.Success(c, result)
}
// CreateAllocation 创建单列 CSV 设备批量分配任务。
// POST /api/admin/devices/import/allocations
func (h *DeviceImportHandler) CreateAllocation(c *fiber.Ctx) error {
var req dto.CreateDeviceBatchAllocationRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.CreateBatchAllocationTask(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *DeviceImportHandler) List(c *fiber.Ctx) error {
userType := middleware.GetUserTypeFromContext(c.UserContext())
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {

View File

@@ -35,6 +35,38 @@ func (h *IotCardHandler) ListStandalone(c *fiber.Ctx) error {
return response.SuccessWithPagination(c, result.List, result.Total, result.Page, result.PageSize)
}
// BatchUpdateRealnamePolicy 批量更新卡实名认证策略。
// POST /api/admin/iot-cards/batch-update-realname-policy
func (h *IotCardHandler) BatchUpdateRealnamePolicy(c *fiber.Ctx) error {
var req dto.BatchUpdateAssetRealnamePolicyRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.service.BatchUpdateRealnamePolicy(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
// SetSpeedTier 设置或恢复 IoT 卡固定限速档位。
// PUT /api/admin/iot-cards/:iccid/speed-tier
func (h *IotCardHandler) SetSpeedTier(c *fiber.Ctx) error {
iccid := c.Params("iccid")
if iccid == "" {
return errors.New(errors.CodeInvalidParam, "ICCID 不能为空")
}
var req dto.SetIotCardSpeedTierRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.SetSpeedTier(c.UserContext(), iccid, req.Code)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *IotCardHandler) AllocateCards(c *fiber.Ctx) error {
var req dto.AllocateStandaloneCardsRequest
if err := c.BodyParser(&req); err != nil {

View File

@@ -0,0 +1,197 @@
package admin
import (
"strconv"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
wecomapp "github.com/break/junhong_cmp_fiber/internal/application/wecom"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
// WeComHandler 提供企业微信自建应用连接配置接口。
type WeComHandler struct {
service *wecomapp.ConnectionService
directory *wecomapp.DirectoryService
scenes *wecomapp.SceneService
validator *validator.Validate
}
// SetSceneService 注入企业微信审批场景配置用例。
func (h *WeComHandler) SetSceneService(service *wecomapp.SceneService) {
h.scenes = service
}
// NewWeComHandler 创建企业微信连接配置 Handler。
func NewWeComHandler(service *wecomapp.ConnectionService, validate *validator.Validate) *WeComHandler {
return &WeComHandler{service: service, validator: validate}
}
// SetDirectoryService 注入企业微信通讯录同步用例。
func (h *WeComHandler) SetDirectoryService(service *wecomapp.DirectoryService) {
h.directory = service
}
// Save 创建或更新企业微信应用连接配置。
// POST /api/admin/wecom/applications
func (h *WeComHandler) Save(c *fiber.Ctx) error {
if h == nil || h.service == nil || h.validator == nil {
return errors.New(errors.CodeServiceUnavailable, "企业微信连接服务未配置")
}
var request dto.SaveWeComApplicationRequest
if err := c.BodyParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
if err := h.validator.Struct(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.service.Save(c.UserContext(), request)
if err != nil {
return err
}
return response.Success(c, result)
}
// List 查询企业微信应用连接配置。
// GET /api/admin/wecom/applications
func (h *WeComHandler) List(c *fiber.Ctx) error {
if h == nil || h.service == nil || h.validator == nil {
return errors.New(errors.CodeServiceUnavailable, "企业微信连接服务未配置")
}
var request dto.WeComApplicationListRequest
if err := c.QueryParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
if err := h.validator.Struct(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.service.List(c.UserContext(), request)
if err != nil {
return err
}
return response.Success(c, result)
}
// Test 测试指定企业微信应用能否成功取得 access_token。
// POST /api/admin/wecom/applications/:id/test
func (h *WeComHandler) Test(c *fiber.Ctx) error {
if h == nil || h.service == nil {
return errors.New(errors.CodeServiceUnavailable, "企业微信连接服务未配置")
}
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil || id == 0 {
return errors.New(errors.CodeInvalidParam, "企业微信应用配置 ID 无效")
}
if err := h.service.Test(c.UserContext(), uint(id)); err != nil {
return err
}
return response.Success(c, dto.WeComConnectionTestResponse{Success: true})
}
// SaveDefaultCreator 保存应用默认审批发起人。
// PUT /api/admin/wecom/applications/:id/default-creator
func (h *WeComHandler) SaveDefaultCreator(c *fiber.Ctx) error {
if h == nil || h.service == nil || h.validator == nil {
return errors.New(errors.CodeServiceUnavailable, "企业微信连接服务未配置")
}
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil || id == 0 {
return errors.New(errors.CodeInvalidParam, "企业微信应用配置 ID 无效")
}
var request dto.SaveWeComDefaultCreatorRequest
if err := c.BodyParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
if err := h.validator.Struct(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.service.SaveDefaultCreator(c.UserContext(), uint(id), request)
if err != nil {
return err
}
return response.Success(c, result)
}
// SyncMembers 同步指定应用当前可见成员。
// POST /api/admin/wecom/applications/:id/members/sync
func (h *WeComHandler) SyncMembers(c *fiber.Ctx) error {
if h == nil || h.directory == nil {
return errors.New(errors.CodeServiceUnavailable, "企业微信通讯录服务未配置")
}
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil || id == 0 {
return errors.New(errors.CodeInvalidParam, "企业微信应用配置 ID 无效")
}
result, err := h.directory.Sync(c.UserContext(), uint(id))
if err != nil {
return err
}
return response.Success(c, result)
}
// ListMembers 分页查询指定应用最近同步的可见成员。
// GET /api/admin/wecom/applications/:id/members
func (h *WeComHandler) ListMembers(c *fiber.Ctx) error {
if h == nil || h.directory == nil || h.validator == nil {
return errors.New(errors.CodeServiceUnavailable, "企业微信通讯录服务未配置")
}
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil || id == 0 {
return errors.New(errors.CodeInvalidParam, "企业微信应用配置 ID 无效")
}
var request dto.WeComMemberListRequest
if err := c.QueryParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
if err := h.validator.Struct(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.directory.List(c.UserContext(), uint(id), request)
if err != nil {
return err
}
return response.Success(c, result)
}
// SaveScene 保存并校验稳定业务类型的企微模板控件映射。
// PUT /api/admin/wecom/scenes/:business_type
func (h *WeComHandler) SaveScene(c *fiber.Ctx) error {
if h == nil || h.scenes == nil || h.validator == nil {
return errors.New(errors.CodeServiceUnavailable, "企业微信审批场景服务未配置")
}
var request dto.SaveWeComApprovalSceneRequest
if err := c.BodyParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
if err := h.validator.Struct(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.scenes.Save(c.UserContext(), c.Params("business_type"), request)
if err != nil {
return err
}
return response.Success(c, result)
}
// ListScenes 分页查询企业微信审批场景当前配置。
// GET /api/admin/wecom/scenes
func (h *WeComHandler) ListScenes(c *fiber.Ctx) error {
if h == nil || h.scenes == nil || h.validator == nil {
return errors.New(errors.CodeServiceUnavailable, "企业微信审批场景服务未配置")
}
var request dto.WeComApprovalSceneListRequest
if err := c.QueryParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
if err := h.validator.Struct(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.scenes.List(c.UserContext(), request)
if err != nil {
return err
}
return response.Success(c, result)
}