feat: 实现企业卡授权和授权记录管理功能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m9s

主要功能:
- 添加企业卡授权/回收接口 (POST /enterprises/:id/allocate-cards, recall-cards)
- 添加授权记录管理接口 (GET/PUT /authorizations)
- 实现代理用户数据权限过滤(只能查看自己店铺下企业的授权记录)
- 添加 GORM callback 支持授权记录表的数据权限过滤

技术改进:
- 原生 SQL 查询手动添加数据权限过滤(ListWithJoin, GetByIDWithJoin)
- 移除卡授权预检接口(allocate-cards/preview),保留内部方法
- 完善单元测试和集成测试覆盖
This commit is contained in:
2026-01-26 15:07:03 +08:00
parent 45aa7deb87
commit fdcff33058
42 changed files with 4782 additions and 298 deletions

View File

@@ -24,6 +24,7 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
CommissionWithdrawalSetting: admin.NewCommissionWithdrawalSettingHandler(svc.CommissionWithdrawalSetting),
Enterprise: admin.NewEnterpriseHandler(svc.Enterprise),
EnterpriseCard: admin.NewEnterpriseCardHandler(svc.EnterpriseCard),
Authorization: admin.NewAuthorizationHandler(svc.Authorization),
CustomerAccount: admin.NewCustomerAccountHandler(svc.CustomerAccount),
MyCommission: admin.NewMyCommissionHandler(svc.MyCommission),
IotCard: admin.NewIotCardHandler(svc.IotCard),

View File

@@ -33,6 +33,7 @@ type services struct {
CommissionWithdrawalSetting *commissionWithdrawalSettingSvc.Service
Enterprise *enterpriseSvc.Service
EnterpriseCard *enterpriseCardSvc.Service
Authorization *enterpriseCardSvc.AuthorizationService
CustomerAccount *customerAccountSvc.Service
MyCommission *myCommissionSvc.Service
IotCard *iotCardSvc.Service
@@ -54,6 +55,7 @@ func initServices(s *stores, deps *Dependencies) *services {
CommissionWithdrawalSetting: commissionWithdrawalSettingSvc.New(deps.DB, s.Account, s.CommissionWithdrawalSetting),
Enterprise: enterpriseSvc.New(deps.DB, s.Enterprise, s.Shop, s.Account),
EnterpriseCard: enterpriseCardSvc.New(deps.DB, s.Enterprise, s.EnterpriseCardAuthorization),
Authorization: enterpriseCardSvc.NewAuthorizationService(s.Enterprise, s.IotCard, s.EnterpriseCardAuthorization, deps.Logger),
CustomerAccount: customerAccountSvc.New(deps.DB, s.Account, s.Shop, s.Enterprise),
MyCommission: myCommissionSvc.New(deps.DB, s.Shop, s.Wallet, s.CommissionWithdrawalRequest, s.CommissionWithdrawalSetting, s.CommissionRecord, s.WalletTransaction),
IotCard: iotCardSvc.New(deps.DB, s.IotCard, s.Shop, s.AssetAllocationRecord),

View File

@@ -22,6 +22,7 @@ type Handlers struct {
CommissionWithdrawalSetting *admin.CommissionWithdrawalSettingHandler
Enterprise *admin.EnterpriseHandler
EnterpriseCard *admin.EnterpriseCardHandler
Authorization *admin.AuthorizationHandler
CustomerAccount *admin.CustomerAccountHandler
MyCommission *admin.MyCommissionHandler
IotCard *admin.IotCardHandler

View File

@@ -0,0 +1,157 @@
package admin
import (
"strconv"
"time"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
enterpriseCardService "github.com/break/junhong_cmp_fiber/internal/service/enterprise_card"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type AuthorizationHandler struct {
service *enterpriseCardService.AuthorizationService
}
func NewAuthorizationHandler(service *enterpriseCardService.AuthorizationService) *AuthorizationHandler {
return &AuthorizationHandler{service: service}
}
func (h *AuthorizationHandler) List(c *fiber.Ctx) error {
var req dto.AuthorizationListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.ListRecords(c.UserContext(), enterpriseCardService.ListRecordsRequest{
EnterpriseID: req.EnterpriseID,
ICCID: req.ICCID,
AuthorizerType: req.AuthorizerType,
Status: req.Status,
StartTime: req.StartTime,
EndTime: req.EndTime,
Page: req.Page,
PageSize: req.PageSize,
})
if err != nil {
return err
}
items := make([]dto.AuthorizationItem, len(result.Items))
for i, r := range result.Items {
authorizedAt, _ := time.ParseInLocation("2006-01-02 15:04:05", r.AuthorizedAt, time.Local)
var revokedAt *time.Time
if r.RevokedAt != nil {
t, _ := time.ParseInLocation("2006-01-02 15:04:05", *r.RevokedAt, time.Local)
revokedAt = &t
}
items[i] = dto.AuthorizationItem{
ID: r.ID,
EnterpriseID: r.EnterpriseID,
EnterpriseName: r.EnterpriseName,
CardID: r.CardID,
ICCID: r.ICCID,
MSISDN: r.MSISDN,
AuthorizedBy: r.AuthorizedBy,
AuthorizerName: r.AuthorizerName,
AuthorizerType: r.AuthorizerType,
AuthorizedAt: authorizedAt,
RevokedBy: r.RevokedBy,
RevokerName: r.RevokerName,
RevokedAt: revokedAt,
Status: r.Status,
Remark: r.Remark,
}
}
return response.SuccessWithPagination(c, items, result.Total, result.Page, result.Size)
}
func (h *AuthorizationHandler) GetDetail(c *fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的授权记录ID")
}
r, err := h.service.GetRecordDetail(c.UserContext(), uint(id))
if err != nil {
return err
}
authorizedAt, _ := time.ParseInLocation("2006-01-02 15:04:05", r.AuthorizedAt, time.Local)
var revokedAt *time.Time
if r.RevokedAt != nil {
t, _ := time.ParseInLocation("2006-01-02 15:04:05", *r.RevokedAt, time.Local)
revokedAt = &t
}
result := dto.AuthorizationItem{
ID: r.ID,
EnterpriseID: r.EnterpriseID,
EnterpriseName: r.EnterpriseName,
CardID: r.CardID,
ICCID: r.ICCID,
MSISDN: r.MSISDN,
AuthorizedBy: r.AuthorizedBy,
AuthorizerName: r.AuthorizerName,
AuthorizerType: r.AuthorizerType,
AuthorizedAt: authorizedAt,
RevokedBy: r.RevokedBy,
RevokerName: r.RevokerName,
RevokedAt: revokedAt,
Status: r.Status,
Remark: r.Remark,
}
return response.Success(c, result)
}
func (h *AuthorizationHandler) UpdateRemark(c *fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的授权记录ID")
}
var req dto.UpdateAuthorizationRemarkReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
r, err := h.service.UpdateRecordRemark(c.UserContext(), uint(id), req.Remark)
if err != nil {
return err
}
authorizedAt, _ := time.ParseInLocation("2006-01-02 15:04:05", r.AuthorizedAt, time.Local)
var revokedAt *time.Time
if r.RevokedAt != nil {
t, _ := time.ParseInLocation("2006-01-02 15:04:05", *r.RevokedAt, time.Local)
revokedAt = &t
}
result := dto.AuthorizationItem{
ID: r.ID,
EnterpriseID: r.EnterpriseID,
EnterpriseName: r.EnterpriseName,
CardID: r.CardID,
ICCID: r.ICCID,
MSISDN: r.MSISDN,
AuthorizedBy: r.AuthorizedBy,
AuthorizerName: r.AuthorizerName,
AuthorizerType: r.AuthorizerType,
AuthorizedAt: authorizedAt,
RevokedBy: r.RevokedBy,
RevokerName: r.RevokerName,
RevokedAt: revokedAt,
Status: r.Status,
Remark: r.Remark,
}
return response.Success(c, result)
}

View File

@@ -19,26 +19,6 @@ func NewEnterpriseCardHandler(service *enterpriseCardService.Service) *Enterpris
return &EnterpriseCardHandler{service: service}
}
func (h *EnterpriseCardHandler) AllocateCardsPreview(c *fiber.Ctx) error {
idStr := c.Params("id")
enterpriseID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的企业ID")
}
var req dto.AllocateCardsPreviewReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.AllocateCardsPreview(c.UserContext(), uint(enterpriseID), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *EnterpriseCardHandler) AllocateCards(c *fiber.Ctx) error {
idStr := c.Params("id")
enterpriseID, err := strconv.ParseUint(idStr, 10, 64)

View File

@@ -0,0 +1,48 @@
package dto
import "time"
type AuthorizationListReq struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
EnterpriseID *uint `json:"enterprise_id" query:"enterprise_id" description:"按企业ID筛选"`
ICCID string `json:"iccid" query:"iccid" description:"按ICCID模糊查询"`
AuthorizerType *int `json:"authorizer_type" query:"authorizer_type" description:"授权人类型2=平台3=代理"`
Status *int `json:"status" query:"status" description:"状态0=已回收1=有效"`
StartTime string `json:"start_time" query:"start_time" description:"授权时间起格式2006-01-02"`
EndTime string `json:"end_time" query:"end_time" description:"授权时间止格式2006-01-02"`
}
type AuthorizationItem struct {
ID uint `json:"id" description:"授权记录ID"`
EnterpriseID uint `json:"enterprise_id" description:"企业ID"`
EnterpriseName string `json:"enterprise_name" description:"企业名称"`
CardID uint `json:"card_id" description:"卡ID"`
ICCID string `json:"iccid" description:"ICCID"`
MSISDN string `json:"msisdn" description:"手机号"`
AuthorizedBy uint `json:"authorized_by" description:"授权人ID"`
AuthorizerName string `json:"authorizer_name" description:"授权人名称"`
AuthorizerType int `json:"authorizer_type" description:"授权人类型2=平台3=代理"`
AuthorizedAt time.Time `json:"authorized_at" description:"授权时间"`
RevokedBy *uint `json:"revoked_by,omitempty" description:"回收人ID"`
RevokerName string `json:"revoker_name,omitempty" description:"回收人名称"`
RevokedAt *time.Time `json:"revoked_at,omitempty" description:"回收时间"`
Status int `json:"status" description:"状态1=有效0=已回收"`
Remark string `json:"remark" description:"备注"`
}
type AuthorizationListResp struct {
Items []AuthorizationItem `json:"items" description:"授权记录列表"`
Total int64 `json:"total" description:"总记录数"`
Page int `json:"page" description:"当前页码"`
Size int `json:"size" description:"每页数量"`
}
type AuthorizationDetailReq struct {
ID uint `json:"-" params:"id" path:"id" validate:"required" required:"true" description:"授权记录ID"`
}
type UpdateAuthorizationRemarkReq struct {
ID uint `json:"-" params:"id" path:"id" validate:"required" required:"true" description:"授权记录ID"`
Remark string `json:"remark" validate:"max=500" description:"备注最多500字"`
}

View File

@@ -7,16 +7,20 @@ import (
)
// EnterpriseCardAuthorization 企业卡授权模型
// 记录企业被授权可见的卡卡的归属owner始终是代理商店铺
// 注意:不使用 BaseModel因为已有 AuthorizedBy/RevokedBy 字段
type EnterpriseCardAuthorization struct {
gorm.Model
BaseModel `gorm:"embedded"`
EnterpriseID uint `gorm:"column:enterprise_id;index;not null;comment:企业ID" json:"enterprise_id"`
IotCardID uint `gorm:"column:iot_card_id;index;not null;comment:IoT卡ID" json:"iot_card_id"`
ShopID uint `gorm:"column:shop_id;index;not null;comment:店铺ID授权方" json:"shop_id"`
AuthorizedBy uint `gorm:"column:authorized_by;not null;comment:授权ID" json:"authorized_by"`
AuthorizedAt *time.Time `gorm:"column:authorized_at;default:now();comment:授权时间" json:"authorized_at"`
Status int `gorm:"column:status;type:int;default:1;comment:状态 1=有效 0=已回收" json:"status"`
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"deleted_at,omitempty"`
EnterpriseID uint `gorm:"column:enterprise_id;not null;comment:被授权企业ID" json:"enterprise_id"`
CardID uint `gorm:"column:card_id;not null;comment:授权ID" json:"card_id"`
AuthorizedBy uint `gorm:"column:authorized_by;not null;comment:授权人账号ID" json:"authorized_by"`
AuthorizedAt time.Time `gorm:"column:authorized_at;not null;default:CURRENT_TIMESTAMP;comment:授权时间" json:"authorized_at"`
AuthorizerType int `gorm:"column:authorizer_type;not null;comment:授权人类型2=平台用户 3=代理账号" json:"authorizer_type"`
RevokedBy *uint `gorm:"column:revoked_by;comment:回收人账号ID" json:"revoked_by"`
RevokedAt *time.Time `gorm:"column:revoked_at;comment:回收时间" json:"revoked_at"`
Remark string `gorm:"column:remark;type:varchar(500);default:'';comment:授权备注" json:"remark"`
}
func (EnterpriseCardAuthorization) TableName() string {

View File

@@ -46,6 +46,9 @@ func RegisterAdminRoutes(router fiber.Router, handlers *bootstrap.Handlers, midd
if handlers.EnterpriseCard != nil {
registerEnterpriseCardRoutes(authGroup, handlers.EnterpriseCard, doc, basePath)
}
if handlers.Authorization != nil {
registerAuthorizationRoutes(authGroup, handlers.Authorization, doc, basePath)
}
if handlers.CustomerAccount != nil {
registerCustomerAccountRoutes(authGroup, handlers.CustomerAccount, doc, basePath)
}

View File

@@ -0,0 +1,38 @@
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/handler/admin"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
func registerAuthorizationRoutes(router fiber.Router, handler *admin.AuthorizationHandler, doc *openapi.Generator, basePath string) {
authorizations := router.Group("/authorizations")
groupPath := basePath + "/authorizations"
Register(authorizations, doc, groupPath, "GET", "", handler.List, RouteSpec{
Summary: "授权记录列表",
Tags: []string{"授权记录管理"},
Input: new(dto.AuthorizationListReq),
Output: new(dto.AuthorizationListResp),
Auth: true,
})
Register(authorizations, doc, groupPath, "GET", "/:id", handler.GetDetail, RouteSpec{
Summary: "授权记录详情",
Tags: []string{"授权记录管理"},
Input: new(dto.AuthorizationDetailReq),
Output: new(dto.AuthorizationItem),
Auth: true,
})
Register(authorizations, doc, groupPath, "PUT", "/:id/remark", handler.UpdateRemark, RouteSpec{
Summary: "修改授权备注",
Tags: []string{"授权记录管理"},
Input: new(dto.UpdateAuthorizationRemarkReq),
Output: new(dto.AuthorizationItem),
Auth: true,
})
}

View File

@@ -12,14 +12,6 @@ func registerEnterpriseCardRoutes(router fiber.Router, handler *admin.Enterprise
enterprises := router.Group("/enterprises")
groupPath := basePath + "/enterprises"
Register(enterprises, doc, groupPath, "POST", "/:id/allocate-cards/preview", handler.AllocateCardsPreview, RouteSpec{
Summary: "卡授权预检",
Tags: []string{"企业卡授权"},
Input: new(dto.AllocateCardsPreviewReq),
Output: new(dto.AllocateCardsPreviewResp),
Auth: true,
})
Register(enterprises, doc, groupPath, "POST", "/:id/allocate-cards", handler.AllocateCards, RouteSpec{
Summary: "授权卡给企业",
Tags: []string{"企业卡授权"},

View File

@@ -0,0 +1,414 @@
package enterprise_card
import (
"context"
"fmt"
"time"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"go.uber.org/zap"
"gorm.io/gorm"
)
type AuthorizationService struct {
enterpriseStore *postgres.EnterpriseStore
iotCardStore *postgres.IotCardStore
authorizationStore *postgres.EnterpriseCardAuthorizationStore
logger *zap.Logger
}
func NewAuthorizationService(
enterpriseStore *postgres.EnterpriseStore,
iotCardStore *postgres.IotCardStore,
authorizationStore *postgres.EnterpriseCardAuthorizationStore,
logger *zap.Logger,
) *AuthorizationService {
return &AuthorizationService{
enterpriseStore: enterpriseStore,
iotCardStore: iotCardStore,
authorizationStore: authorizationStore,
logger: logger,
}
}
type BatchAuthorizeRequest struct {
EnterpriseID uint
CardIDs []uint
AuthorizerID uint
AuthorizerType int
Remark string
}
func (s *AuthorizationService) BatchAuthorize(ctx context.Context, req BatchAuthorizeRequest) error {
if len(req.CardIDs) == 0 {
return errors.New(errors.CodeInvalidParam, "卡ID列表不能为空")
}
userID := middleware.GetUserIDFromContext(ctx)
userType := middleware.GetUserTypeFromContext(ctx)
shopID := middleware.GetShopIDFromContext(ctx)
if userID == 0 {
return errors.New(errors.CodeUnauthorized, "用户信息无效")
}
enterprise, err := s.enterpriseStore.GetByID(ctx, req.EnterpriseID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeEnterpriseNotFound, "企业不存在")
}
return err
}
if userType == constants.UserTypeAgent {
if enterprise.OwnerShopID == nil || *enterprise.OwnerShopID != shopID {
return errors.New(errors.CodeCannotAuthorizeToOthersEnterprise, "只能授权给自己的企业")
}
}
cards, err := s.iotCardStore.GetByIDs(ctx, req.CardIDs)
if err != nil {
return err
}
if len(cards) != len(req.CardIDs) {
return errors.New(errors.CodeIotCardNotFound, "部分卡不存在")
}
cardMap := make(map[uint]*model.IotCard)
for _, card := range cards {
cardMap[card.ID] = card
}
for _, cardID := range req.CardIDs {
card := cardMap[cardID]
if card.ShopID == nil {
return errors.New(errors.CodeIotCardStatusNotAllowed, fmt.Sprintf("卡 %s 未分销,不能授权", card.ICCID))
}
if userType == constants.UserTypeAgent && *card.ShopID != shopID {
return errors.New(errors.CodeCannotAuthorizeOthersCard, fmt.Sprintf("卡 %s 不属于您的店铺", card.ICCID))
}
}
boundCardIDs, err := s.iotCardStore.GetBoundCardIDs(ctx, req.CardIDs)
if err != nil {
return err
}
if len(boundCardIDs) > 0 {
return errors.New(errors.CodeCannotAuthorizeBoundCard, "部分卡已绑定设备,不能授权")
}
existingAuths, err := s.authorizationStore.ListByCards(ctx, req.CardIDs, false)
if err != nil {
return err
}
existingMap := make(map[uint]bool)
for _, auth := range existingAuths {
if auth.EnterpriseID == req.EnterpriseID {
existingMap[auth.CardID] = true
}
}
var newAuths []*model.EnterpriseCardAuthorization
for _, cardID := range req.CardIDs {
if existingMap[cardID] {
continue
}
newAuths = append(newAuths, &model.EnterpriseCardAuthorization{
EnterpriseID: req.EnterpriseID,
CardID: cardID,
AuthorizedBy: req.AuthorizerID,
AuthorizerType: req.AuthorizerType,
Remark: req.Remark,
})
}
if len(newAuths) == 0 {
return errors.New(errors.CodeCardAlreadyAuthorized, "所有卡已授权给该企业")
}
return s.authorizationStore.BatchCreate(ctx, newAuths)
}
type RevokeAuthorizationsRequest struct {
EnterpriseID uint
CardIDs []uint
RevokedBy uint
}
func (s *AuthorizationService) RevokeAuthorizations(ctx context.Context, req RevokeAuthorizationsRequest) error {
if len(req.CardIDs) == 0 {
return errors.New(errors.CodeInvalidParam, "卡ID列表不能为空")
}
userID := middleware.GetUserIDFromContext(ctx)
userType := middleware.GetUserTypeFromContext(ctx)
if userID == 0 {
return errors.New(errors.CodeUnauthorized, "用户信息无效")
}
existingAuths, err := s.authorizationStore.ListByCards(ctx, req.CardIDs, false)
if err != nil {
return err
}
authMap := make(map[uint]*model.EnterpriseCardAuthorization)
for _, auth := range existingAuths {
if auth.EnterpriseID == req.EnterpriseID {
authMap[auth.CardID] = auth
}
}
if len(authMap) == 0 {
return errors.New(errors.CodeCardNotAuthorized, "卡未授权给该企业")
}
if userType == constants.UserTypeAgent {
for _, auth := range authMap {
if auth.AuthorizedBy != userID {
return errors.New(errors.CodeCannotRevokeOthersAuthorization, "只能回收自己创建的授权")
}
}
}
var cardIDsToRevoke []uint
for cardID := range authMap {
cardIDsToRevoke = append(cardIDsToRevoke, cardID)
}
return s.authorizationStore.RevokeAuthorizations(ctx, req.EnterpriseID, cardIDsToRevoke, req.RevokedBy)
}
type ListAuthorizationsRequest struct {
EnterpriseID *uint
AuthorizedBy *uint
IncludeRevoked bool
Page int
PageSize int
}
type ListAuthorizationsResponse struct {
Authorizations []*model.EnterpriseCardAuthorization
Total int64
}
func (s *AuthorizationService) ListAuthorizations(ctx context.Context, req ListAuthorizationsRequest) (*ListAuthorizationsResponse, error) {
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 {
req.PageSize = constants.DefaultPageSize
}
if req.PageSize > constants.MaxPageSize {
req.PageSize = constants.MaxPageSize
}
opts := postgres.AuthorizationListOptions{
EnterpriseID: req.EnterpriseID,
AuthorizedBy: req.AuthorizedBy,
IncludeRevoked: req.IncludeRevoked,
Offset: (req.Page - 1) * req.PageSize,
Limit: req.PageSize,
}
auths, total, err := s.authorizationStore.ListWithOptions(ctx, opts)
if err != nil {
return nil, err
}
return &ListAuthorizationsResponse{
Authorizations: auths,
Total: total,
}, nil
}
func (s *AuthorizationService) GetAuthorizedCardIDs(ctx context.Context, enterpriseID uint) ([]uint, error) {
return s.authorizationStore.GetActiveAuthorizedCardIDs(ctx, enterpriseID)
}
type ListRecordsRequest struct {
EnterpriseID *uint
ICCID string
AuthorizerType *int
Status *int
StartTime string
EndTime string
Page int
PageSize int
}
type AuthorizationRecord struct {
ID uint
EnterpriseID uint
EnterpriseName string
CardID uint
ICCID string
MSISDN string
AuthorizedBy uint
AuthorizerName string
AuthorizerType int
AuthorizedAt string
RevokedBy *uint
RevokerName string
RevokedAt *string
Status int
Remark string
}
type ListRecordsResponse struct {
Items []AuthorizationRecord
Total int64
Page int
Size int
}
func (s *AuthorizationService) ListRecords(ctx context.Context, req ListRecordsRequest) (*ListRecordsResponse, error) {
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 {
req.PageSize = constants.DefaultPageSize
}
if req.PageSize > constants.MaxPageSize {
req.PageSize = constants.MaxPageSize
}
opts := postgres.AuthorizationWithJoinListOptions{
EnterpriseID: req.EnterpriseID,
ICCID: req.ICCID,
AuthorizerType: req.AuthorizerType,
Status: req.Status,
Offset: (req.Page - 1) * req.PageSize,
Limit: req.PageSize,
}
if req.StartTime != "" {
t, err := parseDate(req.StartTime)
if err == nil {
opts.StartTime = &t
}
}
if req.EndTime != "" {
t, err := parseDate(req.EndTime)
if err == nil {
endTime := t.AddDate(0, 0, 1)
opts.EndTime = &endTime
}
}
results, total, err := s.authorizationStore.ListWithJoin(ctx, opts)
if err != nil {
return nil, err
}
items := make([]AuthorizationRecord, len(results))
for i, r := range results {
status := 1
if r.RevokedAt != nil {
status = 0
}
var revokedAt *string
if r.RevokedAt != nil {
t := r.RevokedAt.Format("2006-01-02 15:04:05")
revokedAt = &t
}
revokerName := ""
if r.RevokerName != nil {
revokerName = *r.RevokerName
}
items[i] = AuthorizationRecord{
ID: r.ID,
EnterpriseID: r.EnterpriseID,
EnterpriseName: r.EnterpriseName,
CardID: r.CardID,
ICCID: r.ICCID,
MSISDN: r.MSISDN,
AuthorizedBy: r.AuthorizedBy,
AuthorizerName: r.AuthorizerName,
AuthorizerType: r.AuthorizerType,
AuthorizedAt: r.AuthorizedAt.Format("2006-01-02 15:04:05"),
RevokedBy: r.RevokedBy,
RevokerName: revokerName,
RevokedAt: revokedAt,
Status: status,
Remark: r.Remark,
}
}
return &ListRecordsResponse{
Items: items,
Total: total,
Page: req.Page,
Size: req.PageSize,
}, nil
}
func (s *AuthorizationService) GetRecordDetail(ctx context.Context, id uint) (*AuthorizationRecord, error) {
r, err := s.authorizationStore.GetByIDWithJoin(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "授权记录不存在")
}
return nil, err
}
status := 1
if r.RevokedAt != nil {
status = 0
}
var revokedAt *string
if r.RevokedAt != nil {
t := r.RevokedAt.Format("2006-01-02 15:04:05")
revokedAt = &t
}
revokerName := ""
if r.RevokerName != nil {
revokerName = *r.RevokerName
}
return &AuthorizationRecord{
ID: r.ID,
EnterpriseID: r.EnterpriseID,
EnterpriseName: r.EnterpriseName,
CardID: r.CardID,
ICCID: r.ICCID,
MSISDN: r.MSISDN,
AuthorizedBy: r.AuthorizedBy,
AuthorizerName: r.AuthorizerName,
AuthorizerType: r.AuthorizerType,
AuthorizedAt: r.AuthorizedAt.Format("2006-01-02 15:04:05"),
RevokedBy: r.RevokedBy,
RevokerName: revokerName,
RevokedAt: revokedAt,
Status: status,
Remark: r.Remark,
}, nil
}
func (s *AuthorizationService) UpdateRecordRemark(ctx context.Context, id uint, remark string) (*AuthorizationRecord, error) {
if err := s.authorizationStore.UpdateRemark(ctx, id, remark); err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "授权记录不存在")
}
return nil, err
}
return s.GetRecordDetail(ctx, id)
}
func parseDate(dateStr string) (time.Time, error) {
return time.ParseInLocation("2006-01-02", dateStr, time.Local)
}

View File

@@ -32,7 +32,7 @@ func New(
}
}
func (s *Service) AllocateCardsPreview(ctx context.Context, enterpriseID uint, req *dto.AllocateCardsPreviewReq) (*dto.AllocateCardsPreviewResp, error) {
func (s *Service) allocateCardsPreview(ctx context.Context, enterpriseID uint, req *dto.AllocateCardsPreviewReq) (*dto.AllocateCardsPreviewResp, error) {
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
@@ -172,7 +172,6 @@ func (s *Service) AllocateCardsPreview(ctx context.Context, enterpriseID uint, r
func (s *Service) AllocateCards(ctx context.Context, enterpriseID uint, req *dto.AllocateCardsReq) (*dto.AllocateCardsResp, error) {
currentUserID := middleware.GetUserIDFromContext(ctx)
currentShopID := middleware.GetShopIDFromContext(ctx)
if currentUserID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
@@ -182,7 +181,7 @@ func (s *Service) AllocateCards(ctx context.Context, enterpriseID uint, req *dto
return nil, errors.New(errors.CodeEnterpriseNotFound, "企业不存在")
}
preview, err := s.AllocateCardsPreview(ctx, enterpriseID, &dto.AllocateCardsPreviewReq{ICCIDs: req.ICCIDs})
preview, err := s.allocateCardsPreview(ctx, enterpriseID, &dto.AllocateCardsPreviewReq{ICCIDs: req.ICCIDs})
if err != nil {
return nil, err
}
@@ -224,18 +223,18 @@ func (s *Service) AllocateCards(ctx context.Context, enterpriseID uint, req *dto
}
now := time.Now()
userType := middleware.GetUserTypeFromContext(ctx)
auths := make([]*model.EnterpriseCardAuthorization, 0)
for _, cardID := range cardIDsToAllocate {
if existingAuths[cardID] {
continue
}
auths = append(auths, &model.EnterpriseCardAuthorization{
EnterpriseID: enterpriseID,
IotCardID: cardID,
ShopID: currentShopID,
AuthorizedBy: currentUserID,
AuthorizedAt: &now,
Status: 1,
EnterpriseID: enterpriseID,
CardID: cardID,
AuthorizedBy: currentUserID,
AuthorizedAt: now,
AuthorizerType: userType,
})
}
@@ -409,7 +408,7 @@ func (s *Service) updateCardNetworkStatus(ctx context.Context, enterpriseID, car
}
auth, err := s.enterpriseCardAuthStore.GetByEnterpriseAndCard(ctx, enterpriseID, cardID)
if err != nil || auth.Status != 1 {
if err != nil || auth.RevokedAt != nil {
return errors.New(errors.CodeForbidden, "无权限操作此卡")
}

View File

@@ -2,8 +2,12 @@ package postgres
import (
"context"
"time"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
pkgGorm "github.com/break/junhong_cmp_fiber/pkg/gorm"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
@@ -31,25 +35,23 @@ func (s *EnterpriseCardAuthorizationStore) BatchCreate(ctx context.Context, auth
return s.db.WithContext(ctx).CreateInBatches(auths, 100).Error
}
func (s *EnterpriseCardAuthorizationStore) UpdateStatus(ctx context.Context, enterpriseID, cardID uint, status int) error {
return s.db.WithContext(ctx).Model(&model.EnterpriseCardAuthorization{}).
Where("enterprise_id = ? AND iot_card_id = ?", enterpriseID, cardID).
Update("status", status).Error
}
func (s *EnterpriseCardAuthorizationStore) BatchUpdateStatus(ctx context.Context, enterpriseID uint, cardIDs []uint, status int) error {
func (s *EnterpriseCardAuthorizationStore) RevokeAuthorizations(ctx context.Context, enterpriseID uint, cardIDs []uint, revokedBy uint) error {
if len(cardIDs) == 0 {
return nil
}
now := time.Now()
return s.db.WithContext(ctx).Model(&model.EnterpriseCardAuthorization{}).
Where("enterprise_id = ? AND iot_card_id IN ?", enterpriseID, cardIDs).
Update("status", status).Error
Where("enterprise_id = ? AND card_id IN ? AND revoked_at IS NULL", enterpriseID, cardIDs).
Updates(map[string]interface{}{
"revoked_by": revokedBy,
"revoked_at": now,
}).Error
}
func (s *EnterpriseCardAuthorizationStore) GetByEnterpriseAndCard(ctx context.Context, enterpriseID, cardID uint) (*model.EnterpriseCardAuthorization, error) {
var auth model.EnterpriseCardAuthorization
err := s.db.WithContext(ctx).
Where("enterprise_id = ? AND iot_card_id = ?", enterpriseID, cardID).
Where("enterprise_id = ? AND card_id = ?", enterpriseID, cardID).
First(&auth).Error
if err != nil {
return nil, err
@@ -57,11 +59,11 @@ func (s *EnterpriseCardAuthorizationStore) GetByEnterpriseAndCard(ctx context.Co
return &auth, nil
}
func (s *EnterpriseCardAuthorizationStore) ListByEnterprise(ctx context.Context, enterpriseID uint, status *int) ([]*model.EnterpriseCardAuthorization, error) {
func (s *EnterpriseCardAuthorizationStore) ListByEnterprise(ctx context.Context, enterpriseID uint, includeRevoked bool) ([]*model.EnterpriseCardAuthorization, error) {
var auths []*model.EnterpriseCardAuthorization
query := s.db.WithContext(ctx).Where("enterprise_id = ?", enterpriseID)
if status != nil {
query = query.Where("status = ?", *status)
if !includeRevoked {
query = query.Where("revoked_at IS NULL")
}
if err := query.Find(&auths).Error; err != nil {
return nil, err
@@ -69,28 +71,326 @@ func (s *EnterpriseCardAuthorizationStore) ListByEnterprise(ctx context.Context,
return auths, nil
}
func (s *EnterpriseCardAuthorizationStore) ListCardIDsByEnterprise(ctx context.Context, enterpriseID uint) ([]uint, error) {
func (s *EnterpriseCardAuthorizationStore) ListByCards(ctx context.Context, cardIDs []uint, includeRevoked bool) ([]*model.EnterpriseCardAuthorization, error) {
if len(cardIDs) == 0 {
return []*model.EnterpriseCardAuthorization{}, nil
}
var auths []*model.EnterpriseCardAuthorization
query := s.db.WithContext(ctx).Where("card_id IN ?", cardIDs)
if !includeRevoked {
query = query.Where("revoked_at IS NULL")
}
if err := query.Find(&auths).Error; err != nil {
return nil, err
}
return auths, nil
}
func (s *EnterpriseCardAuthorizationStore) GetActiveAuthorizedCardIDs(ctx context.Context, enterpriseID uint) ([]uint, error) {
var cardIDs []uint
err := s.db.WithContext(ctx).Model(&model.EnterpriseCardAuthorization{}).
Where("enterprise_id = ? AND status = 1", enterpriseID).
Pluck("iot_card_id", &cardIDs).Error
Where("enterprise_id = ? AND revoked_at IS NULL", enterpriseID).
Pluck("card_id", &cardIDs).Error
return cardIDs, err
}
func (s *EnterpriseCardAuthorizationStore) CheckAuthorizationExists(ctx context.Context, enterpriseID, cardID uint) (bool, error) {
var count int64
err := s.db.WithContext(ctx).Model(&model.EnterpriseCardAuthorization{}).
Where("enterprise_id = ? AND card_id = ? AND revoked_at IS NULL", enterpriseID, cardID).
Count(&count).Error
return count > 0, err
}
type AuthorizationListOptions struct {
EnterpriseID *uint
AuthorizerType *int
AuthorizedBy *uint
IncludeRevoked bool
CardIDs []uint
Offset int
Limit int
}
func (s *EnterpriseCardAuthorizationStore) ListWithOptions(ctx context.Context, opts AuthorizationListOptions) ([]*model.EnterpriseCardAuthorization, int64, error) {
var auths []*model.EnterpriseCardAuthorization
query := s.db.WithContext(ctx).Model(&model.EnterpriseCardAuthorization{})
if opts.EnterpriseID != nil {
query = query.Where("enterprise_id = ?", *opts.EnterpriseID)
}
if opts.AuthorizerType != nil {
query = query.Where("authorizer_type = ?", *opts.AuthorizerType)
}
if opts.AuthorizedBy != nil {
query = query.Where("authorized_by = ?", *opts.AuthorizedBy)
}
if !opts.IncludeRevoked {
query = query.Where("revoked_at IS NULL")
}
if len(opts.CardIDs) > 0 {
query = query.Where("card_id IN ?", opts.CardIDs)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
if opts.Limit > 0 {
query = query.Limit(opts.Limit).Offset(opts.Offset)
}
if err := query.Order("id DESC").Find(&auths).Error; err != nil {
return nil, 0, err
}
return auths, total, nil
}
// GetActiveAuthsByCardIDs 获取指定企业和卡ID列表的有效授权记录返回 map[cardID]bool
func (s *EnterpriseCardAuthorizationStore) GetActiveAuthsByCardIDs(ctx context.Context, enterpriseID uint, cardIDs []uint) (map[uint]bool, error) {
if len(cardIDs) == 0 {
return make(map[uint]bool), nil
}
var auths []model.EnterpriseCardAuthorization
err := s.db.WithContext(ctx).
Where("enterprise_id = ? AND iot_card_id IN ? AND status = 1", enterpriseID, cardIDs).
Find(&auths).Error
var authCardIDs []uint
err := s.db.WithContext(ctx).Model(&model.EnterpriseCardAuthorization{}).
Where("enterprise_id = ? AND card_id IN ? AND revoked_at IS NULL", enterpriseID, cardIDs).
Pluck("card_id", &authCardIDs).Error
if err != nil {
return nil, err
}
result := make(map[uint]bool)
for _, auth := range auths {
result[auth.IotCardID] = true
for _, cardID := range authCardIDs {
result[cardID] = true
}
return result, nil
}
// BatchUpdateStatus 批量更新授权状态(回收授权:设置 revoked_at
func (s *EnterpriseCardAuthorizationStore) BatchUpdateStatus(ctx context.Context, enterpriseID uint, cardIDs []uint, status int) error {
if len(cardIDs) == 0 {
return nil
}
// status 0 表示回收(设置 revoked_at
if status == 0 {
now := time.Now()
return s.db.WithContext(ctx).Model(&model.EnterpriseCardAuthorization{}).
Where("enterprise_id = ? AND card_id IN ? AND revoked_at IS NULL", enterpriseID, cardIDs).
Update("revoked_at", now).Error
}
// 其他状态暂不处理
return nil
}
// ListCardIDsByEnterprise 获取企业的有效授权卡ID列表
func (s *EnterpriseCardAuthorizationStore) ListCardIDsByEnterprise(ctx context.Context, enterpriseID uint) ([]uint, error) {
var cardIDs []uint
err := s.db.WithContext(ctx).Model(&model.EnterpriseCardAuthorization{}).
Where("enterprise_id = ? AND revoked_at IS NULL", enterpriseID).
Pluck("card_id", &cardIDs).Error
return cardIDs, err
}
type AuthorizationWithJoinListOptions struct {
EnterpriseID *uint
ICCID string
AuthorizerType *int
Status *int
StartTime *time.Time
EndTime *time.Time
Offset int
Limit int
}
type AuthorizationWithJoin struct {
ID uint `gorm:"column:id"`
EnterpriseID uint `gorm:"column:enterprise_id"`
EnterpriseName string `gorm:"column:enterprise_name"`
CardID uint `gorm:"column:card_id"`
ICCID string `gorm:"column:iccid"`
MSISDN string `gorm:"column:msisdn"`
AuthorizedBy uint `gorm:"column:authorized_by"`
AuthorizerName string `gorm:"column:authorizer_name"`
AuthorizerType int `gorm:"column:authorizer_type"`
AuthorizedAt time.Time `gorm:"column:authorized_at"`
RevokedBy *uint `gorm:"column:revoked_by"`
RevokerName *string `gorm:"column:revoker_name"`
RevokedAt *time.Time `gorm:"column:revoked_at"`
Remark string `gorm:"column:remark"`
}
func (s *EnterpriseCardAuthorizationStore) ListWithJoin(ctx context.Context, opts AuthorizationWithJoinListOptions) ([]AuthorizationWithJoin, int64, error) {
baseQuery := `
FROM tb_enterprise_card_authorization a
LEFT JOIN tb_enterprise e ON a.enterprise_id = e.id AND e.deleted_at IS NULL
LEFT JOIN tb_iot_card c ON a.card_id = c.id AND c.deleted_at IS NULL
LEFT JOIN tb_account acc1 ON a.authorized_by = acc1.id AND acc1.deleted_at IS NULL
LEFT JOIN tb_account acc2 ON a.revoked_by = acc2.id AND acc2.deleted_at IS NULL
WHERE a.deleted_at IS NULL
`
args := []interface{}{}
// 数据权限过滤(原生 SQL 需要手动处理)
// 检查是否跳过数据权限过滤
if skip, ok := ctx.Value(pkgGorm.SkipDataPermissionKey).(bool); !ok || !skip {
userType := middleware.GetUserTypeFromContext(ctx)
// 超级管理员和平台用户跳过过滤
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
if userType == constants.UserTypeAgent {
shopID := middleware.GetShopIDFromContext(ctx)
if shopID == 0 {
// 代理用户没有 shop_id返回空结果
return []AuthorizationWithJoin{}, 0, nil
}
// 只能看到自己店铺下企业的授权记录(不包含下级店铺)
baseQuery += " AND a.enterprise_id IN (SELECT id FROM tb_enterprise WHERE owner_shop_id = ? AND deleted_at IS NULL)"
args = append(args, shopID)
} else if userType == constants.UserTypeEnterprise {
enterpriseID := middleware.GetEnterpriseIDFromContext(ctx)
if enterpriseID == 0 {
return []AuthorizationWithJoin{}, 0, nil
}
baseQuery += " AND a.enterprise_id = ?"
args = append(args, enterpriseID)
} else {
// 其他用户类型(个人客户等)不应访问授权记录
return []AuthorizationWithJoin{}, 0, nil
}
}
}
if opts.EnterpriseID != nil {
baseQuery += " AND a.enterprise_id = ?"
args = append(args, *opts.EnterpriseID)
}
if opts.ICCID != "" {
baseQuery += " AND c.iccid LIKE ?"
args = append(args, "%"+opts.ICCID+"%")
}
if opts.AuthorizerType != nil {
baseQuery += " AND a.authorizer_type = ?"
args = append(args, *opts.AuthorizerType)
}
if opts.Status != nil {
if *opts.Status == 1 {
baseQuery += " AND a.revoked_at IS NULL"
} else {
baseQuery += " AND a.revoked_at IS NOT NULL"
}
}
if opts.StartTime != nil {
baseQuery += " AND a.authorized_at >= ?"
args = append(args, *opts.StartTime)
}
if opts.EndTime != nil {
baseQuery += " AND a.authorized_at < ?"
args = append(args, *opts.EndTime)
}
var total int64
countSQL := "SELECT COUNT(*) " + baseQuery
if err := s.db.WithContext(ctx).Raw(countSQL, args...).Scan(&total).Error; err != nil {
return nil, 0, err
}
selectSQL := `
SELECT
a.id, a.enterprise_id, COALESCE(e.enterprise_name, '') as enterprise_name,
a.card_id, COALESCE(c.iccid, '') as iccid, COALESCE(c.msisdn, '') as msisdn,
a.authorized_by, COALESCE(acc1.username, '') as authorizer_name,
a.authorizer_type, a.authorized_at,
a.revoked_by, acc2.username as revoker_name, a.revoked_at,
COALESCE(a.remark, '') as remark
` + baseQuery + " ORDER BY a.id DESC"
if opts.Limit > 0 {
selectSQL += " LIMIT ? OFFSET ?"
args = append(args, opts.Limit, opts.Offset)
}
var results []AuthorizationWithJoin
if err := s.db.WithContext(ctx).Raw(selectSQL, args...).Scan(&results).Error; err != nil {
return nil, 0, err
}
return results, total, nil
}
func (s *EnterpriseCardAuthorizationStore) GetByIDWithJoin(ctx context.Context, id uint) (*AuthorizationWithJoin, error) {
baseSQL := `
SELECT
a.id, a.enterprise_id, COALESCE(e.enterprise_name, '') as enterprise_name,
a.card_id, COALESCE(c.iccid, '') as iccid, COALESCE(c.msisdn, '') as msisdn,
a.authorized_by, COALESCE(acc1.username, '') as authorizer_name,
a.authorizer_type, a.authorized_at,
a.revoked_by, acc2.username as revoker_name, a.revoked_at,
COALESCE(a.remark, '') as remark
FROM tb_enterprise_card_authorization a
LEFT JOIN tb_enterprise e ON a.enterprise_id = e.id AND e.deleted_at IS NULL
LEFT JOIN tb_iot_card c ON a.card_id = c.id AND c.deleted_at IS NULL
LEFT JOIN tb_account acc1 ON a.authorized_by = acc1.id AND acc1.deleted_at IS NULL
LEFT JOIN tb_account acc2 ON a.revoked_by = acc2.id AND acc2.deleted_at IS NULL
WHERE a.id = ? AND a.deleted_at IS NULL
`
args := []interface{}{id}
// 数据权限过滤(原生 SQL 需要手动处理)
if skip, ok := ctx.Value(pkgGorm.SkipDataPermissionKey).(bool); !ok || !skip {
userType := middleware.GetUserTypeFromContext(ctx)
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
if userType == constants.UserTypeAgent {
shopID := middleware.GetShopIDFromContext(ctx)
if shopID == 0 {
return nil, gorm.ErrRecordNotFound
}
baseSQL += " AND a.enterprise_id IN (SELECT id FROM tb_enterprise WHERE owner_shop_id = ? AND deleted_at IS NULL)"
args = append(args, shopID)
} else if userType == constants.UserTypeEnterprise {
enterpriseID := middleware.GetEnterpriseIDFromContext(ctx)
if enterpriseID == 0 {
return nil, gorm.ErrRecordNotFound
}
baseSQL += " AND a.enterprise_id = ?"
args = append(args, enterpriseID)
} else {
return nil, gorm.ErrRecordNotFound
}
}
}
var result AuthorizationWithJoin
if err := s.db.WithContext(ctx).Raw(baseSQL, args...).Scan(&result).Error; err != nil {
return nil, err
}
if result.ID == 0 {
return nil, gorm.ErrRecordNotFound
}
return &result, nil
}
func (s *EnterpriseCardAuthorizationStore) UpdateRemark(ctx context.Context, id uint, remark string) error {
result := s.db.WithContext(ctx).Model(&model.EnterpriseCardAuthorization{}).
Where("id = ?", id).
Update("remark", remark)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}
func (s *EnterpriseCardAuthorizationStore) GetByID(ctx context.Context, id uint) (*model.EnterpriseCardAuthorization, error) {
var auth model.EnterpriseCardAuthorization
err := s.db.WithContext(ctx).Where("id = ?", id).First(&auth).Error
if err != nil {
return nil, err
}
return &auth, nil
}

View File

@@ -49,6 +49,17 @@ func (s *IotCardStore) GetByICCID(ctx context.Context, iccid string) (*model.Iot
return &card, nil
}
func (s *IotCardStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.IotCard, error) {
if len(ids) == 0 {
return []*model.IotCard{}, nil
}
var cards []*model.IotCard
if err := s.db.WithContext(ctx).Where("id IN ?", ids).Find(&cards).Error; err != nil {
return nil, err
}
return cards, nil
}
func (s *IotCardStore) ExistsByICCID(ctx context.Context, iccid string) (bool, error) {
var count int64
if err := s.db.WithContext(ctx).Model(&model.IotCard{}).Where("iccid = ?", iccid).Count(&count).Error; err != nil {
@@ -84,6 +95,89 @@ func (s *IotCardStore) Delete(ctx context.Context, id uint) error {
return s.db.WithContext(ctx).Delete(&model.IotCard{}, id).Error
}
func (s *IotCardStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]any) ([]*model.IotCard, int64, error) {
var cards []*model.IotCard
var total int64
query := s.db.WithContext(ctx).Model(&model.IotCard{})
// 企业用户特殊处理:只能看到授权给自己的卡
if enterpriseID, ok := filters["authorized_enterprise_id"].(uint); ok && enterpriseID > 0 {
query = query.Where("id IN (?)",
s.db.Table("tb_enterprise_card_authorization").
Select("card_id").
Where("enterprise_id = ? AND revoked_at IS NULL AND deleted_at IS NULL", enterpriseID))
}
// 基础过滤条件
if status, ok := filters["status"].(int); ok && status > 0 {
query = query.Where("status = ?", status)
}
if carrierID, ok := filters["carrier_id"].(uint); ok && carrierID > 0 {
query = query.Where("carrier_id = ?", carrierID)
}
if shopID, ok := filters["shop_id"].(uint); ok && shopID > 0 {
query = query.Where("shop_id = ?", shopID)
}
if iccid, ok := filters["iccid"].(string); ok && iccid != "" {
query = query.Where("iccid LIKE ?", "%"+iccid+"%")
}
if msisdn, ok := filters["msisdn"].(string); ok && msisdn != "" {
query = query.Where("msisdn LIKE ?", "%"+msisdn+"%")
}
if batchNo, ok := filters["batch_no"].(string); ok && batchNo != "" {
query = query.Where("batch_no = ?", batchNo)
}
if packageID, ok := filters["package_id"].(uint); ok && packageID > 0 {
query = query.Where("id IN (?)",
s.db.Table("tb_package_usage").
Select("iot_card_id").
Where("package_id = ? AND deleted_at IS NULL", packageID))
}
if isDistributed, ok := filters["is_distributed"].(bool); ok {
if isDistributed {
query = query.Where("shop_id IS NOT NULL")
} else {
query = query.Where("shop_id IS NULL")
}
}
if iccidStart, ok := filters["iccid_start"].(string); ok && iccidStart != "" {
query = query.Where("iccid >= ?", iccidStart)
}
if iccidEnd, ok := filters["iccid_end"].(string); ok && iccidEnd != "" {
query = query.Where("iccid <= ?", iccidEnd)
}
// 统计总数
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
// 分页处理
if opts == nil {
opts = &store.QueryOptions{
Page: 1,
PageSize: constants.DefaultPageSize,
}
}
offset := (opts.Page - 1) * opts.PageSize
query = query.Offset(offset).Limit(opts.PageSize)
// 排序
if opts.OrderBy != "" {
query = query.Order(opts.OrderBy)
} else {
query = query.Order("created_at DESC")
}
// 查询结果
if err := query.Find(&cards).Error; err != nil {
return nil, 0, err
}
return cards, total, nil
}
func (s *IotCardStore) ListStandalone(ctx context.Context, opts *store.QueryOptions, filters map[string]any) ([]*model.IotCard, int64, error) {
var cards []*model.IotCard
var total int64
@@ -251,10 +345,32 @@ func (s *IotCardStore) GetBoundCardIDs(ctx context.Context, cardIDs []uint) ([]u
if len(cardIDs) == 0 {
return nil, nil
}
var boundCardIDs []uint
var boundIDs []uint
err := s.db.WithContext(ctx).Model(&model.DeviceSimBinding{}).
Select("iot_card_id").
Where("iot_card_id IN ? AND bind_status = ?", cardIDs, 1).
Pluck("iot_card_id", &boundCardIDs).Error
return boundCardIDs, err
Pluck("iot_card_id", &boundIDs).Error
return boundIDs, err
}
func (s *IotCardStore) GetByIDsWithEnterpriseFilter(ctx context.Context, cardIDs []uint, enterpriseID *uint) ([]*model.IotCard, error) {
if len(cardIDs) == 0 {
return []*model.IotCard{}, nil
}
query := s.db.WithContext(ctx).Model(&model.IotCard{})
if enterpriseID != nil && *enterpriseID > 0 {
query = query.Where("id IN (?) AND id IN (?)",
cardIDs,
s.db.Table("tb_enterprise_card_authorization").
Select("card_id").
Where("enterprise_id = ? AND revoked_at IS NULL AND deleted_at IS NULL", *enterpriseID))
} else {
query = query.Where("id IN ?", cardIDs)
}
var cards []*model.IotCard
if err := query.Find(&cards).Error; err != nil {
return nil, err
}
return cards, nil
}