123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- package dao
- import (
- "context"
- "database/sql"
- "encoding/json"
- "fmt"
- "strings"
- "time"
- "go-common/app/service/openplatform/ticket-sales/model"
- "go-common/library/cache/redis"
- xsql "go-common/library/database/sql"
- "go-common/library/log"
- "go-common/library/xstr"
- )
- const (
- _lockStockSQL = "UPDATE sku_stock SET stock=stock-?, locked_stock=locked_stock+? WHERE sku_id=? AND stock>=?"
- _unlockStockSQL = "UPDATE sku_stock SET stock=stock+?, locked_stock=locked_stock-? WHERE sku_id=? AND stock<=total_stock-? AND locked_stock>=?"
- _decrStockSQL = "UPDATE sku_stock SET stock=stock-? WHERE sku_id=? AND stock>=?"
- _incrStockSQL = "UPDATE sku_stock SET stock=stock+? WHERE sku_id=? AND stock<=total_stock-?"
- _decrStockLockedSQL = "UPDATE sku_stock SET locked_stock=locked_stock-? WHERE sku_id=? AND total_stock>=?"
- _getStockBySkuIDSQL = "SELECT sku_id, parent_sku_id, item_id, specs, total_stock, stock, locked_stock, sk_alert, ctime, mtime FROM sku_stock WHERE sku_id=? LIMIT 1"
- _getStocksBySkuIDSQL = "SELECT sku_id, parent_sku_id, item_id, specs, total_stock, stock, locked_stock, sk_alert, ctime, mtime FROM sku_stock WHERE sku_id IN (%s)"
- _getStockByItemIDSQL = "SELECT sku_id, parent_sku_id, item_id, specs, total_stock, stock, locked_stock, sk_alert, ctime, mtime FROM sku_stock WHERE item_id=?"
- _getStockBySpecsSQL = "SELECT sku_id, parent_sku_id, item_id, specs, total_stock, stock, locked_stock, sk_alert, ctime, mtime FROM sku_stock WHERE item_id=? AND specs=? LIMIT 1"
- _insertStockSQL = "INSERT INTO sku_stock (sku_id, parent_sku_id, item_id, specs, total_stock, stock, locked_stock, sk_alert) VALUES %s"
- _resetStockSQL = "UPDATE sku_stock SET stock=stock+(?-total_stock),total_stock=?, sk_alert=? WHERE sku_id=? AND stock>=(total_stock-?)"
- _getStockBySkuID = "SELECT sku_id, stock, locked_stock FROM sku_stock WHERE sku_id IN (%s)"
- _insertSKUStockLog = "INSERT INTO sku_stock_log (sku_id, op_type, src_id, stock) VALUES %s"
- _selectSKUStockLog = "SELECT id, sku_id, op_type, src_id, stock FROM sku_stock_log WHERE src_id=? AND op_type=? AND sku_id IN(%s)"
- _rollbackSKUStockLog = "UPDATE sku_stock_log set canceled_at=? where id=? AND canceled_at=0"
- )
- // StockLock lock stock
- func (d *Dao) StockLock(c context.Context, skuID int64, cnt int64) (err error) {
- _, err = d.db.Exec(c, _lockStockSQL, cnt, cnt, skuID, cnt)
- if err != nil {
- log.Error("d.StockLock(%d, %d) error(%v)", skuID, cnt, err)
- }
- return
- }
- // TxStockLock lock stock with tx
- func (d *Dao) TxStockLock(tx *xsql.Tx, skuID int64, cnt int64) (affected int64, err error) {
- res, err := tx.Exec(_lockStockSQL, cnt, cnt, skuID, cnt)
- if err != nil {
- log.Error("d.TxStockLock(%d, %d) error(%v)", skuID, cnt, err)
- return
- }
- affected, err = res.RowsAffected()
- if err != nil {
- log.Error("d.TxStockLock(%d, %d) res.RowsAffected() error(%v)", skuID, cnt, err)
- return
- }
- return
- }
- // StockDecr 减库存 DB
- func (d *Dao) StockDecr(c context.Context, skuID int64, num int64) (affected int64, err error) {
- res, err := d.db.Exec(c, _decrStockSQL, num, skuID, num)
- if err != nil {
- log.Error("d.StockDecr(%d, %d) error(%v)", skuID, num, err)
- return
- }
- affected, err = res.RowsAffected()
- if err != nil {
- log.Error("d.StockDecr(%d, %d) res.RowsAffected() error(%v)", skuID, num, err)
- return
- }
- return
- }
- // TxStockDecr 减库存 DB
- func (d *Dao) TxStockDecr(tx *xsql.Tx, skuID int64, num int64) (affected int64, err error) {
- res, err := tx.Exec(_decrStockSQL, num, skuID, num)
- if err != nil {
- log.Error("d.TxStockDecr(%d, %d) error(%v)", skuID, num, err)
- return
- }
- affected, err = res.RowsAffected()
- if err != nil {
- log.Error("d.TxStockDecr(%d, %d) res.RowsAffected() error(%v)", skuID, num, err)
- return
- }
- return
- }
- // StockCacheDecr 减库存缓存
- func (d *Dao) StockCacheDecr(c context.Context, skuID int64, total int64) (err error) {
- if err = d.RedisDecrExist(c, fmt.Sprintf(model.CacheKeyStock, skuID), total); err != nil {
- log.Error("d.StockCacheDecr(%d) error(%v)", skuID, err)
- }
- return
- }
- // StockLockedCacheDecr 减锁定库存缓存
- func (d *Dao) StockLockedCacheDecr(c context.Context, skuID int64, total int64) (err error) {
- if err = d.RedisDecrExist(c, fmt.Sprintf(model.CacheKeyStockL, skuID), total); err != nil {
- log.Error("d.StockLockedCacheDecr(%d) error(%v)", skuID, err)
- }
- return
- }
- // StockCacheDel 删除库存缓存
- func (d *Dao) StockCacheDel(c context.Context, skuID int64) (err error) {
- if err = d.RedisDel(c, fmt.Sprintf(model.CacheKeyStock, skuID)); err != nil {
- log.Error("d.StockCacheDel(%d) error(%v)", skuID, err)
- }
- return
- }
- // DelCacheSku 删除 skuId => sku 缓存
- func (d *Dao) DelCacheSku(c context.Context, skuID int64) (err error) {
- if err = d.RedisDel(c, fmt.Sprintf(model.CacheKeySku, skuID)); err != nil {
- log.Error("d.StockCacheDel(%d) error(%v)", skuID, err)
- }
- return
- }
- // AddStockLog TxAddStockLog
- func (d *Dao) AddStockLog() {
- }
- // TxAddStockLog 添加库存操作日志
- func (d *Dao) TxAddStockLog(tx *xsql.Tx, stockLogs ...*model.SKUStockLog) (err error) {
- if len(stockLogs) == 0 {
- return
- }
- placeholder := strings.Trim(strings.Repeat("(?, ?, ?, ?),", len(stockLogs)), ",")
- var values []interface{}
- for _, stockLog := range stockLogs {
- values = append(values, stockLog.SKUID, stockLog.OpType, stockLog.SrcID, stockLog.Stock)
- }
- if _, err = tx.Exec(fmt.Sprintf(_insertSKUStockLog, placeholder), values...); err != nil {
- log.Error("d.TxAddStockLog() error(%v)", err)
- return
- }
- return
- }
- // TxStockUnlock 解锁库存(减去锁定库存增加库存)
- func (d *Dao) TxStockUnlock(tx *xsql.Tx, skuID int64, count int64) (affected int64, err error) {
- res, err := tx.Exec(_unlockStockSQL, count, count, skuID, count, count)
- if err != nil {
- log.Error("d.TxStockUnlock(%d, %d) error(%v)", skuID, count, err)
- return
- }
- if affected, err = res.RowsAffected(); err != nil {
- log.Error("d.TxStockUnlock(%d, %d) res.RowsAffected() error(%v)", skuID, count, err)
- }
- return
- }
- // TxStockIncr 增加库存
- func (d *Dao) TxStockIncr(tx *xsql.Tx, skuID int64, count int64) (affected int64, err error) {
- res, err := tx.Exec(_incrStockSQL, count, skuID, count)
- if err != nil {
- log.Error("d.TxStockIncr(%d, %d) error(%v)", skuID, count, err)
- return
- }
- if affected, err = res.RowsAffected(); err != nil {
- log.Error("d.TxStockIncr(%d, %d) res.RowsAffected() error(%v)", skuID, count, err)
- }
- return
- }
- // TxStockLockedDecr 减去锁定库存
- func (d *Dao) TxStockLockedDecr(tx *xsql.Tx, skuID int64, count int64) (affected int64, err error) {
- res, err := tx.Exec(_decrStockLockedSQL, count, skuID, count)
- if err != nil {
- log.Error("d.TxStockLockedDecr(%d, %d) error(%v)", skuID, count, err)
- return
- }
- if affected, err = res.RowsAffected(); err != nil {
- log.Error("d.TxStockLockedDecr(%d, %d) res.RowsAffected() error(%v)", skuID, count, err)
- }
- return
- }
- // Stock 查询库存信息
- func (d *Dao) Stock(c context.Context, skuID int64) (stock *model.SKUStock, err error) {
- stock = new(model.SKUStock)
- if err = d.db.QueryRow(c, _getStockBySkuIDSQL, skuID).Scan(&stock.SKUID, &stock.ParentSKUID, &stock.ItemID, &stock.Specs, &stock.TotalStock, &stock.Stock, &stock.LockedStock, &stock.SkAlert, &stock.Ctime, &stock.Mtime); err != nil {
- log.Error("d.Stock(%d), error(%v)", skuID, err)
- }
- return
- }
- // StockLogs 查询库存操作记录
- func (d *Dao) StockLogs(c context.Context, opType int16, srcID int64, skuIDs ...int64) (stockLogs []*model.SKUStockLog, err error) {
- if len(skuIDs) == 0 {
- return
- }
- rows, err := d.db.Query(c, fmt.Sprintf(_selectSKUStockLog, xstr.JoinInts(skuIDs)), srcID, opType)
- if err != nil {
- log.Error("d.StockLogs() error(%v)", err)
- return
- }
- defer rows.Close()
- stockLogs = make([]*model.SKUStockLog, 0)
- for rows.Next() {
- stockLog := &model.SKUStockLog{}
- if err = rows.Scan(&stockLog.ID, &stockLog.SKUID, &stockLog.OpType, &stockLog.SrcID, &stockLog.Stock); err != nil {
- log.Error("d.StockLogs() rows.Scan() error(%v)", err)
- return
- }
- stockLogs = append(stockLogs)
- }
- return
- }
- // TxAddStockInsert 插入 stock 数据
- func (d *Dao) TxAddStockInsert(tx *xsql.Tx, stocks ...*model.SKUStock) (affected int64, err error) {
- placeholder := strings.Trim(strings.Repeat("(?, ?, ?, ?, ?, ?, ?, ?),", len(stocks)), ",")
- var values []interface{}
- for _, stock := range stocks {
- values = append(values, stock.SKUID, stock.ParentSKUID, stock.ItemID, stock.Specs, stock.TotalStock, stock.Stock, stock.LockedStock, stock.SkAlert)
- }
- res, err := tx.Exec(fmt.Sprintf(_insertStockSQL, placeholder), values...)
- if err != nil {
- log.Error("d.TxStockInsert() error(%v)", err)
- return
- }
- if affected, err = res.RowsAffected(); err != nil {
- log.Error("d.TxStockInsert() res.RowsAffected() error(%v)", err)
- return
- }
- return
- }
- // TxStockReset 重置库存
- func (d *Dao) TxStockReset(tx *xsql.Tx, stock *model.SKUStock) (affected int64, err error) {
- res, err := tx.Exec(_resetStockSQL, stock.TotalStock, stock.TotalStock, stock.SkAlert, stock.SKUID, stock.TotalStock)
- fmt.Println(_resetStockSQL, stock.TotalStock, stock.TotalStock, stock.SkAlert, stock.SKUID, stock.TotalStock)
- if err != nil {
- log.Error("d.TxStockReset() error(%v)", err)
- return
- }
- if affected, err = res.RowsAffected(); err != nil {
- log.Error("d.TxStockReset() res.RowsAffected() error(%v)", err)
- return
- }
- return
- }
- // TxStockLogRollBack 回滚操作日志
- func (d *Dao) TxStockLogRollBack(tx *xsql.Tx, stockLogID int64) (affected int64, err error) {
- res, err := tx.Exec(_rollbackSKUStockLog, time.Now().Unix(), stockLogID)
- if err != nil {
- log.Error("d.TxStockLogRollBack(%d) error(%v)", stockLogID, err)
- return
- }
- if affected, err = res.RowsAffected(); err != nil {
- log.Error("d.TxStockLogRollBack(%d) res.RowsAffected() error(%v)", stockLogID, err)
- }
- return
- }
- // SkuItemCacheDel 删除 itemId => sku 缓存
- func (d *Dao) SkuItemCacheDel(c context.Context, itemID int64) (err error) {
- if err = d.RedisDel(c, fmt.Sprintf(model.CacheKeyItemSku, itemID)); err != nil {
- log.Error("d.SkuItemCacheDel(%d) error(%v)", itemID, err)
- }
- return
- }
- // SkuByItemSpecs 通过 itemID specs 获取单个 sku
- func (d *Dao) SkuByItemSpecs(c context.Context, itemID int64, specs string) (stock *model.SKUStock, err error) {
- res, err := d.SkuByItemID(c, itemID)
- if err != nil {
- log.Error("d.SkuByItemSpecs(%d, %s) d.SkuByItemID() error(%v)", itemID, specs, err)
- return
- }
- if item, ok := res[specs]; ok {
- stock = item
- }
- return
- }
- // RawSkuByItemSpecs 根据 itemID 和规格获取单个 sku
- func (d *Dao) RawSkuByItemSpecs(c context.Context, itemID int64, specs string) (stock *model.SKUStock, err error) {
- stock = new(model.SKUStock)
- if err = d.db.QueryRow(c, _getStockBySpecsSQL, itemID, specs).Scan(&stock.SKUID, &stock.ParentSKUID, &stock.ItemID, &stock.Specs, &stock.TotalStock, &stock.Stock, &stock.LockedStock, &stock.SkAlert, &stock.Ctime, &stock.Mtime); err != nil {
- if err != sql.ErrNoRows {
- log.Error("d.SkuByItemSpecs(%d, %s) error(%v)", itemID, specs, err)
- }
- }
- return
- }
- // RawSkuByItemID 根据规格获取 sku
- func (d *Dao) RawSkuByItemID(c context.Context, itemID int64) (stocks map[string]*model.SKUStock, err error) {
- rows, err := d.db.Query(c, _getStockByItemIDSQL, itemID)
- if err != nil {
- log.Error("d.RawSkuByItemID(%d) error(%v)", itemID, err)
- return
- }
- defer rows.Close()
- stocks = make(map[string]*model.SKUStock)
- for rows.Next() {
- stock := new(model.SKUStock)
- if err = rows.Scan(&stock.SKUID, &stock.ParentSKUID, &stock.ItemID, &stock.Specs, &stock.TotalStock, &stock.Stock, &stock.LockedStock, &stock.SkAlert, &stock.Ctime, &stock.Mtime); err != nil {
- log.Error("d.RawSkuByItemID(%d) rows.Scan() error(%v)", itemID, err)
- return
- }
- stocks[stock.Specs] = stock
- }
- return
- }
- // CacheSkuByItemID 根据 itemID 获取 sku 缓存
- func (d *Dao) CacheSkuByItemID(c context.Context, itemID int64) (stocks map[string]*model.SKUStock, err error) {
- conn := d.redis.Get(c)
- defer conn.Close()
- reply, err := redis.Bytes(conn.Do("GET", fmt.Sprintf(model.CacheKeyItemSku, itemID)))
- if err != nil {
- if err == redis.ErrNil {
- err = nil
- return
- }
- log.Error("d.CacheSkuByItemID(%d) redis.Bytes() error(%v)", itemID, err)
- return
- }
- stocks = make(map[string]*model.SKUStock)
- if err = json.Unmarshal(reply, &stocks); err != nil {
- log.Error("d.CacheSkuByItemID(%d) json.Unmarshal() error(%v)", itemID, err)
- return
- }
- return
- }
- // AddCacheSkuByItemID 添加 itemId => sku 缓存
- func (d *Dao) AddCacheSkuByItemID(c context.Context, itemID int64, stocks map[string]*model.SKUStock) (err error) {
- if stocks == nil {
- return
- }
- conn := d.redis.Get(c)
- defer conn.Close()
- s, err := json.Marshal(stocks)
- if err != nil {
- log.Error("d.AddCacheSkuByItemID(%d, %+v) json.Marshal() error(%v)", itemID, stocks, err)
- return
- }
- if _, err = conn.Do("SETEX", fmt.Sprintf(model.CacheKeyItemSku, itemID), model.RedisExpireSku, s); err != nil {
- log.Error("d.AddCacheSkuByItemID(%d, %+v) conn.Do() error(%v)", itemID, stocks, err)
- return
- }
- return
- }
- // CacheStocks 获取 skuID => stock 库存缓存
- func (d *Dao) CacheStocks(c context.Context, keys []int64, isLocked bool) (res map[int64]int64, err error) {
- if len(keys) == 0 {
- return
- }
- conn := d.redis.Get(c)
- defer conn.Close()
- cacheKey := model.CacheKeyStock
- if isLocked {
- cacheKey = model.CacheKeyStockL
- }
- args := make([]interface{}, 0)
- for _, key := range keys {
- args = append(args, fmt.Sprintf(cacheKey, key))
- }
- int64s, err := redis.Int64s(conn.Do("MGET", args...))
- if err != nil {
- if err == redis.ErrNil {
- err = nil
- return
- }
- log.Error("d.CacheStocks(%v, %t) error(%v)", keys, isLocked, err)
- return
- }
- res = make(map[int64]int64)
- for index, val := range int64s {
- if val < 0 {
- val = 0
- }
- res[keys[index]] = val
- }
- return
- }
- // RawStocks skuID => stock 缓存回源
- func (d *Dao) RawStocks(c context.Context, keys []int64, isLocked bool) (res map[int64]int64, err error) {
- if len(keys) == 0 {
- return
- }
- rows, err := d.db.Query(c, fmt.Sprintf(_getStockBySkuID, xstr.JoinInts(keys)))
- if err != nil {
- log.Error("d.RawStocks() error(%v)", err)
- return
- }
- defer rows.Close()
- res = make(map[int64]int64)
- for rows.Next() {
- var skuID, stock, lockedStock int64
- if err = rows.Scan(&skuID, &stock, &lockedStock); err != nil {
- log.Error("d.RawStocks() rows.Scan() error(%v)", err)
- return
- }
- if isLocked {
- res[skuID] = lockedStock
- } else {
- res[skuID] = stock
- }
- }
- return
- }
- // AddCacheStocks skuID => stock 加入缓存
- func (d *Dao) AddCacheStocks(c context.Context, stocks map[int64]int64, isLocked bool) (err error) {
- cacheKey := model.CacheKeyStock
- if isLocked {
- cacheKey = model.CacheKeyStockL
- }
- for skuID, stock := range stocks {
- if err1 := d.RedisSetnx(c, fmt.Sprintf(cacheKey, skuID), stock, model.RedisExpireStock); err1 != nil {
- log.Warn("d.AddCacheStocks() d.RedisSetnx(%s, %d, %d) error(%v)", fmt.Sprintf(cacheKey, skuID), stock, model.RedisExpireStock, err)
- }
- }
- return
- }
- // CacheGetSKUs 根据 skuID 获取 sku
- // withNewStock 是否获取最新库存信息
- func (d *Dao) CacheGetSKUs(c context.Context, skuIds []int64, withNewStock bool) (skuMap map[int64]*model.SKUStock, err error) {
- if len(skuIds) == 0 {
- return
- }
- conn := d.redis.Get(c)
- defer conn.Close()
- args := make([]interface{}, 0)
- for _, skuID := range skuIds {
- args = append(args, fmt.Sprintf(model.CacheKeySku, skuID))
- }
- res, err := redis.ByteSlices(conn.Do("MGET", args...))
- if err != nil {
- if err == redis.ErrNil {
- err = nil
- return
- }
- log.Error("d.CacheGetSKUs(%v, %t) error(%v)", skuIds, withNewStock, err)
- return
- }
- skuMap = make(map[int64]*model.SKUStock, len(res))
- for _, v := range res {
- if len(v) == 0 {
- continue
- }
- sku := &model.SKUStock{}
- if err = json.Unmarshal(v, sku); err != nil {
- log.Error("d.CacheGetSKUs() json.Unmarshal(%s) error(%v)", v, err)
- return
- }
- skuMap[sku.SKUID] = sku
- }
- if withNewStock {
- var stockMap map[int64]int64
- if stockMap, err = d.Stocks(c, skuIds, false); err != nil {
- log.Error("d.CacheGetSKUs() d.Stocks(%v) error(%v)", skuIds, err)
- return
- }
- for _, sku := range skuMap {
- sku.Stock = stockMap[sku.SKUID]
- }
- }
- return
- }
- // RawGetSKUs .
- func (d *Dao) RawGetSKUs(c context.Context, skuIds []int64, withNewStock bool) (skuMap map[int64]*model.SKUStock, err error) {
- if len(skuIds) == 0 {
- return
- }
- rows, err := d.db.Query(c, fmt.Sprintf(_getStocksBySkuIDSQL, xstr.JoinInts(skuIds)))
- if err != nil {
- log.Error("d.RawGetSKUs() error(%v)", err)
- }
- defer rows.Close()
- skuMap = make(map[int64]*model.SKUStock)
- for rows.Next() {
- sku := &model.SKUStock{}
- if err = rows.Scan(&sku.SKUID, &sku.ParentSKUID, &sku.ItemID, &sku.Specs, &sku.TotalStock, &sku.Stock, &sku.LockedStock, &sku.SkAlert, &sku.Ctime, &sku.Mtime); err != nil {
- log.Error("d.RawGetSKUs() rows.Scan error(%v)", err)
- return
- }
- skuMap[sku.SKUID] = sku
- }
- return
- }
- // AddCacheGetSKUs .
- func (d *Dao) AddCacheGetSKUs(c context.Context, skuMap map[int64]*model.SKUStock, withNewStock bool) (err error) {
- conn := d.redis.Get(c)
- defer func() {
- conn.Flush()
- conn.Close()
- }()
- for skuID, sku := range skuMap {
- var v []byte
- if v, err = json.Marshal(sku); err != nil {
- log.Warn("d.AddCacheGetSKUs() json.Marshal(%v) error(%v)", sku, err)
- err = nil
- continue
- }
- conn.Send("SETEX", fmt.Sprintf(model.CacheKeySku, skuID), model.RedisExpireSkuTmp, v)
- }
- return
- }
|