Files
junhong_cmp_fiber/internal/store/postgres/device_store.go
huang b9c3875c08
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s
feat: 新增数据库迁移,重命名 device_no 为 virtual_no,新增 iot_card.virtual_no 和 package.virtual_ratio 字段
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-14 18:27:28 +08:00

246 lines
7.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package postgres
import (
"context"
"time"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type DeviceStore struct {
db *gorm.DB
redis *redis.Client
}
func NewDeviceStore(db *gorm.DB, redis *redis.Client) *DeviceStore {
return &DeviceStore{
db: db,
redis: redis,
}
}
func (s *DeviceStore) Create(ctx context.Context, device *model.Device) error {
return s.db.WithContext(ctx).Create(device).Error
}
func (s *DeviceStore) CreateBatch(ctx context.Context, devices []*model.Device) error {
if len(devices) == 0 {
return nil
}
return s.db.WithContext(ctx).CreateInBatches(devices, 100).Error
}
func (s *DeviceStore) GetByID(ctx context.Context, id uint) (*model.Device, error) {
var device model.Device
query := s.db.WithContext(ctx).Where("id = ?", id)
// 应用数据权限过滤NULL shop_id 对代理用户不可见)
query = middleware.ApplyShopFilter(ctx, query)
if err := query.First(&device).Error; err != nil {
return nil, err
}
return &device, nil
}
func (s *DeviceStore) GetByDeviceNo(ctx context.Context, deviceNo string) (*model.Device, error) {
var device model.Device
query := s.db.WithContext(ctx).Where("virtual_no = ?", deviceNo)
// 应用数据权限过滤NULL shop_id 对代理用户不可见)
query = middleware.ApplyShopFilter(ctx, query)
if err := query.First(&device).Error; err != nil {
return nil, err
}
return &device, nil
}
// GetByIdentifier 通过任意标识符查找设备
// 支持 virtual_no虚拟号、imei、sn 三个字段的自动匹配
func (s *DeviceStore) GetByIdentifier(ctx context.Context, identifier string) (*model.Device, error) {
var device model.Device
query := s.db.WithContext(ctx).Where("virtual_no = ? OR imei = ? OR sn = ?", identifier, identifier, identifier)
// 应用数据权限过滤NULL shop_id 对代理用户不可见)
query = middleware.ApplyShopFilter(ctx, query)
if err := query.First(&device).Error; err != nil {
return nil, err
}
return &device, nil
}
func (s *DeviceStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.Device, error) {
var devices []*model.Device
if len(ids) == 0 {
return devices, nil
}
query := s.db.WithContext(ctx).Where("id IN ?", ids)
// 应用数据权限过滤NULL shop_id 对代理用户不可见)
query = middleware.ApplyShopFilter(ctx, query)
if err := query.Find(&devices).Error; err != nil {
return nil, err
}
return devices, nil
}
func (s *DeviceStore) Update(ctx context.Context, device *model.Device) error {
return s.db.WithContext(ctx).Save(device).Error
}
func (s *DeviceStore) Delete(ctx context.Context, id uint) error {
return s.db.WithContext(ctx).Delete(&model.Device{}, id).Error
}
func (s *DeviceStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]any) ([]*model.Device, int64, error) {
var devices []*model.Device
var total int64
query := s.db.WithContext(ctx).Model(&model.Device{})
// 应用数据权限过滤NULL shop_id 对代理用户不可见)
query = middleware.ApplyShopFilter(ctx, query)
if virtualNo, ok := filters["virtual_no"].(string); ok && virtualNo != "" {
query = query.Where("virtual_no LIKE ?", "%"+virtualNo+"%")
}
if deviceName, ok := filters["device_name"].(string); ok && deviceName != "" {
query = query.Where("device_name LIKE ?", "%"+deviceName+"%")
}
if status, ok := filters["status"].(int); ok && status > 0 {
query = query.Where("status = ?", status)
}
if shopID, ok := filters["shop_id"].(*uint); ok {
if shopID == nil {
query = query.Where("shop_id IS NULL")
} else {
query = query.Where("shop_id = ?", *shopID)
}
}
if batchNo, ok := filters["batch_no"].(string); ok && batchNo != "" {
query = query.Where("batch_no = ?", batchNo)
}
if deviceType, ok := filters["device_type"].(string); ok && deviceType != "" {
query = query.Where("device_type = ?", deviceType)
}
if manufacturer, ok := filters["manufacturer"].(string); ok && manufacturer != "" {
query = query.Where("manufacturer LIKE ?", "%"+manufacturer+"%")
}
if createdAtStart, ok := filters["created_at_start"].(time.Time); ok && !createdAtStart.IsZero() {
query = query.Where("created_at >= ?", createdAtStart)
}
if createdAtEnd, ok := filters["created_at_end"].(time.Time); ok && !createdAtEnd.IsZero() {
query = query.Where("created_at <= ?", createdAtEnd)
}
if seriesID, ok := filters["series_id"].(uint); ok && seriesID > 0 {
query = query.Where("series_id = ?", seriesID)
}
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(&devices).Error; err != nil {
return nil, 0, err
}
return devices, total, nil
}
func (s *DeviceStore) UpdateShopID(ctx context.Context, id uint, shopID *uint) error {
return s.db.WithContext(ctx).Model(&model.Device{}).Where("id = ?", id).Update("shop_id", shopID).Error
}
func (s *DeviceStore) BatchUpdateShopIDAndStatus(ctx context.Context, ids []uint, shopID *uint, status int) error {
if len(ids) == 0 {
return nil
}
updates := map[string]any{
"shop_id": shopID,
"status": status,
"updated_at": time.Now(),
}
return s.db.WithContext(ctx).Model(&model.Device{}).Where("id IN ?", ids).Updates(updates).Error
}
func (s *DeviceStore) ExistsByDeviceNoBatch(ctx context.Context, deviceNos []string) (map[string]bool, error) {
result := make(map[string]bool)
if len(deviceNos) == 0 {
return result, nil
}
var existingDevices []struct {
VirtualNo string
}
if err := s.db.WithContext(ctx).Model(&model.Device{}).
Select("virtual_no").
Where("virtual_no IN ?", deviceNos).
Find(&existingDevices).Error; err != nil {
return nil, err
}
for _, d := range existingDevices {
result[d.VirtualNo] = true
}
return result, nil
}
func (s *DeviceStore) GetByDeviceNos(ctx context.Context, deviceNos []string) ([]*model.Device, error) {
var devices []*model.Device
if len(deviceNos) == 0 {
return devices, nil
}
query := s.db.WithContext(ctx).Where("virtual_no IN ?", deviceNos)
// 应用数据权限过滤NULL shop_id 对代理用户不可见)
query = middleware.ApplyShopFilter(ctx, query)
if err := query.Find(&devices).Error; err != nil {
return nil, err
}
return devices, nil
}
// BatchUpdateSeriesID 批量更新设备的套餐系列ID
func (s *DeviceStore) BatchUpdateSeriesID(ctx context.Context, deviceIDs []uint, seriesID *uint) error {
if len(deviceIDs) == 0 {
return nil
}
return s.db.WithContext(ctx).Model(&model.Device{}).
Where("id IN ?", deviceIDs).
Update("series_id", seriesID).Error
}
// ListBySeriesID 根据套餐系列ID查询设备列表
func (s *DeviceStore) ListBySeriesID(ctx context.Context, seriesID uint) ([]*model.Device, error) {
var devices []*model.Device
query := s.db.WithContext(ctx).Where("series_id = ?", seriesID)
// 应用数据权限过滤NULL shop_id 对代理用户不可见)
query = middleware.ApplyShopFilter(ctx, query)
if err := query.Find(&devices).Error; err != nil {
return nil, err
}
return devices, nil
}
func (s *DeviceStore) UpdateRechargeTrackingFields(ctx context.Context, deviceID uint, accumulatedJSON, triggeredJSON string) error {
return s.db.WithContext(ctx).Model(&model.Device{}).
Where("id = ?", deviceID).
Updates(map[string]interface{}{
"accumulated_recharge_by_series": accumulatedJSON,
"first_recharge_triggered_by_series": triggeredJSON,
}).Error
}