实现个人客户微信认证和短信验证功能
- 添加个人客户微信登录和手机验证码登录接口 - 实现个人客户设备、ICCID、手机号关联管理 - 添加短信发送服务(HTTP 客户端) - 添加微信认证服务(含 mock 实现) - 添加 JWT Token 生成和验证工具 - 创建数据库迁移脚本(personal_customer 关联表) - 修复测试文件中的路由注册参数错误 - 重构 scripts 目录结构(分离独立脚本到子目录) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
117
internal/store/postgres/personal_customer_device_store.go
Normal file
117
internal/store/postgres/personal_customer_device_store.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PersonalCustomerDeviceStore 个人客户设备号绑定数据访问层
|
||||
type PersonalCustomerDeviceStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPersonalCustomerDeviceStore 创建个人客户设备号 Store
|
||||
func NewPersonalCustomerDeviceStore(db *gorm.DB) *PersonalCustomerDeviceStore {
|
||||
return &PersonalCustomerDeviceStore{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建设备号绑定记录
|
||||
func (s *PersonalCustomerDeviceStore) Create(ctx context.Context, record *model.PersonalCustomerDevice) error {
|
||||
now := time.Now()
|
||||
record.BindAt = now
|
||||
record.LastUsedAt = now
|
||||
return s.db.WithContext(ctx).Create(record).Error
|
||||
}
|
||||
|
||||
// GetByCustomerID 根据客户 ID 获取所有设备号绑定记录
|
||||
func (s *PersonalCustomerDeviceStore) GetByCustomerID(ctx context.Context, customerID uint) ([]*model.PersonalCustomerDevice, error) {
|
||||
var records []*model.PersonalCustomerDevice
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("customer_id = ?", customerID).
|
||||
Order("last_used_at DESC").
|
||||
Find(&records).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// GetByDeviceNo 根据设备号获取所有绑定记录(查询哪些用户使用过这个设备)
|
||||
func (s *PersonalCustomerDeviceStore) GetByDeviceNo(ctx context.Context, deviceNo string) ([]*model.PersonalCustomerDevice, error) {
|
||||
var records []*model.PersonalCustomerDevice
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("device_no = ?", deviceNo).
|
||||
Order("last_used_at DESC").
|
||||
Find(&records).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// GetByCustomerAndDevice 根据客户 ID 和设备号获取绑定记录
|
||||
func (s *PersonalCustomerDeviceStore) GetByCustomerAndDevice(ctx context.Context, customerID uint, deviceNo string) (*model.PersonalCustomerDevice, error) {
|
||||
var record model.PersonalCustomerDevice
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("customer_id = ? AND device_no = ?", customerID, deviceNo).
|
||||
First(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// UpdateLastUsedAt 更新最后使用时间
|
||||
func (s *PersonalCustomerDeviceStore) UpdateLastUsedAt(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.PersonalCustomerDevice{}).
|
||||
Where("id = ?", id).
|
||||
Update("last_used_at", time.Now()).Error
|
||||
}
|
||||
|
||||
// UpdateStatus 更新状态
|
||||
func (s *PersonalCustomerDeviceStore) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.PersonalCustomerDevice{}).
|
||||
Where("id = ?", id).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// Delete 软删除绑定记录
|
||||
func (s *PersonalCustomerDeviceStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.PersonalCustomerDevice{}, id).Error
|
||||
}
|
||||
|
||||
// ExistsByCustomerAndDevice 检查客户是否已绑定该设备
|
||||
func (s *PersonalCustomerDeviceStore) ExistsByCustomerAndDevice(ctx context.Context, customerID uint, deviceNo string) (bool, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.PersonalCustomerDevice{}).
|
||||
Where("customer_id = ? AND device_no = ? AND status = ?", customerID, deviceNo, 1).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateLastUsed 创建或更新绑定记录的最后使用时间
|
||||
// 如果绑定记录存在,更新最后使用时间;如果不存在,创建新记录
|
||||
func (s *PersonalCustomerDeviceStore) CreateOrUpdateLastUsed(ctx context.Context, customerID uint, deviceNo string) error {
|
||||
record, err := s.GetByCustomerAndDevice(ctx, customerID, deviceNo)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// 不存在,创建新记录
|
||||
newRecord := &model.PersonalCustomerDevice{
|
||||
CustomerID: customerID,
|
||||
DeviceNo: deviceNo,
|
||||
Status: 1, // 启用
|
||||
}
|
||||
return s.Create(ctx, newRecord)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 存在,更新最后使用时间
|
||||
return s.UpdateLastUsedAt(ctx, record.ID)
|
||||
}
|
||||
117
internal/store/postgres/personal_customer_iccid_store.go
Normal file
117
internal/store/postgres/personal_customer_iccid_store.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PersonalCustomerICCIDStore 个人客户 ICCID 绑定数据访问层
|
||||
type PersonalCustomerICCIDStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPersonalCustomerICCIDStore 创建个人客户 ICCID Store
|
||||
func NewPersonalCustomerICCIDStore(db *gorm.DB) *PersonalCustomerICCIDStore {
|
||||
return &PersonalCustomerICCIDStore{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建 ICCID 绑定记录
|
||||
func (s *PersonalCustomerICCIDStore) Create(ctx context.Context, record *model.PersonalCustomerICCID) error {
|
||||
now := time.Now()
|
||||
record.BindAt = now
|
||||
record.LastUsedAt = now
|
||||
return s.db.WithContext(ctx).Create(record).Error
|
||||
}
|
||||
|
||||
// GetByCustomerID 根据客户 ID 获取所有 ICCID 绑定记录
|
||||
func (s *PersonalCustomerICCIDStore) GetByCustomerID(ctx context.Context, customerID uint) ([]*model.PersonalCustomerICCID, error) {
|
||||
var records []*model.PersonalCustomerICCID
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("customer_id = ?", customerID).
|
||||
Order("last_used_at DESC").
|
||||
Find(&records).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// GetByICCID 根据 ICCID 获取所有绑定记录(查询哪些用户使用过这个 ICCID)
|
||||
func (s *PersonalCustomerICCIDStore) GetByICCID(ctx context.Context, iccid string) ([]*model.PersonalCustomerICCID, error) {
|
||||
var records []*model.PersonalCustomerICCID
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("iccid = ?", iccid).
|
||||
Order("last_used_at DESC").
|
||||
Find(&records).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// GetByCustomerAndICCID 根据客户 ID 和 ICCID 获取绑定记录
|
||||
func (s *PersonalCustomerICCIDStore) GetByCustomerAndICCID(ctx context.Context, customerID uint, iccid string) (*model.PersonalCustomerICCID, error) {
|
||||
var record model.PersonalCustomerICCID
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("customer_id = ? AND iccid = ?", customerID, iccid).
|
||||
First(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// UpdateLastUsedAt 更新最后使用时间
|
||||
func (s *PersonalCustomerICCIDStore) UpdateLastUsedAt(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.PersonalCustomerICCID{}).
|
||||
Where("id = ?", id).
|
||||
Update("last_used_at", time.Now()).Error
|
||||
}
|
||||
|
||||
// UpdateStatus 更新状态
|
||||
func (s *PersonalCustomerICCIDStore) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.PersonalCustomerICCID{}).
|
||||
Where("id = ?", id).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// Delete 软删除绑定记录
|
||||
func (s *PersonalCustomerICCIDStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.PersonalCustomerICCID{}, id).Error
|
||||
}
|
||||
|
||||
// ExistsByCustomerAndICCID 检查客户是否已绑定该 ICCID
|
||||
func (s *PersonalCustomerICCIDStore) ExistsByCustomerAndICCID(ctx context.Context, customerID uint, iccid string) (bool, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.PersonalCustomerICCID{}).
|
||||
Where("customer_id = ? AND iccid = ? AND status = ?", customerID, iccid, 1).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateLastUsed 创建或更新绑定记录的最后使用时间
|
||||
// 如果绑定记录存在,更新最后使用时间;如果不存在,创建新记录
|
||||
func (s *PersonalCustomerICCIDStore) CreateOrUpdateLastUsed(ctx context.Context, customerID uint, iccid string) error {
|
||||
record, err := s.GetByCustomerAndICCID(ctx, customerID, iccid)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// 不存在,创建新记录
|
||||
newRecord := &model.PersonalCustomerICCID{
|
||||
CustomerID: customerID,
|
||||
ICCID: iccid,
|
||||
Status: 1, // 启用
|
||||
}
|
||||
return s.Create(ctx, newRecord)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 存在,更新最后使用时间
|
||||
return s.UpdateLastUsedAt(ctx, record.ID)
|
||||
}
|
||||
120
internal/store/postgres/personal_customer_phone_store.go
Normal file
120
internal/store/postgres/personal_customer_phone_store.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PersonalCustomerPhoneStore 个人客户手机号数据访问层
|
||||
type PersonalCustomerPhoneStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPersonalCustomerPhoneStore 创建个人客户手机号 Store
|
||||
func NewPersonalCustomerPhoneStore(db *gorm.DB) *PersonalCustomerPhoneStore {
|
||||
return &PersonalCustomerPhoneStore{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建手机号绑定记录
|
||||
func (s *PersonalCustomerPhoneStore) Create(ctx context.Context, phone *model.PersonalCustomerPhone) error {
|
||||
phone.VerifiedAt = time.Now()
|
||||
return s.db.WithContext(ctx).Create(phone).Error
|
||||
}
|
||||
|
||||
// GetByCustomerID 根据客户 ID 获取所有手机号
|
||||
func (s *PersonalCustomerPhoneStore) GetByCustomerID(ctx context.Context, customerID uint) ([]*model.PersonalCustomerPhone, error) {
|
||||
var phones []*model.PersonalCustomerPhone
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("customer_id = ?", customerID).
|
||||
Order("is_primary DESC, created_at DESC").
|
||||
Find(&phones).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return phones, nil
|
||||
}
|
||||
|
||||
// GetPrimaryPhone 获取客户的主手机号
|
||||
func (s *PersonalCustomerPhoneStore) GetPrimaryPhone(ctx context.Context, customerID uint) (*model.PersonalCustomerPhone, error) {
|
||||
var phone model.PersonalCustomerPhone
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("customer_id = ? AND is_primary = ? AND status = ?", customerID, true, 1).
|
||||
First(&phone).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &phone, nil
|
||||
}
|
||||
|
||||
// GetByPhone 根据手机号查询绑定记录
|
||||
func (s *PersonalCustomerPhoneStore) GetByPhone(ctx context.Context, phone string) (*model.PersonalCustomerPhone, error) {
|
||||
var record model.PersonalCustomerPhone
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("phone = ? AND status = ?", phone, 1).
|
||||
First(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// SetPrimary 设置主手机号
|
||||
// 将指定的手机号设置为主号,同时将该客户的其他手机号设置为非主号
|
||||
func (s *PersonalCustomerPhoneStore) SetPrimary(ctx context.Context, id uint, customerID uint) error {
|
||||
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 将该客户的所有手机号设置为非主号
|
||||
if err := tx.Model(&model.PersonalCustomerPhone{}).
|
||||
Where("customer_id = ?", customerID).
|
||||
Update("is_primary", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 将指定的手机号设置为主号
|
||||
if err := tx.Model(&model.PersonalCustomerPhone{}).
|
||||
Where("id = ? AND customer_id = ?", id, customerID).
|
||||
Update("is_primary", true).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateStatus 更新手机号状态
|
||||
func (s *PersonalCustomerPhoneStore) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.PersonalCustomerPhone{}).
|
||||
Where("id = ?", id).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// Delete 软删除手机号
|
||||
func (s *PersonalCustomerPhoneStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.PersonalCustomerPhone{}, id).Error
|
||||
}
|
||||
|
||||
// ExistsByPhone 检查手机号是否已被绑定
|
||||
func (s *PersonalCustomerPhoneStore) ExistsByPhone(ctx context.Context, phone string) (bool, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.PersonalCustomerPhone{}).
|
||||
Where("phone = ? AND status = ?", phone, 1).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// ExistsByCustomerAndPhone 检查某个客户是否已绑定该手机号
|
||||
func (s *PersonalCustomerPhoneStore) ExistsByCustomerAndPhone(ctx context.Context, customerID uint, phone string) (bool, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.PersonalCustomerPhone{}).
|
||||
Where("customer_id = ? AND phone = ? AND status = ?", customerID, phone, 1).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
@@ -39,9 +39,16 @@ func (s *PersonalCustomerStore) GetByID(ctx context.Context, id uint) (*model.Pe
|
||||
}
|
||||
|
||||
// GetByPhone 根据手机号获取个人客户
|
||||
// 注意:由于 PersonalCustomer 不再直接存储手机号,此方法需要通过 PersonalCustomerPhone 关联表查询
|
||||
func (s *PersonalCustomerStore) GetByPhone(ctx context.Context, phone string) (*model.PersonalCustomer, error) {
|
||||
var customerPhone model.PersonalCustomerPhone
|
||||
if err := s.db.WithContext(ctx).Where("phone = ?", phone).First(&customerPhone).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 查询关联的个人客户
|
||||
var customer model.PersonalCustomer
|
||||
if err := s.db.WithContext(ctx).Where("phone = ?", phone).First(&customer).Error; err != nil {
|
||||
if err := s.db.WithContext(ctx).First(&customer, customerPhone.CustomerID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &customer, nil
|
||||
@@ -83,9 +90,8 @@ func (s *PersonalCustomerStore) List(ctx context.Context, opts *store.QueryOptio
|
||||
query := s.db.WithContext(ctx).Model(&model.PersonalCustomer{})
|
||||
|
||||
// 应用过滤条件
|
||||
if phone, ok := filters["phone"].(string); ok && phone != "" {
|
||||
query = query.Where("phone LIKE ?", "%"+phone+"%")
|
||||
}
|
||||
// 注意:phone 过滤需要通过关联表查询,这里先移除该过滤条件
|
||||
// TODO: 如果需要按手机号过滤,需要通过 JOIN PersonalCustomerPhone 表实现
|
||||
if nickname, ok := filters["nickname"].(string); ok && nickname != "" {
|
||||
query = query.Where("nickname LIKE ?", "%"+nickname+"%")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user