package dao import ( "errors" "fmt" "github.com/sirupsen/logrus" "gorm.io/gorm" "hy2xs-admin/model/constant" "hy2xs-admin/model/dto" "hy2xs-admin/model/entity" "time" ) // PeerTx — операции над пирами в границах одной единицы работы. // // Интерфейс существует, чтобы транзакция была доступна слою service, но // *gorm.DB туда не протекал: выбор ORM остаётся деталью dao. Реализация одна и // та же и для глобального соединения, и для транзакции — за счёт этого // невозможна ситуация, когда «обычный» путь и «транзакционный» расходятся в // поведении. type PeerTx interface { GetPeer(query interface{}, args ...interface{}) (entity.Peer, error) SavePeer(peer entity.Peer) (int64, error) UpdatePeer(ids []int64, updates map[string]interface{}) error } type peerTx struct { db *gorm.DB } // WithPeerTx выполняет fn в одной транзакции: либо применяются все записи, // либо ни одной. // // Что было. Импорт пиров валидировал партию целиком до первой записи, но сами // записи шли по одной, каждая своим оператором. Комментарий обещал «либо // целиком, либо никак», а на практике UNIQUE-конфликт на 37-й записи оставлял // 36 применённых. Конфликт при этом совершенно реальный: если в базе есть // A(auth_id=a, name=alice) и B(auth_id=b, name=bob), а импорт несёт // (auth_id=a, name=bob), то поиск найдёт A по auth_id и попытается // переименовать его в bob — прямо в UNIQUE(name). func WithPeerTx(fn func(tx PeerTx) error) error { if sqliteDB == nil { return errors.New("sqlite is not initialized") } return sqliteDB.Transaction(func(tx *gorm.DB) error { return fn(peerTx{db: tx}) }) } func (p peerTx) SavePeer(peer entity.Peer) (int64, error) { if tx := p.db.Save(&peer); tx.Error != nil { logrus.Errorf("%v", tx.Error) return 0, errors.New(constant.SysError) } return *peer.Id, nil } func (p peerTx) UpdatePeer(ids []int64, updates map[string]interface{}) error { if len(updates) == 0 { return nil } updates["update_time"] = time.Now().Format("2006-01-02 15:04:05") if tx := p.db.Model(&entity.Peer{}).Where("id in ?", ids).Updates(updates); tx.Error != nil { logrus.Errorf("%v", tx.Error) return errors.New(constant.SysError) } return nil } func (p peerTx) GetPeer(query interface{}, args ...interface{}) (entity.Peer, error) { var peer entity.Peer if tx := p.db.Model(&entity.Peer{}).Where(query, args...).First(&peer); tx.Error != nil { if tx.Error == gorm.ErrRecordNotFound { return peer, errors.New(constant.WrongPassword) } logrus.Errorf("%v", tx.Error) return peer, errors.New(constant.SysError) } return peer, nil } // global — операции вне транзакции. Одна и та же реализация, другое соединение. func global() peerTx { return peerTx{db: sqliteDB} } func SavePeer(peer entity.Peer) (int64, error) { return global().SavePeer(peer) } func UpdatePeer(ids []int64, updates map[string]interface{}) error { return global().UpdatePeer(ids, updates) } func GetPeer(query interface{}, args ...interface{}) (entity.Peer, error) { return global().GetPeer(query, args...) } func DeletePeer(ids []int64) error { if tx := sqliteDB.Where("id in ?", ids).Delete(&entity.Peer{}); tx.Error != nil { logrus.Errorf("%v", tx.Error) return errors.New(constant.SysError) } return nil } func ListPeer(query interface{}, args ...interface{}) ([]entity.Peer, error) { var peers []entity.Peer if tx := sqliteDB.Model(&entity.Peer{}).Where(query, args...).Order("create_time desc").Find(&peers); tx.Error != nil { logrus.Errorf("%v", tx.Error) return peers, errors.New(constant.SysError) } return peers, nil } func PagePeer(peerPageDto dto.PeerPageDto) ([]entity.Peer, int64, error) { var peers []entity.Peer var total int64 tx := sqliteDB.Model(&entity.Peer{}) if peerPageDto.Name != nil && *peerPageDto.Name != "" { tx.Where("name like ?", fmt.Sprintf("%%%s%%", *peerPageDto.Name)) } if peerPageDto.Disabled != nil { tx.Where("disabled = ?", *peerPageDto.Disabled) } if peerPageDto.Remark != nil && *peerPageDto.Remark != "" { tx.Where("remark like ?", fmt.Sprintf("%%%s%%", *peerPageDto.Remark)) } tx.Count(&total) if tx.Scopes(Paginate(peerPageDto.PageNum, peerPageDto.PageSize)).Order("create_time desc").Find(&peers); tx.Error != nil { logrus.Errorf("%v", tx.Error) return peers, 0, errors.New(constant.SysError) } return peers, total, nil }