feat: 实现企业卡授权和授权记录管理功能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m9s
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:
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
157
internal/handler/admin/authorization.go
Normal file
157
internal/handler/admin/authorization.go
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
48
internal/model/dto/authorization_dto.go
Normal file
48
internal/model/dto/authorization_dto.go
Normal 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字)"`
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
38
internal/routes/authorization.go
Normal file
38
internal/routes/authorization.go
Normal 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,
|
||||
})
|
||||
}
|
||||
@@ -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{"企业卡授权"},
|
||||
|
||||
414
internal/service/enterprise_card/authorization_service.go
Normal file
414
internal/service/enterprise_card/authorization_service.go
Normal 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)
|
||||
}
|
||||
@@ -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, "无权限操作此卡")
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user