七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
92
internal/handler/admin/asset_package_batch_order.go
Normal file
92
internal/handler/admin/asset_package_batch_order.go
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
197
internal/handler/admin/wecom.go
Normal file
197
internal/handler/admin/wecom.go
Normal 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)
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
customerBinding "github.com/break/junhong_cmp_fiber/internal/service/customer_binding"
|
||||
packagepkg "github.com/break/junhong_cmp_fiber/internal/service/package"
|
||||
"github.com/break/junhong_cmp_fiber/internal/service/packageprice"
|
||||
rechargeSvc "github.com/break/junhong_cmp_fiber/internal/service/recharge"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
@@ -39,6 +40,21 @@ type ClientAssetHandler struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
observationSeries cardObservationApp.BestEffortSeriesDispatcher
|
||||
paymentMethodPolicy ClientPaymentMethodPolicy
|
||||
forceRechargeChecker ClientForceRechargeChecker
|
||||
}
|
||||
|
||||
// ClientPaymentMethodPolicy 提供 C 端支付方式读取和校验能力。
|
||||
type ClientPaymentMethodPolicy interface {
|
||||
AllowedMethods(ctx context.Context, assetType string) ([]string, error)
|
||||
AllowedRechargeMethods(ctx context.Context, assetType string) ([]string, error)
|
||||
EnsureAllowed(ctx context.Context, assetType, paymentMethod string) error
|
||||
EnsureRechargeAllowed(ctx context.Context, assetType, paymentMethod string) error
|
||||
}
|
||||
|
||||
// ClientForceRechargeChecker 提供 C 端资产当前强充资格的统一判定。
|
||||
type ClientForceRechargeChecker interface {
|
||||
GetRechargeCheck(ctx context.Context, resourceType string, resourceID uint) (*rechargeSvc.ForceRechargeRequirement, error)
|
||||
}
|
||||
|
||||
// SetObservationSeriesDispatcher 注入读取型后台观测序列端口。
|
||||
@@ -46,6 +62,16 @@ func (h *ClientAssetHandler) SetObservationSeriesDispatcher(dispatcher cardObser
|
||||
h.observationSeries = dispatcher
|
||||
}
|
||||
|
||||
// SetPaymentMethodPolicy 注入 C 端支付方式策略。
|
||||
func (h *ClientAssetHandler) SetPaymentMethodPolicy(policy ClientPaymentMethodPolicy) {
|
||||
h.paymentMethodPolicy = policy
|
||||
}
|
||||
|
||||
// SetForceRechargeChecker 注入复用现有一次性佣金状态语义的强充判定服务。
|
||||
func (h *ClientAssetHandler) SetForceRechargeChecker(checker ClientForceRechargeChecker) {
|
||||
h.forceRechargeChecker = checker
|
||||
}
|
||||
|
||||
// NewClientAssetHandler 创建 C 端资产信息处理器
|
||||
func NewClientAssetHandler(
|
||||
assetService *asset.Service,
|
||||
@@ -158,6 +184,34 @@ func (h *ClientAssetHandler) GetAssetInfo(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentPackageID, err := h.getCurrentPackageID(resolved.SkipPermissionCtx, resolved.Asset.CurrentPackageUsageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if h.paymentMethodPolicy == nil {
|
||||
return errors.New(errors.CodeNoPaymentConfig)
|
||||
}
|
||||
if h.forceRechargeChecker == nil {
|
||||
return errors.New(errors.CodeNoPaymentConfig)
|
||||
}
|
||||
resourceType := resolved.Asset.AssetType
|
||||
if resourceType == "card" {
|
||||
resourceType = constants.ResourceTypeIotCard
|
||||
}
|
||||
forceRecharge, err := h.forceRechargeChecker.GetRechargeCheck(resolved.SkipPermissionCtx, resourceType, resolved.Asset.AssetID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allowedPaymentMethods, err := h.paymentMethodPolicy.AllowedMethods(resolved.SkipPermissionCtx, resolved.Asset.AssetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if forceRecharge.NeedForceRecharge {
|
||||
allowedPaymentMethods, err = h.paymentMethodPolicy.AllowedRechargeMethods(resolved.SkipPermissionCtx, resolved.Asset.AssetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
phone, _ := middleware.GetCustomerPhone(c)
|
||||
|
||||
@@ -172,11 +226,16 @@ func (h *ClientAssetHandler) GetAssetInfo(c *fiber.Ctx) error {
|
||||
StatusName: resolved.Asset.StatusName,
|
||||
RealNameStatus: resolved.Asset.RealNameStatus,
|
||||
RealNameStatusName: constants.GetRealNameStatusName(resolved.Asset.RealNameStatus),
|
||||
RealnamePolicy: resolved.Asset.RealnamePolicy,
|
||||
EffectiveRealnamePolicy: resolved.Asset.RealnamePolicy,
|
||||
RealnameRequired: resolved.Asset.RealnamePolicy != constants.RealnamePolicyNone,
|
||||
CarrierName: resolved.Asset.CarrierName,
|
||||
Generation: strconv.Itoa(resolved.Generation),
|
||||
WalletBalance: resolved.WalletBalance,
|
||||
AllowedPaymentMethods: allowedPaymentMethods,
|
||||
ActivatedAt: resolved.Asset.ActivatedAt,
|
||||
CurrentPackage: resolved.Asset.CurrentPackage,
|
||||
CurrentPackageID: currentPackageID,
|
||||
CurrentPackageUsageID: resolved.Asset.CurrentPackageUsageID,
|
||||
CurrentPackageActivatedAt: resolved.Asset.CurrentPackageActivatedAt,
|
||||
CurrentPackageExpiresAt: resolved.Asset.CurrentPackageExpiresAt,
|
||||
@@ -226,6 +285,20 @@ func (h *ClientAssetHandler) GetAssetInfo(c *fiber.Ctx) error {
|
||||
return response.Success(c, resp)
|
||||
}
|
||||
|
||||
func (h *ClientAssetHandler) getCurrentPackageID(ctx context.Context, usageID *uint) (uint, error) {
|
||||
if usageID == nil || *usageID == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var usage model.PackageUsage
|
||||
if err := h.db.WithContext(ctx).Select("package_id").First(&usage, *usageID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, errors.Wrap(errors.CodeDatabaseError, err, "查询当前套餐引用失败")
|
||||
}
|
||||
return usage.PackageID, nil
|
||||
}
|
||||
|
||||
func (h *ClientAssetHandler) dispatchAssetReadObservations(ctx context.Context, asset *dto.AssetResolveResponse) {
|
||||
if h.observationSeries == nil || asset == nil {
|
||||
return
|
||||
@@ -301,8 +374,9 @@ func (h *ClientAssetHandler) GetAvailablePackages(c *fiber.Ctx) error {
|
||||
PageSize: constants.MaxPageSize,
|
||||
OrderBy: "id DESC",
|
||||
}, map[string]any{
|
||||
"series_id": *resolved.Asset.SeriesID,
|
||||
"status": constants.StatusEnabled,
|
||||
"series_id": *resolved.Asset.SeriesID,
|
||||
"status": constants.StatusEnabled,
|
||||
"shelf_status": constants.ShelfStatusOn,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询可购套餐失败")
|
||||
|
||||
@@ -45,6 +45,12 @@ type ClientWalletHandler struct {
|
||||
db *gorm.DB
|
||||
iotCardStore *postgres.IotCardStore
|
||||
deviceStore *postgres.DeviceStore
|
||||
paymentMethodPolicy ClientPaymentMethodPolicy
|
||||
}
|
||||
|
||||
// SetPaymentMethodPolicy 注入 C 端支付方式策略。
|
||||
func (h *ClientWalletHandler) SetPaymentMethodPolicy(policy ClientPaymentMethodPolicy) {
|
||||
h.paymentMethodPolicy = policy
|
||||
}
|
||||
|
||||
// NewClientWalletHandler 创建 C 端钱包处理器
|
||||
@@ -243,14 +249,22 @@ func (h *ClientWalletHandler) GetRechargeCheck(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if h.paymentMethodPolicy == nil {
|
||||
return errors.New(errors.CodeNoPaymentConfig)
|
||||
}
|
||||
allowedPaymentMethods, err := h.paymentMethodPolicy.AllowedRechargeMethods(resolved.SkipPermissionCtx, resolved.Asset.AssetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := &dto.ClientRechargeCheckResponse{
|
||||
NeedForceRecharge: check.NeedForceRecharge,
|
||||
ForceRechargeAmount: check.ForceRechargeAmount,
|
||||
TriggerType: check.TriggerType,
|
||||
MinAmount: check.MinAmount,
|
||||
MaxAmount: check.MaxAmount,
|
||||
Message: check.Message,
|
||||
NeedForceRecharge: check.NeedForceRecharge,
|
||||
ForceRechargeAmount: check.ForceRechargeAmount,
|
||||
TriggerType: check.TriggerType,
|
||||
MinAmount: check.MinAmount,
|
||||
MaxAmount: check.MaxAmount,
|
||||
Message: check.Message,
|
||||
AllowedPaymentMethods: allowedPaymentMethods,
|
||||
}
|
||||
|
||||
return response.Success(c, resp)
|
||||
@@ -273,6 +287,12 @@ func (h *ClientWalletHandler) CreateRecharge(c *fiber.Ctx) error {
|
||||
if resolved.Asset.RealnamePolicy == constants.RealnamePolicyBeforeOrder && resolved.Asset.RealNameStatus != 1 {
|
||||
return errors.New(errors.CodeNeedRealname)
|
||||
}
|
||||
if h.paymentMethodPolicy == nil {
|
||||
return errors.New(errors.CodeNoPaymentConfig)
|
||||
}
|
||||
if err := h.paymentMethodPolicy.EnsureRechargeAllowed(resolved.SkipPermissionCtx, resolved.Asset.AssetType, req.PaymentMethod); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wallet, err := h.getOrCreateWallet(resolved)
|
||||
if err != nil {
|
||||
@@ -302,9 +322,10 @@ func (h *ClientWalletHandler) CreateRecharge(c *fiber.Ctx) error {
|
||||
switch req.PaymentMethod {
|
||||
case constants.RechargeMethodAlipay:
|
||||
return h.createAlipayRecharge(c, resolved, config, wallet, req)
|
||||
default:
|
||||
// 微信充值(默认):app_type 必填
|
||||
case constants.RechargeMethodWechat:
|
||||
return h.createWechatRecharge(c, resolved, config, wallet, req)
|
||||
default:
|
||||
return errors.New(errors.CodePaymentMethodUnavailable)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,7 +606,6 @@ func (h *ClientWalletHandler) resolveAssetFromIdentifier(c *fiber.Ctx, identifie
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
func (h *ClientWalletHandler) getAssetGeneration(ctx context.Context, assetType string, assetID uint) (int, error) {
|
||||
switch assetType {
|
||||
case "card":
|
||||
|
||||
61
internal/handler/callback/wecom_approval.go
Normal file
61
internal/handler/callback/wecom_approval.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package callback
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
wecominfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/wecom"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
// WeComApprovalHandler 提供企业微信审批加密回调入口。
|
||||
type WeComApprovalHandler struct {
|
||||
service *wecominfra.CallbackService
|
||||
}
|
||||
|
||||
// NewWeComApprovalHandler 创建企业微信审批回调 Handler。
|
||||
func NewWeComApprovalHandler(service *wecominfra.CallbackService) *WeComApprovalHandler {
|
||||
return &WeComApprovalHandler{service: service}
|
||||
}
|
||||
|
||||
// Verify 验证企业微信审批回调 URL。
|
||||
// GET /api/callback/wecom/approval/:application_id
|
||||
func (h *WeComApprovalHandler) Verify(c *fiber.Ctx) error {
|
||||
applicationID, err := parseWeComApplicationID(c.Params("application_id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plaintext, err := h.service.VerifyURL(
|
||||
c.UserContext(), applicationID, c.Query("msg_signature"), c.Query("timestamp"), c.Query("nonce"), c.Query("echostr"),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
|
||||
return c.Send(plaintext)
|
||||
}
|
||||
|
||||
// Receive 接收企业微信审批状态变化事件并快速返回 success。
|
||||
// POST /api/callback/wecom/approval/:application_id
|
||||
func (h *WeComApprovalHandler) Receive(c *fiber.Ctx) error {
|
||||
applicationID, err := parseWeComApplicationID(c.Params("application_id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.service.Receive(
|
||||
c.UserContext(), applicationID, c.Query("msg_signature"), c.Query("timestamp"), c.Query("nonce"), c.Body(),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
|
||||
return c.SendString("success")
|
||||
}
|
||||
|
||||
func parseWeComApplicationID(value string) (uint, error) {
|
||||
id, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
return 0, errors.New(errors.CodeInvalidParam, "企业微信应用配置 ID 无效")
|
||||
}
|
||||
return uint(id), nil
|
||||
}
|
||||
Reference in New Issue
Block a user